ConfigLMM 0.3.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.
Files changed (250) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/CNAME +1 -0
  4. data/Examples/.lmm.state.yaml +159 -0
  5. data/Examples/ConfigLMM.mm.yaml +32 -0
  6. data/Examples/Implemented.mm.yaml +252 -4
  7. data/Examples/SmallBusiness.mm.yaml +492 -0
  8. data/Plugins/Apps/Answer/answer.lmm.rb +165 -0
  9. data/Plugins/Apps/Answer/answer@.service +40 -0
  10. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +0 -3
  11. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +0 -1
  12. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +20 -0
  13. data/Plugins/Apps/Authentik/Authentik-Server.container +7 -1
  14. data/Plugins/Apps/Authentik/Authentik-Worker.container +7 -1
  15. data/Plugins/Apps/Authentik/Authentik.conf.erb +18 -6
  16. data/Plugins/Apps/Authentik/Authentik.lmm.rb +232 -45
  17. data/Plugins/Apps/BookStack/BookStack.conf.erb +38 -0
  18. data/Plugins/Apps/BookStack/BookStack.container +20 -0
  19. data/Plugins/Apps/BookStack/BookStack.lmm.rb +91 -0
  20. data/Plugins/Apps/Cassandra/Cassandra.lmm.rb +9 -19
  21. data/Plugins/Apps/ClickHouse/ClickHouse.container +28 -0
  22. data/Plugins/Apps/ClickHouse/ClickHouse.lmm.rb +113 -0
  23. data/Plugins/Apps/ClickHouse/Config/listen.yaml +2 -0
  24. data/Plugins/Apps/ClickHouse/Config/logger.yaml +8 -0
  25. data/Plugins/Apps/ClickHouse/Config/zookeepers.yaml +5 -0
  26. data/Plugins/Apps/ClickHouse/Connection.rb +96 -0
  27. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +22 -0
  28. data/Plugins/Apps/Discourse/Discourse.conf.erb +38 -0
  29. data/Plugins/Apps/Discourse/Discourse.container +21 -0
  30. data/Plugins/Apps/Discourse/Discourse.lmm.rb +156 -0
  31. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +87 -52
  32. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +24 -0
  33. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +22 -0
  34. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +22 -0
  35. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +24 -0
  36. data/Plugins/Apps/ERPNext/ERPNext.container +23 -0
  37. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +204 -0
  38. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  39. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  40. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  41. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  42. data/Plugins/Apps/GitLab/GitLab.container +9 -2
  43. data/Plugins/Apps/GitLab/GitLab.lmm.rb +52 -33
  44. data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
  45. data/Plugins/Apps/Homepage/Homepage.container +19 -0
  46. data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
  47. data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
  48. data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
  49. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
  50. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
  51. data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
  52. data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
  53. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
  54. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
  55. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +78 -0
  56. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  57. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  58. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  59. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  60. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  61. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  62. data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
  63. data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
  64. data/Plugins/Apps/Lobsters/Containerfile +81 -0
  65. data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
  66. data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
  67. data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
  68. data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
  69. data/Plugins/Apps/Lobsters/crontab +3 -0
  70. data/Plugins/Apps/Lobsters/database.yml +26 -0
  71. data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
  72. data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
  73. data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
  74. data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
  75. data/Plugins/Apps/Lobsters/puma.rb +49 -0
  76. data/Plugins/Apps/MariaDB/Connection.rb +55 -0
  77. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +122 -0
  78. data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
  79. data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
  80. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
  81. data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
  82. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
  83. data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
  84. data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
  85. data/Plugins/Apps/Matrix/Element.container +19 -0
  86. data/Plugins/Apps/Matrix/Matrix.conf.erb +47 -9
  87. data/Plugins/Apps/Matrix/Matrix.lmm.rb +119 -5
  88. data/Plugins/Apps/Matrix/Synapse.container +22 -0
  89. data/Plugins/Apps/Matrix/config.json +50 -0
  90. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  91. data/Plugins/Apps/Matrix/log.config +30 -0
  92. data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
  93. data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
  94. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
  95. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +155 -48
  96. data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
  97. data/Plugins/Apps/Nextcloud/config.php +10 -1
  98. data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
  99. data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
  100. data/Plugins/Apps/Nginx/Connection.rb +93 -0
  101. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +54 -4
  102. data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
  103. data/Plugins/Apps/Nginx/config-lmm/errors.conf +33 -22
  104. data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
  105. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +6 -2
  106. data/Plugins/Apps/Nginx/main.conf.erb +7 -3
  107. data/Plugins/Apps/Nginx/nginx.conf +2 -2
  108. data/Plugins/Apps/Nginx/nginx.lmm.rb +103 -81
  109. data/Plugins/Apps/Nginx/proxy.conf.erb +24 -6
  110. data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
  111. data/Plugins/Apps/Odoo/Odoo.container +7 -1
  112. data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
  113. data/Plugins/Apps/Ollama/Ollama.container +26 -0
  114. data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
  115. data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
  116. data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
  117. data/Plugins/Apps/OpenVidu/Ingress.container +23 -0
  118. data/Plugins/Apps/{GitLab/GitLab.conf.erb → OpenVidu/OpenVidu.conf.erb} +8 -3
  119. data/Plugins/Apps/OpenVidu/OpenVidu.container +21 -0
  120. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +94 -0
  121. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +32 -0
  122. data/Plugins/Apps/OpenVidu/OpenViduCall.container +20 -0
  123. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  124. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  125. data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
  126. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
  127. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -9
  128. data/Plugins/Apps/Peppermint/Peppermint.container +7 -1
  129. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
  130. data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
  131. data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
  132. data/Plugins/Apps/Perplexica/config.toml +26 -0
  133. data/Plugins/Apps/Podman/Connection.rb +24 -0
  134. data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
  135. data/Plugins/Apps/Podman/storage.conf +6 -0
  136. data/Plugins/Apps/Postfix/Postfix.lmm.rb +249 -145
  137. data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
  138. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +204 -99
  139. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
  140. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
  141. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
  142. data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
  143. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +72 -0
  144. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +141 -0
  145. data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
  146. data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
  147. data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
  148. data/Plugins/Apps/SearXNG/limiter.toml +40 -0
  149. data/Plugins/Apps/SearXNG/settings.yml +2 -0
  150. data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
  151. data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
  152. data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
  153. data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
  154. data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
  155. data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
  156. data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
  157. data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
  158. data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
  159. data/Plugins/Apps/Solr/log4j2.xml +89 -0
  160. data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
  161. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
  162. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
  163. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +59 -0
  164. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  165. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  166. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  167. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  168. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
  169. data/Plugins/Apps/Umami/Umami.container +19 -0
  170. data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
  171. data/Plugins/Apps/Valkey/Valkey.lmm.rb +64 -20
  172. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
  173. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
  174. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +67 -28
  175. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +39 -0
  176. data/Plugins/Apps/Wiki.js/Wiki.js.container +20 -0
  177. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +55 -0
  178. data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
  179. data/Plugins/Apps/YaCy/YaCy.container +21 -0
  180. data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
  181. data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
  182. data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
  183. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
  184. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
  185. data/Plugins/Apps/gollum/gollum.conf.erb +40 -4
  186. data/Plugins/Apps/gollum/gollum.container +10 -1
  187. data/Plugins/Apps/gollum/gollum.lmm.rb +56 -47
  188. data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
  189. data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
  190. data/Plugins/Apps/vLLM/vLLM.container +32 -0
  191. data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
  192. data/Plugins/OS/General/Utils.lmm.rb +26 -0
  193. data/Plugins/OS/Linux/Connection.rb +472 -0
  194. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +81 -0
  195. data/Plugins/OS/Linux/Distributions.yaml +32 -0
  196. data/Plugins/OS/Linux/Flavours.yaml +24 -0
  197. data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
  198. data/Plugins/OS/Linux/HTTP.rb +32 -0
  199. data/Plugins/OS/Linux/Linux.lmm.rb +708 -174
  200. data/Plugins/OS/Linux/Packages.yaml +67 -3
  201. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  202. data/Plugins/OS/Linux/Services.yaml +8 -0
  203. data/Plugins/OS/Linux/Shell.rb +70 -0
  204. data/Plugins/OS/Linux/Syslinux/default +8 -0
  205. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +93 -40
  206. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
  207. data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
  208. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
  209. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
  210. data/Plugins/Platforms/GitHub.lmm.rb +73 -28
  211. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +10 -7
  212. data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
  213. data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
  214. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +41 -15
  215. data/Plugins/Platforms/porkbun.lmm.rb +12 -2
  216. data/Plugins/Platforms/porkbun_spec.rb +2 -2
  217. data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
  218. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
  219. data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
  220. data/Plugins/Services/DNS/PowerDNS.lmm.rb +130 -41
  221. data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
  222. data/bootstrap.sh +41 -3
  223. data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
  224. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +187 -144
  225. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +54 -6
  226. data/lib/ConfigLMM/Framework/plugins/plugin.rb +68 -140
  227. data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
  228. data/lib/ConfigLMM/Framework/variables.rb +75 -0
  229. data/lib/ConfigLMM/Framework.rb +1 -0
  230. data/lib/ConfigLMM/cli.rb +13 -5
  231. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  232. data/lib/ConfigLMM/commands/configsCommand.rb +38 -5
  233. data/lib/ConfigLMM/commands/diff.rb +33 -9
  234. data/lib/ConfigLMM/context.rb +22 -3
  235. data/lib/ConfigLMM/io/configList.rb +85 -7
  236. data/lib/ConfigLMM/io/connection.rb +143 -0
  237. data/lib/ConfigLMM/io/dhcp.rb +330 -0
  238. data/lib/ConfigLMM/io/http.rb +78 -0
  239. data/lib/ConfigLMM/io/local.rb +207 -0
  240. data/lib/ConfigLMM/io/pxe.rb +92 -0
  241. data/lib/ConfigLMM/io/ssh.rb +156 -0
  242. data/lib/ConfigLMM/io/tftp.rb +105 -0
  243. data/lib/ConfigLMM/io.rb +2 -0
  244. data/lib/ConfigLMM/secrets/envStore.rb +39 -0
  245. data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
  246. data/lib/ConfigLMM/state.rb +12 -3
  247. data/lib/ConfigLMM/version.rb +2 -1
  248. data/lib/ConfigLMM.rb +1 -0
  249. data/{Examples → scripts}/configlmmAuth.sh +7 -5
  250. metadata +257 -9
