ConfigLMM 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +34 -0
- data/CNAME +1 -0
- data/Examples/.lmm.state.yaml +159 -0
- data/Examples/ConfigLMM.mm.yaml +32 -0
- data/Examples/Implemented.mm.yaml +252 -4
- data/Examples/SmallBusiness.mm.yaml +492 -0
- data/Plugins/Apps/Answer/answer.lmm.rb +165 -0
- data/Plugins/Apps/Answer/answer@.service +40 -0
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +0 -3
- data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +0 -1
- data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +7 -1
- data/Plugins/Apps/Authentik/Authentik-Server.container +6 -1
- data/Plugins/Apps/Authentik/Authentik-Worker.container +6 -1
- data/Plugins/Apps/Authentik/Authentik.conf.erb +12 -7
- data/Plugins/Apps/Authentik/Authentik.lmm.rb +226 -61
- data/Plugins/Apps/BookStack/BookStack.conf.erb +0 -3
- data/Plugins/Apps/BookStack/BookStack.container +5 -0
- data/Plugins/Apps/BookStack/BookStack.lmm.rb +14 -3
- data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +9 -19
- data/Plugins/Apps/ClickHouse/ClickHouse.container +28 -0
- data/Plugins/Apps/ClickHouse/ClickHouse.lmm.rb +113 -0
- data/Plugins/Apps/ClickHouse/Config/listen.yaml +2 -0
- data/Plugins/Apps/ClickHouse/Config/logger.yaml +8 -0
- data/Plugins/Apps/ClickHouse/Config/zookeepers.yaml +5 -0
- data/Plugins/Apps/ClickHouse/Connection.rb +96 -0
- data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +5 -0
- data/Plugins/Apps/Discourse/Discourse.conf.erb +1 -4
- data/Plugins/Apps/Discourse/Discourse.container +4 -0
- data/Plugins/Apps/Discourse/Discourse.lmm.rb +116 -55
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +74 -62
- data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext-Queue.container +5 -0
- data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +5 -0
- data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext.container +6 -1
- data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +138 -127
- data/Plugins/Apps/GitLab/GitLab.container +6 -0
- data/Plugins/Apps/GitLab/GitLab.lmm.rb +43 -49
- data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
- data/Plugins/Apps/Homepage/Homepage.container +19 -0
- data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
- data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
- data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
- data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
- data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
- data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
- data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
- data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
- data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
- data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +49 -28
- data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
- data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
- data/Plugins/Apps/Lobsters/Containerfile +81 -0
- data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
- data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
- data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
- data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
- data/Plugins/Apps/Lobsters/crontab +3 -0
- data/Plugins/Apps/Lobsters/database.yml +26 -0
- data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
- data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
- data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
- data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
- data/Plugins/Apps/Lobsters/puma.rb +49 -0
- data/Plugins/Apps/MariaDB/Connection.rb +55 -0
- data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +60 -53
- data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
- data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
- data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
- data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
- data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
- data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
- data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
- data/Plugins/Apps/Matrix/Element.container +5 -0
- data/Plugins/Apps/Matrix/Matrix.conf.erb +2 -8
- data/Plugins/Apps/Matrix/Matrix.lmm.rb +100 -71
- data/Plugins/Apps/Matrix/Synapse.container +5 -0
- data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
- data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
- data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
- data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +150 -68
- data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
- data/Plugins/Apps/Nextcloud/config.php +10 -1
- data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
- data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
- data/Plugins/Apps/Nginx/Connection.rb +93 -0
- data/Plugins/Apps/Nginx/conf.d/configlmm.conf +50 -9
- data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +25 -20
- data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
- data/Plugins/Apps/Nginx/config-lmm/proxy.conf +1 -1
- data/Plugins/Apps/Nginx/main.conf.erb +7 -3
- data/Plugins/Apps/Nginx/nginx.conf +2 -2
- data/Plugins/Apps/Nginx/nginx.lmm.rb +99 -81
- data/Plugins/Apps/Nginx/proxy.conf.erb +11 -3
- data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
- data/Plugins/Apps/Odoo/Odoo.container +5 -0
- data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
- data/Plugins/Apps/Ollama/Ollama.container +26 -0
- data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
- data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
- data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
- data/Plugins/Apps/OpenVidu/Ingress.container +5 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +0 -3
- data/Plugins/Apps/OpenVidu/OpenVidu.container +5 -0
- data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +7 -3
- data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +0 -3
- data/Plugins/Apps/OpenVidu/OpenViduCall.container +5 -0
- data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
- data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
- data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -5
- data/Plugins/Apps/Peppermint/Peppermint.container +5 -0
- data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
- data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
- data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
- data/Plugins/Apps/Perplexica/config.toml +26 -0
- data/Plugins/Apps/Podman/Connection.rb +24 -0
- data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
- data/Plugins/Apps/Podman/storage.conf +6 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +242 -164
- data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +184 -148
- data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
- data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
- data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
- data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
- data/Plugins/Apps/Roundcube/Roundcube.conf.erb +0 -3
- data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +15 -19
- data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
- data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
- data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
- data/Plugins/Apps/SearXNG/limiter.toml +40 -0
- data/Plugins/Apps/SearXNG/settings.yml +2 -0
- data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
- data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
- data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
- data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
- data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
- data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
- data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
- data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
- data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
- data/Plugins/Apps/Solr/log4j2.xml +89 -0
- data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
- data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
- data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
- data/Plugins/Apps/Tunnel/tunnel.lmm.rb +33 -37
- data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
- data/Plugins/Apps/Umami/Umami.container +19 -0
- data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
- data/Plugins/Apps/Valkey/Valkey.lmm.rb +54 -42
- data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
- data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
- data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +64 -29
- data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +1 -4
- data/Plugins/Apps/Wiki.js/Wiki.js.container +5 -0
- data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +31 -37
- data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
- data/Plugins/Apps/YaCy/YaCy.container +21 -0
- data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
- data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
- data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
- data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
- data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
- data/Plugins/Apps/gollum/gollum.conf.erb +2 -4
- data/Plugins/Apps/gollum/gollum.container +6 -0
- data/Plugins/Apps/gollum/gollum.lmm.rb +51 -50
- data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
- data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
- data/Plugins/Apps/vLLM/vLLM.container +32 -0
- data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
- data/Plugins/OS/General/Utils.lmm.rb +26 -0
- data/Plugins/OS/Linux/Connection.rb +472 -0
- data/Plugins/OS/Linux/Debian/preseed.cfg.erb +25 -6
- data/Plugins/OS/Linux/Flavours.yaml +13 -0
- data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
- data/Plugins/OS/Linux/HTTP.rb +32 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +533 -187
- data/Plugins/OS/Linux/Packages.yaml +20 -1
- data/Plugins/OS/Linux/Services.yaml +8 -0
- data/Plugins/OS/Linux/Shell.rb +70 -0
- data/Plugins/OS/Linux/Syslinux/default +8 -0
- data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +83 -59
- data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
- data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
- data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
- data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
- data/Plugins/Platforms/GitHub.lmm.rb +73 -28
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +9 -6
- data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
- data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +38 -13
- data/Plugins/Platforms/porkbun.lmm.rb +12 -2
- data/Plugins/Platforms/porkbun_spec.rb +2 -2
- data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
- data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
- data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
- data/Plugins/Services/DNS/PowerDNS.lmm.rb +70 -68
- data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
- data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +145 -184
- data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -17
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +53 -181
- data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
- data/lib/ConfigLMM/Framework/variables.rb +75 -0
- data/lib/ConfigLMM/Framework.rb +1 -0
- data/lib/ConfigLMM/cli.rb +12 -6
- data/lib/ConfigLMM/commands/configsCommand.rb +37 -6
- data/lib/ConfigLMM/commands/diff.rb +33 -9
- data/lib/ConfigLMM/context.rb +22 -3
- data/lib/ConfigLMM/io/configList.rb +82 -6
- data/lib/ConfigLMM/io/connection.rb +143 -0
- data/lib/ConfigLMM/io/dhcp.rb +330 -0
- data/lib/ConfigLMM/io/http.rb +78 -0
- data/lib/ConfigLMM/io/local.rb +207 -0
- data/lib/ConfigLMM/io/pxe.rb +92 -0
- data/lib/ConfigLMM/io/ssh.rb +156 -0
- data/lib/ConfigLMM/io/tftp.rb +105 -0
- data/lib/ConfigLMM/io.rb +2 -0
- data/lib/ConfigLMM/secrets/envStore.rb +39 -0
- data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
- data/lib/ConfigLMM/state.rb +2 -1
- data/lib/ConfigLMM/version.rb +2 -1
- data/lib/ConfigLMM.rb +1 -0
- data/{Examples → scripts}/configlmmAuth.sh +7 -5
- metadata +205 -8
@@ -3,11 +3,6 @@
|
|
3
3
|
require_relative 'errors'
|
4
4
|
require_relative 'store'
|
5
5
|
require 'addressable/uri'
|
6
|
-
require 'http'
|
7
|
-
require 'fileutils'
|
8
|
-
require 'net/ssh'
|
9
|
-
require 'net/scp'
|
10
|
-
require 'open3'
|
11
6
|
|
12
7
|
module ConfigLMM
|
13
8
|
module Framework
|
@@ -49,11 +44,13 @@ module ConfigLMM
|
|
49
44
|
|
50
45
|
attr_accessor :state
|
51
46
|
|
52
|
-
def initialize(logger, prompt, plugins)
|
47
|
+
def initialize(context, logger, prompt, plugins)
|
48
|
+
@Context = context
|
53
49
|
@Logger = logger
|
54
50
|
@Prompt = prompt
|
55
51
|
@Plugins = plugins
|
56
52
|
@Diff = {}
|
53
|
+
@Local = IO::Local.new(prompt, logger)
|
57
54
|
end
|
58
55
|
|
59
56
|
def id
|
@@ -80,6 +77,10 @@ module ConfigLMM
|
|
80
77
|
|
81
78
|
protected
|
82
79
|
|
80
|
+
def context
|
81
|
+
@Context
|
82
|
+
end
|
83
|
+
|
83
84
|
def logger
|
84
85
|
@Logger
|
85
86
|
end
|
@@ -92,127 +93,54 @@ module ConfigLMM
|
|
92
93
|
@Plugins
|
93
94
|
end
|
94
95
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
96
|
+
def local
|
97
|
+
@Local
|
98
|
+
end
|
99
|
+
|
100
|
+
def shouldMatch(id, activeState, stateKey, target, targetKey)
|
101
|
+
data = nil
|
102
|
+
data = activeState[stateKey] if activeState.is_a?(Hash)
|
103
|
+
if target[targetKey] != data
|
104
|
+
@Diff.update({targetKey => [data, target[targetKey]]})
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
101
108
|
def fileWrite(target, data, dry)
|
102
|
-
|
103
|
-
prompt.say('Would write file ' + target)
|
104
|
-
else
|
105
|
-
File.write(target, data)
|
106
|
-
end
|
109
|
+
local.fileWrite(target, data, dry)
|
107
110
|
end
|
108
111
|
|
109
112
|
def copy(source, target, dry)
|
110
|
-
|
111
|
-
prompt.say('Would copy ' + source + ' to ' + target)
|
112
|
-
else
|
113
|
-
FileUtils.cp_r(source, target, noop: dry)
|
114
|
-
end
|
113
|
+
local.copy(source, target, dry)
|
115
114
|
end
|
116
115
|
|
117
116
|
def copyNotPresent(source, target, dry)
|
118
|
-
|
119
|
-
if dry
|
120
|
-
prompt.say('Would copy ' + source + ' to ' + target)
|
121
|
-
else
|
122
|
-
FileUtils.cp_r(source, target, noop: dry)
|
123
|
-
end
|
124
|
-
end
|
117
|
+
local.copyNotPresent(source, target, dry)
|
125
118
|
end
|
126
119
|
|
127
120
|
def rm(path, dry, ssh = nil)
|
128
|
-
if
|
129
|
-
|
130
|
-
prompt.say("Would remove ssh://#{ssh.transport.host}:#{ssh.transport.port}" + path)
|
131
|
-
else
|
132
|
-
prompt.say('Would remove ' + path)
|
133
|
-
end
|
121
|
+
if ssh
|
122
|
+
IO::SSH.new(prompt, logger, ssh).rm(path, dry)
|
134
123
|
else
|
135
|
-
|
136
|
-
self.class.sshExec!(ssh, "rm -rf #{path}")
|
137
|
-
else
|
138
|
-
FileUtils.rm_r(path, noop: dry)
|
139
|
-
end
|
124
|
+
local.rm(path, dry)
|
140
125
|
end
|
141
126
|
end
|
142
127
|
|
143
128
|
def mkdir(target, dry)
|
144
|
-
|
145
|
-
prompt.say('Would create ' + target)
|
146
|
-
else
|
147
|
-
FileUtils.mkdir_p(target)
|
148
|
-
end
|
129
|
+
local.mkdir(target, dry)
|
149
130
|
end
|
150
131
|
|
151
132
|
def chown(user, group, target, dry)
|
152
|
-
|
153
|
-
prompt.say("Would chown #{target} as #{user}:#{group}")
|
154
|
-
else
|
155
|
-
FileUtils.chown_R(user, group, target)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def self.loadVariable(value, target)
|
160
|
-
variableStart = value.index('${')
|
161
|
-
return value unless variableStart
|
162
|
-
variableEnd = value.index('}', variableStart + 2)
|
163
|
-
variableName = value[variableStart + 2...variableEnd]
|
164
|
-
if variableName.start_with?('ENV:')
|
165
|
-
value = value[0...variableStart].to_s + ENV[variableName[4..variableEnd]] + value[(variableEnd + 1)..-1].to_s
|
166
|
-
else
|
167
|
-
raise 'Not implemented!'
|
168
|
-
end
|
169
|
-
value
|
133
|
+
local.chown(user, group, target, dry)
|
170
134
|
end
|
171
135
|
|
172
|
-
CONFIGLMM_SECTION_BEGIN = "# -----BEGIN CONFIGLMM-----\n"
|
173
|
-
CONFIGLMM_SECTION_END = "# -----END CONFIGLMM-----\n"
|
174
|
-
|
175
136
|
def updateLocalFile(file, options, atTop = false, comment = '#')
|
176
|
-
|
177
|
-
sectionBegin = CONFIGLMM_SECTION_BEGIN.gsub('#', comment)
|
178
|
-
sectionEnd = CONFIGLMM_SECTION_END.gsub('#', comment)
|
179
|
-
fileLines = File.read(file).lines
|
180
|
-
sectionBeginIndex = fileLines.index(sectionBegin)
|
181
|
-
sectionEndIndex = fileLines.index(sectionEnd)
|
182
|
-
if sectionBeginIndex.nil?
|
183
|
-
linesBefore = []
|
184
|
-
linesBefore = fileLines unless atTop
|
185
|
-
linesBefore << "\n"
|
186
|
-
linesBefore << sectionBegin
|
187
|
-
linesAfter = [sectionEnd]
|
188
|
-
linesAfter << "\n"
|
189
|
-
linesAfter += fileLines if atTop
|
190
|
-
else
|
191
|
-
linesBefore = fileLines[0..sectionBeginIndex]
|
192
|
-
if sectionEndIndex.nil?
|
193
|
-
linesAfter = [sectionEnd]
|
194
|
-
linesAfter << "\n"
|
195
|
-
else
|
196
|
-
linesAfter = fileLines[sectionEndIndex..fileLines.length]
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
fileLines = linesBefore
|
201
|
-
fileLines = yield(fileLines)
|
202
|
-
fileLines += linesAfter
|
203
|
-
|
204
|
-
fileWrite(file, fileLines.join(), options[:dry])
|
137
|
+
local.updateFile(file, options, atTop, comment)
|
205
138
|
end
|
206
139
|
|
140
|
+
# DEPRECATED
|
207
141
|
def updateRemoteFile(locationOrSSH, file, options, atTop = false, comment = '#', &block)
|
208
|
-
|
209
142
|
closure = Proc.new do |ssh|
|
210
|
-
|
211
|
-
File.write(localFile, '')
|
212
|
-
self.class.sshExec!(ssh, "touch #{file}")
|
213
|
-
ssh.scp.download!(file, localFile)
|
214
|
-
updateLocalFile(localFile, options, atTop, comment, &block)
|
215
|
-
ssh.scp.upload!(localFile, file)
|
143
|
+
IO::SSH.new(prompt, logger, ssh).updateFile(file, options, atTop, comment, &block)
|
216
144
|
end
|
217
145
|
|
218
146
|
if locationOrSSH.is_a?(String) || locationOrSSH.is_a?(Addressable::URI)
|
@@ -227,6 +155,7 @@ module ConfigLMM
|
|
227
155
|
end
|
228
156
|
end
|
229
157
|
|
158
|
+
# DEPRECATED
|
230
159
|
def self.filePresent?(file, ssh = nil)
|
231
160
|
result = self.exec("stat #{file}", ssh, true)
|
232
161
|
!result.start_with?('stat: cannot')
|
@@ -237,100 +166,38 @@ module ConfigLMM
|
|
237
166
|
self.filePresent?(file, ssh)
|
238
167
|
end
|
239
168
|
|
240
|
-
def self.remoteFileContains?(file, content, ssh)
|
241
|
-
!self.sshExec!(ssh, "grep '#{content}' #{file}", true).strip.empty?
|
242
|
-
end
|
243
|
-
|
244
|
-
def self.uploadNotPresent(file, target, ssh)
|
245
|
-
target += '/' + File.basename(file)
|
246
|
-
if !self.remoteFilePresent?(target, ssh)
|
247
|
-
ssh.scp.upload!(file, target)
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
169
|
def self.uploadFolder(folder, target, ssh)
|
252
170
|
target += '/' + File.basename(folder) + '/'
|
253
|
-
|
171
|
+
IO::SSH.exec!(ssh, "mkdir -p #{target}")
|
254
172
|
Dir[folder + '/*'].each do |file|
|
255
173
|
ssh.scp.upload!(file, target + File.basename(file), recursive: true)
|
256
174
|
end
|
257
175
|
end
|
258
176
|
|
259
|
-
def
|
260
|
-
|
261
|
-
if dry
|
262
|
-
puts "Would execute: #{command}"
|
263
|
-
return
|
264
|
-
end
|
265
|
-
stdout, stdeerr, status = Open3.capture3(command)
|
266
|
-
if !allowFailure && !status.success?
|
267
|
-
$stderr.puts(stdout)
|
268
|
-
$stderr.puts(stdeerr)
|
269
|
-
raise Framework::PluginProcessError.new("Failed '#{command}'")
|
270
|
-
end
|
271
|
-
stdout + stdeerr
|
272
|
-
else
|
273
|
-
self.sshExec!(ssh, command, allowFailure, dry)
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def self.cmdSuccess?(command, ssh = nil)
|
278
|
-
if ssh.nil?
|
279
|
-
system(command, :out => File::NULL)
|
280
|
-
else
|
281
|
-
self.sshSuccess?(ssh, command)
|
282
|
-
end
|
177
|
+
def withConnection(uri, target, &block)
|
178
|
+
IO::Connection.tunnel(uri, target, self.context, self.prompt, self.logger, &block)
|
283
179
|
end
|
284
180
|
|
285
|
-
def
|
286
|
-
|
287
|
-
params = {}
|
288
|
-
params[:port] = locationUri.port if locationUri.port
|
289
|
-
params[:user] = locationUri.user if locationUri.user
|
290
|
-
[server, params]
|
181
|
+
def ping(uri, target, &block)
|
182
|
+
IO::Connection.ping(uri, target, self.context, self.prompt, self.logger, &block)
|
291
183
|
end
|
292
184
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
cmd = 'ssh '
|
297
|
-
cmd += '-p ' + sshParams[:port] if sshParams[:port]
|
298
|
-
cmd += sshParams[:user] + '@' if sshParams[:port]
|
299
|
-
cmd + server
|
185
|
+
# DEPRECATED
|
186
|
+
def self.exec(command, ssh = nil, allowFailure = false, dry = false)
|
187
|
+
IO::Connection.exec(command, ssh, allowFailure, { dry: dry })
|
300
188
|
end
|
301
189
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
Net::SSH.start(server, nil, sshParams) do |ssh|
|
306
|
-
yield(ssh)
|
307
|
-
end
|
190
|
+
# DEPRECATED
|
191
|
+
def self.sshStart(uri, &block)
|
192
|
+
IO::SSH.tunnel(uri, &block)
|
308
193
|
end
|
309
194
|
|
195
|
+
# DEPRECATED
|
310
196
|
def self.sshExec!(ssh, command, allowFailure = false, dry = false)
|
311
|
-
|
312
|
-
puts "Would execute: ssh #{ssh.transport.host} -p #{ssh.transport.port} '#{command}'"
|
313
|
-
return
|
314
|
-
end
|
315
|
-
status = {}
|
316
|
-
output = ''
|
317
|
-
channel = ssh.exec(command, status: status) do |channel, stream, data|
|
318
|
-
output += data
|
319
|
-
end
|
320
|
-
channel.wait
|
321
|
-
if !allowFailure && !status[:exit_code].zero?
|
322
|
-
$stderr.puts(output)
|
323
|
-
raise Framework::PluginProcessError.new("Failed '#{command}'")
|
324
|
-
end
|
325
|
-
output
|
326
|
-
end
|
327
|
-
|
328
|
-
def self.sshSuccess?(ssh, command)
|
329
|
-
status = {}
|
330
|
-
ssh.exec!(command, status)
|
331
|
-
status[:exit_code].zero?
|
197
|
+
IO::SSH.exec!(ssh, command, allowFailure, { dry: dry })
|
332
198
|
end
|
333
199
|
|
200
|
+
# DEPRECATED
|
334
201
|
def renderTemplate(template, target, outputPath, options)
|
335
202
|
variables = {
|
336
203
|
config: target,
|
@@ -348,12 +215,17 @@ module ConfigLMM
|
|
348
215
|
items = state.selectType(type)
|
349
216
|
items.each do |id, item|
|
350
217
|
if !configs.key?(id) && item['Status'] != State::STATUS_DESTROYED && (item['Status'] != State::STATUS_DELETED || options[:destroy])
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
218
|
+
begin
|
219
|
+
self.withConnection(item['Config']['Location'], item) do |connection|
|
220
|
+
yield(item, id, state, context, options, connection)
|
221
|
+
end
|
222
|
+
rescue SystemCallError => error
|
223
|
+
if error.errno == Errno::EHOSTUNREACH::Errno
|
224
|
+
prompt.say("#{id}: #{item[:Type].to_s} failed to connect #{item['Config'].to_h['Location'].to_s}", color: :red)
|
225
|
+
prompt.say(error, color: :red)
|
226
|
+
prompt.say("Skipping!", color: :red)
|
227
|
+
else
|
228
|
+
raise
|
357
229
|
end
|
358
230
|
end
|
359
231
|
end
|
@@ -37,19 +37,19 @@ module ConfigLMM
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def self.boot(logger, prompt, plugins)
|
40
|
+
def self.boot(context, logger, prompt, plugins)
|
41
41
|
leafPlugins.each do |id, plugin|
|
42
|
-
self.initPlugin(id, logger, prompt, plugins)
|
42
|
+
self.initPlugin(id, context, logger, prompt, plugins)
|
43
43
|
rescue PluginLoadError => error
|
44
44
|
logger.warn("Plugin '#{id}' failed to load!\n#{error.message}" + (error.cause ? ' - ' : ''), error.cause)
|
45
45
|
end
|
46
46
|
true
|
47
47
|
end
|
48
48
|
|
49
|
-
def self.initPlugin(pluginId, logger, prompt, plugins)
|
49
|
+
def self.initPlugin(pluginId, context, logger, prompt, plugins)
|
50
50
|
pluginId = pluginId.to_sym
|
51
51
|
raise 'Recursive/cyclic plugin' if plugins.key?(pluginId)
|
52
|
-
plugins[pluginId] = @@AvailablePlugins[pluginId].new(logger, prompt, plugins)
|
52
|
+
plugins[pluginId] = @@AvailablePlugins[pluginId].new(context, logger, prompt, plugins)
|
53
53
|
end
|
54
54
|
|
55
55
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConfigLMM
|
4
|
+
module Framework
|
5
|
+
class Variable
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(name, args, context)
|
10
|
+
@name = name
|
11
|
+
@args = args
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def eval()
|
16
|
+
if name == 'ENV'
|
17
|
+
value = ENV[@args.first]
|
18
|
+
raise "Environment variable #{@args.first} not found!" if value.nil?
|
19
|
+
value
|
20
|
+
elsif name == 'SECRET'
|
21
|
+
value = @context.secrets.load(*@args)
|
22
|
+
raise "Secret #{@args.join(':')} not found!" if value.nil?
|
23
|
+
value
|
24
|
+
elsif name == 'GENERATE'
|
25
|
+
secretId, secretName, length = @args
|
26
|
+
raise 'Not enough arguments for GENERATE!' if secretId.to_s.empty? || secretName.to_s.empty?
|
27
|
+
value = @context.secrets.load(secretId, secretName)
|
28
|
+
if value.nil?
|
29
|
+
length = 30 unless length
|
30
|
+
value = SecureRandom.alphanumeric(length)
|
31
|
+
@context.secrets.store(secretId, secretName, value)
|
32
|
+
end
|
33
|
+
value
|
34
|
+
else
|
35
|
+
raise "Unsupported function #{name}!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s()
|
40
|
+
eval()
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def Value
|
45
|
+
def initialize(value)
|
46
|
+
@value = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s()
|
50
|
+
@value.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Variables
|
55
|
+
def self.parse(value, context)
|
56
|
+
if value.to_s[0, 2] == '${'
|
57
|
+
raise "Unterminated variable: #{value}" if value[-1] != '}'
|
58
|
+
parts = value[2...-1].split(':')
|
59
|
+
Variable.new(parts.shift, parts, context)
|
60
|
+
else
|
61
|
+
Value.new(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.stringEval(data, context)
|
66
|
+
variableStart = data.index('${')
|
67
|
+
return data unless variableStart
|
68
|
+
variableEnd = data.index('}', variableStart + 2)
|
69
|
+
raise "Unterminated variable: #{data}" if variableEnd.nil?
|
70
|
+
parts = data[variableStart + 2...variableEnd].split(':')
|
71
|
+
data[0...variableStart].to_s + Variable.new(parts.shift, parts, context).to_s + data[(variableEnd + 1)..-1].to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/ConfigLMM/Framework.rb
CHANGED
data/lib/ConfigLMM/cli.rb
CHANGED
@@ -45,6 +45,8 @@ module ConfigLMM
|
|
45
45
|
desc 'refresh [CONFIGS...]', 'Update local state to match deployed things'
|
46
46
|
method_option :help, aliases: '-h', type: :boolean,
|
47
47
|
desc: 'Display usage information'
|
48
|
+
method_option :secrets, aliases: '-s', type: :string, default: '.secrets',
|
49
|
+
desc: 'Path to the secrets provider (can be a file)'
|
48
50
|
method_option :state, aliases: '-s', type: :string,
|
49
51
|
desc: 'Path to the state file'
|
50
52
|
method_option :context, aliases: '-c', type: :string,
|
@@ -82,6 +84,8 @@ module ConfigLMM
|
|
82
84
|
desc 'deploy [CONFIGS...]', 'Deploy configuration'
|
83
85
|
method_option :help, aliases: '-h', type: :boolean,
|
84
86
|
desc: 'Display usage information'
|
87
|
+
method_option :secrets, aliases: '-s', type: :string, default: '.secrets',
|
88
|
+
desc: 'Path to the secrets provider (can be a file)'
|
85
89
|
method_option :state, aliases: '-s', type: :string,
|
86
90
|
desc: 'Path to the state file'
|
87
91
|
method_option :context, aliases: '-c', type: :string,
|
@@ -95,12 +99,14 @@ module ConfigLMM
|
|
95
99
|
|
96
100
|
|
97
101
|
desc 'cleanup [CONFIGS...]', 'In deployed infrastructure cleanup/delete unused things (eg. deployment leftover junk) (note this can be risky due to mistakes)'
|
98
|
-
method_option :help,
|
99
|
-
|
100
|
-
method_option :
|
101
|
-
|
102
|
-
method_option :
|
103
|
-
|
102
|
+
method_option :help, aliases: '-h', type: :boolean,
|
103
|
+
desc: 'Display usage information'
|
104
|
+
method_option :secrets, aliases: '-s', type: :string, default: '.secrets',
|
105
|
+
desc: 'Path to the secrets provider (can be a file)'
|
106
|
+
method_option :state, aliases: '-s', type: :string,
|
107
|
+
desc: 'Path to the state file'
|
108
|
+
method_option :destroy, aliases: '-d', type: :boolean,
|
109
|
+
desc: 'Destroy all data (eg. even DB)'
|
104
110
|
def cleanup(*configPaths)
|
105
111
|
handleCommand(:cleanup, configPaths, options)
|
106
112
|
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
require_relative '../command'
|
4
4
|
require_relative '../utils/filters'
|
5
|
-
require_relative '../io/configList'
|
6
5
|
require_relative '../context'
|
7
6
|
require_relative '../state'
|
8
7
|
require_relative '../Framework'
|
9
8
|
require_relative '../LMM'
|
9
|
+
require_relative '../io'
|
10
10
|
require 'xdg'
|
11
11
|
require 'tmpdir'
|
12
12
|
|
@@ -23,7 +23,7 @@ module ConfigLMM
|
|
23
23
|
config.level = @Options[:level]
|
24
24
|
end
|
25
25
|
|
26
|
-
@Context = Context.new(logger, prompt, ::XDG.new, @Options
|
26
|
+
@Context = Context.new(logger, prompt, ::XDG.new, @Options)
|
27
27
|
@State = State.new(logger, prompt)
|
28
28
|
@Diff = {}
|
29
29
|
|
@@ -31,7 +31,7 @@ module ConfigLMM
|
|
31
31
|
Framework::Registrator.registerAll(logger)
|
32
32
|
|
33
33
|
# Create Plugin instances
|
34
|
-
Framework::Store.boot(logger, prompt, @Plugins)
|
34
|
+
Framework::Store.boot(@Context, logger, prompt, @Plugins)
|
35
35
|
@Plugins.each do |id, plugin|
|
36
36
|
plugin.state = @State
|
37
37
|
end
|
@@ -89,6 +89,10 @@ module ConfigLMM
|
|
89
89
|
elsif activeState[:Type] != singleTarget['Type'].to_s
|
90
90
|
raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
|
91
91
|
end
|
92
|
+
|
93
|
+
singleTarget['Location'] = '@me' unless singleTarget['Location']
|
94
|
+
singleTarget['SecretId'] = (singleTarget['SecretId'] || id).upcase
|
95
|
+
|
92
96
|
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Refresh')
|
93
97
|
if plugin.methods.include?(:authenticate)
|
94
98
|
result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
|
@@ -124,8 +128,10 @@ module ConfigLMM
|
|
124
128
|
elsif activeState[:Type] != singleTarget['Type'].to_s
|
125
129
|
raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
|
126
130
|
end
|
127
|
-
|
128
|
-
|
131
|
+
|
132
|
+
singleTarget['Location'] = '@me' unless singleTarget['Location']
|
133
|
+
singleTarget['SecretId'] = (singleTarget['SecretId'] || id).upcase
|
134
|
+
|
129
135
|
actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Deploy')
|
130
136
|
if plugin.methods.include?(:authenticate)
|
131
137
|
result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
|
@@ -148,7 +154,7 @@ module ConfigLMM
|
|
148
154
|
end
|
149
155
|
|
150
156
|
if !options['dry']
|
151
|
-
|
157
|
+
# Prevent others accessing it
|
152
158
|
FileUtils.chmod(0750, options['output'])
|
153
159
|
end
|
154
160
|
|
@@ -158,9 +164,34 @@ module ConfigLMM
|
|
158
164
|
|
159
165
|
plugin.send(actionMethod, id, singleTarget, activeState, context, options)
|
160
166
|
end
|
167
|
+
activeState['Config'] = self.class.sanitizeConfig(singleTarget)
|
168
|
+
activeState['Config'].delete(:Parent)
|
169
|
+
activeState['Config'].delete('Resources')
|
170
|
+
activeState['Config'] = activeState['Config'].sort.to_h
|
171
|
+
activeState['Status'] = State::STATUS_DEPLOYED if !activeState['Status'] || [State::STATUS_DELETED, State::STATUS_DESTROYED].include?(activeState['Status'])
|
161
172
|
state.save
|
162
173
|
end
|
163
174
|
|
175
|
+
def self.sanitizeConfig(config)
|
176
|
+
return config unless config.is_a?(Hash)
|
177
|
+
newConfig = config.dup
|
178
|
+
config.each do |key, value|
|
179
|
+
lowerkey = key.to_s.downcase
|
180
|
+
if lowerkey.include?('password') || lowerkey.include?('privatekey') || lowerkey.include?('sharedkey')
|
181
|
+
newConfig.delete(key)
|
182
|
+
elsif value.is_a?(Hash)
|
183
|
+
newConfig[key] = self.sanitizeConfig(value)
|
184
|
+
elsif value.is_a?(Array)
|
185
|
+
newValue = value
|
186
|
+
value.each_with_index do |v, i|
|
187
|
+
newValue[i] = self.sanitizeConfig(v)
|
188
|
+
end
|
189
|
+
newConfig[key] = newValue
|
190
|
+
end
|
191
|
+
end
|
192
|
+
newConfig
|
193
|
+
end
|
194
|
+
|
164
195
|
|
165
196
|
def checkDiff(id, targetName, stateName, target, activeState)
|
166
197
|
if target[targetName] != activeState[stateName]
|
@@ -22,22 +22,46 @@ module ConfigLMM
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def showDiff(configDiffs)
|
25
|
+
level = 4
|
25
26
|
configDiffs.each do |id, diffs|
|
26
27
|
prompt.say(' ' + id + ':')
|
27
28
|
diffs.each do |name, diff|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
outputDiff(name.to_s, ' ', diff.first, diff.last, level)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def outputDiff(name, type, old, new, level)
|
35
|
+
indent = ' ' * level
|
36
|
+
if old.is_a?(Hash) || new.is_a?(Hash)
|
37
|
+
if type == '-'
|
38
|
+
prompt.say('-'+ indent + name + ':', :color => :red)
|
39
|
+
elsif type == '+'
|
40
|
+
prompt.say('+'+ indent + name + ':', :color => :green)
|
41
|
+
else
|
42
|
+
prompt.say(' '+ indent + name + ':')
|
43
|
+
end
|
44
|
+
if !old.nil?
|
45
|
+
old.each do |name, value|
|
46
|
+
if value.is_a?(Hash)
|
47
|
+
outputDiff(name.to_s, '-', value, nil, level + 4)
|
48
|
+
else
|
49
|
+
prompt.say('-' + (' ' * (level + 4)) + name.to_s + ': ' + value.to_s, :color => :red)
|
32
50
|
end
|
33
|
-
|
34
|
-
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if !new.nil?
|
54
|
+
new.each do |name, value|
|
55
|
+
if value.is_a?(Hash)
|
56
|
+
outputDiff(name.to_s, '+', nil, value, level + 4)
|
57
|
+
else
|
58
|
+
prompt.say('+' + (' ' * (level + 4)) + name.to_s + ': ' + value.to_s, :color => :green)
|
35
59
|
end
|
36
|
-
else
|
37
|
-
prompt.say('- ' + name + ': ' + diff.first, :color => :red)
|
38
|
-
prompt.say('+ ' + name + ': ' + diff.last, :color => :green)
|
39
60
|
end
|
40
61
|
end
|
62
|
+
else
|
63
|
+
prompt.say('-' + indent + name + ': ' + old.to_s, :color => :red) unless old.nil?
|
64
|
+
prompt.say('+' + indent + name + ': ' + new.to_s, :color => :green) unless new.nil?
|
41
65
|
end
|
42
66
|
end
|
43
67
|
end
|