@@ -3,17 +3,13 @@
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
14
9
 
15
10
  class Plugin
16
11
 
12
+ REPOS_CACHE = '~/.cache/configlmm/repos'
17
13
 
18
14
  def self.inherited(plugin)
19
15
  Store.registerPlugin(plugin)
@@ -48,11 +44,13 @@ module ConfigLMM
48
44
 
49
45
  attr_accessor :state
50
46
 
51
- def initialize(logger, prompt, plugins)
47
+ def initialize(context, logger, prompt, plugins)
48
+ @Context = context
52
49
  @Logger = logger
53
50
  @Prompt = prompt
54
51
  @Plugins = plugins
55
52
  @Diff = {}
53
+ @Local = IO::Local.new(prompt, logger)
56
54
  end
57
55
 
58
56
  def id
@@ -79,6 +77,10 @@ module ConfigLMM
79
77
 
80
78
  protected
81
79
 
80
+ def context
81
+ @Context
82
+ end
83
+
82
84
  def logger
83
85
  @Logger
84
86
  end
@@ -91,119 +93,54 @@ module ConfigLMM
91
93
  @Plugins
92
94
  end
93
95
 
94
- def shouldMatch(id, targetKey, stateKey, target, activeState)
95
- if target[targetKey] != activeState[stateKey]
96
- @Diff.update({targetKey => [target[targetKey], activeState[stateKey]]})
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]]})
97
105
  end
98
106
  end
99
107
 
100
108
  def fileWrite(target, data, dry)
101
- if dry
102
- prompt.say('Would write file ' + target)
103
- else
104
- File.write(target, data)
105
- end
109
+ local.fileWrite(target, data, dry)
106
110
  end
107
111
 
108
112
  def copy(source, target, dry)
109
- if dry
110
- prompt.say('Would copy ' + source + ' to ' + target)
111
- else
112
- FileUtils.cp_r(source, target, noop: dry)
113
- end
113
+ local.copy(source, target, dry)
114
114
  end
115
115
 
116
116
  def copyNotPresent(source, target, dry)
117
- if !File.exist?(target + File.basename(source))
118
- if dry
119
- prompt.say('Would copy ' + source + ' to ' + target)
120
- else
121
- FileUtils.cp_r(source, target, noop: dry)
122
- end
123
- end
117
+ local.copyNotPresent(source, target, dry)
124
118
  end
125
119
 
126
- def rm(path, dry)
127
- if dry
128
- prompt.say('Would remove ' + path)
120
+ def rm(path, dry, ssh = nil)
121
+ if ssh
122
+ IO::SSH.new(prompt, logger, ssh).rm(path, dry)
129
123
  else
130
- FileUtils.rm_r(path, noop: dry)
124
+ local.rm(path, dry)
131
125
  end
132
126
  end
133
127
 
134
128
  def mkdir(target, dry)
135
- if dry
136
- prompt.say('Would create ' + target)
137
- else
138
- FileUtils.mkdir_p(target)
139
- end
129
+ local.mkdir(target, dry)
140
130
  end
141
131
 
142
132
  def chown(user, group, target, dry)
143
- if dry
144
- prompt.say("Would chown #{target} as #{user}:#{group}")
145
- else
146
- FileUtils.chown_R(user, group, target)
147
- end
148
- end
149
-
150
- def self.loadVariable(value, target)
151
- variableStart = value.index('${')
152
- return value unless variableStart
153
- variableEnd = value.index('}', variableStart + 2)
154
- variableName = value[variableStart + 2...variableEnd]
155
- if variableName.start_with?('ENV:')
156
- value = value[0...variableStart].to_s + ENV[variableName[4..variableEnd]] + value[(variableEnd + 1)..-1].to_s
157
- else
158
- raise 'Not implemented!'
159
- end
160
- value
133
+ local.chown(user, group, target, dry)
161
134
  end
162
135
 
163
- CONFIGLMM_SECTION_BEGIN = "# -----BEGIN CONFIGLMM-----\n"
164
- CONFIGLMM_SECTION_END = "# -----END CONFIGLMM-----\n"
165
-
166
136
  def updateLocalFile(file, options, atTop = false, comment = '#')
167
- File.write(file, '') unless File.exist?(file)
168
- sectionBegin = CONFIGLMM_SECTION_BEGIN.gsub('#', comment)
169
- sectionEnd = CONFIGLMM_SECTION_END.gsub('#', comment)
170
- fileLines = File.read(file).lines
171
- sectionBeginIndex = fileLines.index(sectionBegin)
172
- sectionEndIndex = fileLines.index(sectionEnd)
173
- if sectionBeginIndex.nil?
174
- linesBefore = []
175
- linesBefore = fileLines unless atTop
176
- linesBefore << "\n"
177
- linesBefore << sectionBegin
178
- linesAfter = [sectionEnd]
179
- linesAfter << "\n"
180
- linesAfter += fileLines if atTop
181
- else
182
- linesBefore = fileLines[0..sectionBeginIndex]
183
- if sectionEndIndex.nil?
184
- linesAfter = [sectionEnd]
185
- linesAfter << "\n"
186
- else
187
- linesAfter = fileLines[sectionEndIndex..fileLines.length]
188
- end
189
- end
190
-
191
- fileLines = linesBefore
192
- fileLines = yield(fileLines)
193
- fileLines += linesAfter
194
-
195
- fileWrite(file, fileLines.join(), options[:dry])
137
+ local.updateFile(file, options, atTop, comment)
196
138
  end
197
139
 
140
+ # DEPRECATED
198
141
  def updateRemoteFile(locationOrSSH, file, options, atTop = false, comment = '#', &block)
199
-
200
142
  closure = Proc.new do |ssh|
201
- localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
202
- File.write(localFile, '')
203
- self.class.sshExec!(ssh, "touch #{file}")
204
- ssh.scp.download!(file, localFile)
205
- updateLocalFile(localFile, options, atTop, comment, &block)
206
- ssh.scp.upload!(localFile, file)
143
+ IO::SSH.new(prompt, logger, ssh).updateFile(file, options, atTop, comment, &block)
207
144
  end
208
145
 
209
146
  if locationOrSSH.is_a?(String) || locationOrSSH.is_a?(Addressable::URI)
@@ -218,6 +155,7 @@ module ConfigLMM
218
155
  end
219
156
  end
220
157
 
158
+ # DEPRECATED
221
159
  def self.filePresent?(file, ssh = nil)
222
160
  result = self.exec("stat #{file}", ssh, true)
223
161
  !result.start_with?('stat: cannot')
@@ -228,69 +166,38 @@ module ConfigLMM
228
166
  self.filePresent?(file, ssh)
229
167
  end
230
168
 
231
- def self.remoteFileContains?(file, content, ssh)
232
- !self.sshExec!(ssh, "grep '#{content}' #{file}", true).strip.empty?
233
- end
234
-
235
- def self.uploadNotPresent(file, target, ssh)
236
- target += '/' + File.basename(file)
237
- if !self.remoteFilePresent?(target, ssh)
238
- ssh.scp.upload!(file, target)
239
- end
240
- end
241
-
242
169
  def self.uploadFolder(folder, target, ssh)
243
170
  target += '/' + File.basename(folder) + '/'
244
- self.sshExec!(ssh, "mkdir -p #{target}")
171
+ IO::SSH.exec!(ssh, "mkdir -p #{target}")
245
172
  Dir[folder + '/*'].each do |file|
246
173
  ssh.scp.upload!(file, target + File.basename(file), recursive: true)
247
174
  end
248
175
  end
249
176
 
250
- def self.exec(command, ssh = nil, allowFailure = false)
251
- if ssh.nil?
252
- stdout, stdeerr, status = Open3.capture3(command)
253
- if !allowFailure && !status.success?
254
- $stderr.puts(stdout)
255
- $stderr.puts(stdeerr)
256
- raise Framework::PluginProcessError.new("Failed '#{command}'")
257
- end
258
- stdout + stdeerr
259
- else
260
- self.sshExec!(ssh, command, allowFailure)
261
- end
177
+ def withConnection(uri, target, &block)
178
+ IO::Connection.tunnel(uri, target, self.context, self.prompt, self.logger, &block)
262
179
  end
263
180
 
264
- def self.toSSHparams(locationUri)
265
- server = locationUri.hostname
266
- params = {}
267
- params[:port] = locationUri.port if locationUri.port
268
- params[:user] = locationUri.user if locationUri.user
269
- [server, params]
181
+ def ping(uri, target, &block)
182
+ IO::Connection.ping(uri, target, self.context, self.prompt, self.logger, &block)
270
183
  end
271
184
 
272
- def self.sshStart(uri)
273
- uri = Addressable::URI.parse(uri) if uri.is_a?(String)
274
- server, sshParams = self.toSSHparams(uri)
275
- Net::SSH.start(server, nil, sshParams) do |ssh|
276
- yield(ssh)
277
- end
185
+ # DEPRECATED
186
+ def self.exec(command, ssh = nil, allowFailure = false, dry = false)
187
+ IO::Connection.exec(command, ssh, allowFailure, { dry: dry })
278
188
  end
279
189
 
280
- def self.sshExec!(ssh, command, allowFailure = false)
281
- status = {}
282
- output = ''
283
- channel = ssh.exec(command, status: status) do |channel, stream, data|
284
- output += data
285
- end
286
- channel.wait
287
- if !allowFailure && !status[:exit_code].zero?
288
- $stderr.puts(output)
289
- raise Framework::PluginProcessError.new("Failed '#{command}'")
290
- end
291
- output
190
+ # DEPRECATED
191
+ def self.sshStart(uri, &block)
192
+ IO::SSH.tunnel(uri, &block)
193
+ end
194
+
195
+ # DEPRECATED
196
+ def self.sshExec!(ssh, command, allowFailure = false, dry = false)
197
+ IO::SSH.exec!(ssh, command, allowFailure, { dry: dry })
292
198
  end
293
199
 
200
+ # DEPRECATED
294
201
  def renderTemplate(template, target, outputPath, options)
295
202
  variables = {
296
203
  config: target,
@@ -304,6 +211,27 @@ module ConfigLMM
304
211
  end
305
212
  end
306
213
 
214
+ def cleanupType(type, configs, state, context, options)
215
+ items = state.selectType(type)
216
+ items.each do |id, item|
217
+ if !configs.key?(id) && item['Status'] != State::STATUS_DESTROYED && (item['Status'] != State::STATUS_DELETED || options[:destroy])
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
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+
307
235
  def self.normalizeId(id)
308
236
  id = id.split('::').last
309
237
  if id.downcase.end_with?('plugin')
@@ -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
@@ -7,3 +7,4 @@ end
7
7
 
8
8
  require_relative 'Framework/registrator'
9
9
  require_relative 'Framework/plugins'
10
+ require_relative 'Framework/variables'
data/lib/ConfigLMM/cli.rb CHANGED
@@ -14,7 +14,7 @@ module ConfigLMM
14
14
  MissingArgument = Class.new(ArgumentError)
15
15
  InvalidOption = Class.new(ArgumentError)
16
16
 
17
- #class_option :locations, type: :string, default: '', group: :configs, desc: 'Filter by config file locations'
17
+ class_option :locations, type: :string, default: '', group: :configs, desc: 'Filter by config file locations'
18
18
  #class_option :things, type: :string, default: '', group: :configs, desc: 'Filter things to use'
19
19
  class_option :level, type: :string, enum: ['debug', 'info', 'warn', 'error'], default: 'info', desc: 'Logging level to use'
20
20
  class_option :dry, aliases: '-n', type: :boolean, desc: 'Only show actions without performing'
@@ -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,10 +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, aliases: '-h', type: :boolean,
99
- desc: 'Display usage information'
100
- method_option :state, aliases: '-s', type: :string,
101
- desc: 'Path to the state file'
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)'
102
110
  def cleanup(*configPaths)
103
111
  handleCommand(:cleanup, configPaths, options)
104
112
  end
@@ -12,6 +12,7 @@ module ConfigLMM
12
12
  loadConfigs(plugin, config, configs)
13
13
  plugin.cleanup(configs, state, context, options)
14
14
  end
15
+ state.save
15
16
  prompt.ok('Cleanup successful!')
16
17
  end
17
18
 
@@ -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[:context])
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
@@ -43,7 +43,7 @@ module ConfigLMM
43
43
  options = @Options.dup
44
44
  options.delete(:locations)
45
45
  options.delete(:things)
46
- #options[:locationFilter] = Utils::Filters.parseLocationsOption(@Options[:locations], logger)
46
+ options[:locationFilter] = Utils::Filters.parseLocationsOption(@Options[:locations], logger)
47
47
  #options[:thingFilter] = Utils::Filters.parseThingsOption(@Options[:things], logger)
48
48
 
49
49
  configList = IO::ConfigList.create(@ConfigPaths, logger)
@@ -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,6 +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
131
+
132
+ singleTarget['Location'] = '@me' unless singleTarget['Location']
133
+ singleTarget['SecretId'] = (singleTarget['SecretId'] || id).upcase
134
+
127
135
  actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Deploy')
128
136
  if plugin.methods.include?(:authenticate)
129
137
  result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
@@ -146,7 +154,7 @@ module ConfigLMM
146
154
  end
147
155
 
148
156
  if !options['dry']
149
- # Prevent others accessing it
157
+ # Prevent others accessing it
150
158
  FileUtils.chmod(0750, options['output'])
151
159
  end
152
160
 
@@ -156,9 +164,34 @@ module ConfigLMM
156
164
 
157
165
  plugin.send(actionMethod, id, singleTarget, activeState, context, options)
158
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'])
159
172
  state.save
160
173
  end
161
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
+
162
195
 
163
196
  def checkDiff(id, targetName, stateName, target, activeState)
164
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
- if diff.first.is_a?(Hash)
29
- prompt.say(' ' + name + ':')
30
- diff.first.each do |name, value|
31
- prompt.say('- ' + name + ': ' + value, :color => :red)
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
- diff.last.each do |name, value|
34
- prompt.say('+ ' + name + ': ' + value, :color => :green)
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