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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -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 +7 -1
  13. data/Plugins/Apps/Authentik/Authentik-Server.container +6 -1
  14. data/Plugins/Apps/Authentik/Authentik-Worker.container +6 -1
  15. data/Plugins/Apps/Authentik/Authentik.conf.erb +12 -7
  16. data/Plugins/Apps/Authentik/Authentik.lmm.rb +226 -61
  17. data/Plugins/Apps/BookStack/BookStack.conf.erb +0 -3
  18. data/Plugins/Apps/BookStack/BookStack.container +5 -0
  19. data/Plugins/Apps/BookStack/BookStack.lmm.rb +14 -3
  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 +5 -0
  28. data/Plugins/Apps/Discourse/Discourse.conf.erb +1 -4
  29. data/Plugins/Apps/Discourse/Discourse.container +4 -0
  30. data/Plugins/Apps/Discourse/Discourse.lmm.rb +116 -55
  31. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +74 -62
  32. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +6 -1
  33. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +5 -0
  34. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +5 -0
  35. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +6 -1
  36. data/Plugins/Apps/ERPNext/ERPNext.container +6 -1
  37. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +138 -127
  38. data/Plugins/Apps/GitLab/GitLab.container +6 -0
  39. data/Plugins/Apps/GitLab/GitLab.lmm.rb +43 -49
  40. data/Plugins/Apps/Homepage/Homepage.conf.erb +86 -0
  41. data/Plugins/Apps/Homepage/Homepage.container +19 -0
  42. data/Plugins/Apps/Homepage/Homepage.lmm.rb +54 -0
  43. data/Plugins/Apps/IPFS/IPFS.conf.erb +0 -3
  44. data/Plugins/Apps/IPFS/IPFS.lmm.rb +0 -1
  45. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +0 -3
  46. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +0 -1
  47. data/Plugins/Apps/Jackett/Jackett.conf.erb +0 -3
  48. data/Plugins/Apps/Jackett/Jackett.lmm.rb +0 -1
  49. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +0 -3
  50. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +0 -1
  51. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +49 -28
  52. data/Plugins/Apps/LibreTranslate/LibreTranslate.container +21 -0
  53. data/Plugins/Apps/LibreTranslate/LibreTranslate.lmm.rb +34 -0
  54. data/Plugins/Apps/Lobsters/Containerfile +81 -0
  55. data/Plugins/Apps/Lobsters/Lobsters-Tasks.container +26 -0
  56. data/Plugins/Apps/Lobsters/Lobsters.conf.erb +99 -0
  57. data/Plugins/Apps/Lobsters/Lobsters.container +27 -0
  58. data/Plugins/Apps/Lobsters/Lobsters.lmm.rb +196 -0
  59. data/Plugins/Apps/Lobsters/crontab +3 -0
  60. data/Plugins/Apps/Lobsters/database.yml +26 -0
  61. data/Plugins/Apps/Lobsters/entrypoint.sh +30 -0
  62. data/Plugins/Apps/Lobsters/generateCredentials.rb +19 -0
  63. data/Plugins/Apps/Lobsters/lobsters-cron.sh +25 -0
  64. data/Plugins/Apps/Lobsters/lobsters-daily.sh +23 -0
  65. data/Plugins/Apps/Lobsters/puma.rb +49 -0
  66. data/Plugins/Apps/MariaDB/Connection.rb +55 -0
  67. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +60 -53
  68. data/Plugins/Apps/Mastodon/Mastodon-Sidekiq.container +22 -0
  69. data/Plugins/Apps/Mastodon/Mastodon-Streaming.container +20 -0
  70. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +34 -45
  71. data/Plugins/Apps/Mastodon/Mastodon.container +28 -0
  72. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +240 -5
  73. data/Plugins/Apps/Mastodon/configlmm.rake +30 -0
  74. data/Plugins/Apps/Mastodon/entrypoint.sh +16 -0
  75. data/Plugins/Apps/Matrix/Element.container +5 -0
  76. data/Plugins/Apps/Matrix/Matrix.conf.erb +2 -8
  77. data/Plugins/Apps/Matrix/Matrix.lmm.rb +100 -71
  78. data/Plugins/Apps/Matrix/Synapse.container +5 -0
  79. data/Plugins/Apps/Netdata/Netdata.conf.erb +0 -3
  80. data/Plugins/Apps/Netdata/Netdata.lmm.rb +0 -1
  81. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +3 -4
  82. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +150 -68
  83. data/Plugins/Apps/Nextcloud/autoconfig.php +13 -0
  84. data/Plugins/Apps/Nextcloud/config.php +10 -1
  85. data/Plugins/Apps/Nextcloud/nextcloudcron.service +8 -0
  86. data/Plugins/Apps/Nextcloud/nextcloudcron.timer +10 -0
  87. data/Plugins/Apps/Nginx/Connection.rb +93 -0
  88. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +50 -9
  89. data/Plugins/Apps/Nginx/conf.d/languages.conf +21 -0
  90. data/Plugins/Apps/Nginx/config-lmm/errors.conf +25 -20
  91. data/Plugins/Apps/Nginx/config-lmm/gateway-errors.conf +20 -0
  92. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +1 -1
  93. data/Plugins/Apps/Nginx/main.conf.erb +7 -3
  94. data/Plugins/Apps/Nginx/nginx.conf +2 -2
  95. data/Plugins/Apps/Nginx/nginx.lmm.rb +99 -81
  96. data/Plugins/Apps/Nginx/proxy.conf.erb +11 -3
  97. data/Plugins/Apps/Odoo/Odoo.conf.erb +0 -3
  98. data/Plugins/Apps/Odoo/Odoo.container +5 -0
  99. data/Plugins/Apps/Odoo/Odoo.lmm.rb +4 -5
  100. data/Plugins/Apps/Ollama/Ollama.container +26 -0
  101. data/Plugins/Apps/Ollama/Ollama.lmm.rb +73 -0
  102. data/Plugins/Apps/OpenTelemetry/Config/config.yaml +704 -0
  103. data/Plugins/Apps/OpenTelemetry/OpenTelemetry.lmm.rb +154 -0
  104. data/Plugins/Apps/OpenVidu/Ingress.container +5 -0
  105. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +0 -3
  106. data/Plugins/Apps/OpenVidu/OpenVidu.container +5 -0
  107. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +7 -3
  108. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +0 -3
  109. data/Plugins/Apps/OpenVidu/OpenViduCall.container +5 -0
  110. data/Plugins/Apps/PHP-FPM/Connection.rb +91 -0
  111. data/Plugins/Apps/PHP-FPM/PHP-FPM.lmm.rb +31 -4
  112. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +2 -5
  113. data/Plugins/Apps/Peppermint/Peppermint.container +5 -0
  114. data/Plugins/Apps/Peppermint/Peppermint.lmm.rb +29 -33
  115. data/Plugins/Apps/Perplexica/Perplexica.container +25 -0
  116. data/Plugins/Apps/Perplexica/Perplexica.lmm.rb +92 -0
  117. data/Plugins/Apps/Perplexica/config.toml +26 -0
  118. data/Plugins/Apps/Podman/Connection.rb +24 -0
  119. data/Plugins/Apps/Podman/Podman.lmm.rb +80 -0
  120. data/Plugins/Apps/Podman/storage.conf +6 -0
  121. data/Plugins/Apps/Postfix/Postfix.lmm.rb +242 -164
  122. data/Plugins/Apps/PostgreSQL/Connection.rb +97 -0
  123. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +184 -148
  124. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +0 -3
  125. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +0 -2
  126. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +0 -3
  127. data/Plugins/Apps/RVM/RVM.lmm.rb +57 -0
  128. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +0 -3
  129. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +15 -19
  130. data/Plugins/Apps/SSH/SSH.lmm.rb +9 -15
  131. data/Plugins/Apps/SearXNG/SearXNG.container +22 -0
  132. data/Plugins/Apps/SearXNG/SearXNG.lmm.rb +79 -0
  133. data/Plugins/Apps/SearXNG/limiter.toml +40 -0
  134. data/Plugins/Apps/SearXNG/settings.yml +2 -0
  135. data/Plugins/Apps/SigNoz/Config/alerts.yml +11 -0
  136. data/Plugins/Apps/SigNoz/Config/otel-collector-config.yaml +110 -0
  137. data/Plugins/Apps/SigNoz/Config/otel-collector-opamp-config.yaml +1 -0
  138. data/Plugins/Apps/SigNoz/Config/prometheus.yml +18 -0
  139. data/Plugins/Apps/SigNoz/SigNoz-Collector.container +23 -0
  140. data/Plugins/Apps/SigNoz/SigNoz-Migrator.container +17 -0
  141. data/Plugins/Apps/SigNoz/SigNoz.conf.erb +61 -0
  142. data/Plugins/Apps/SigNoz/SigNoz.container +26 -0
  143. data/Plugins/Apps/SigNoz/SigNoz.lmm.rb +319 -0
  144. data/Plugins/Apps/Solr/log4j2.xml +89 -0
  145. data/Plugins/Apps/Solr/solr.lmm.rb +82 -0
  146. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +0 -3
  147. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +0 -1
  148. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +33 -37
  149. data/Plugins/Apps/UVdesk/UVdesk.conf.erb +0 -3
  150. data/Plugins/Apps/Umami/Umami.container +19 -0
  151. data/Plugins/Apps/Umami/Umami.lmm.rb +108 -0
  152. data/Plugins/Apps/Valkey/Valkey.lmm.rb +54 -42
  153. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +9 -6
  154. data/Plugins/Apps/Vaultwarden/Vaultwarden.container +7 -1
  155. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +64 -29
  156. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +1 -4
  157. data/Plugins/Apps/Wiki.js/Wiki.js.container +5 -0
  158. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +31 -37
  159. data/Plugins/Apps/YaCy/YaCy.conf.erb +93 -0
  160. data/Plugins/Apps/YaCy/YaCy.container +21 -0
  161. data/Plugins/Apps/YaCy/YaCy.lmm.rb +160 -0
  162. data/Plugins/Apps/ZooKeeper/ZooKeeper.container +24 -0
  163. data/Plugins/Apps/ZooKeeper/ZooKeeper.lmm.rb +68 -0
  164. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +0 -3
  165. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +0 -1
  166. data/Plugins/Apps/gollum/gollum.conf.erb +2 -4
  167. data/Plugins/Apps/gollum/gollum.container +6 -0
  168. data/Plugins/Apps/gollum/gollum.lmm.rb +51 -50
  169. data/Plugins/Apps/llama.cpp/llama.cpp.container +28 -0
  170. data/Plugins/Apps/llama.cpp/llama.cpp.lmm.rb +90 -0
  171. data/Plugins/Apps/vLLM/vLLM.container +32 -0
  172. data/Plugins/Apps/vLLM/vLLM.lmm.rb +89 -0
  173. data/Plugins/OS/General/Utils.lmm.rb +26 -0
  174. data/Plugins/OS/Linux/Connection.rb +472 -0
  175. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +25 -6
  176. data/Plugins/OS/Linux/Flavours.yaml +13 -0
  177. data/Plugins/OS/Linux/Grub/grub.cfg +10 -0
  178. data/Plugins/OS/Linux/HTTP.rb +32 -0
  179. data/Plugins/OS/Linux/Linux.lmm.rb +533 -187
  180. data/Plugins/OS/Linux/Packages.yaml +20 -1
  181. data/Plugins/OS/Linux/Services.yaml +8 -0
  182. data/Plugins/OS/Linux/Shell.rb +70 -0
  183. data/Plugins/OS/Linux/Syslinux/default +8 -0
  184. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +83 -59
  185. data/Plugins/OS/Linux/WireGuard/wg0.conf.erb +3 -0
  186. data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +29 -3
  187. data/Plugins/OS/Linux/systemd/systemd.lmm.rb +13 -11
  188. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +6 -5
  189. data/Plugins/Platforms/GitHub.lmm.rb +73 -28
  190. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +9 -6
  191. data/Plugins/Platforms/Proxmox/Proxmox.lmm.rb +402 -0
  192. data/Plugins/Platforms/Proxmox/XTerm.rb +321 -0
  193. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +38 -13
  194. data/Plugins/Platforms/porkbun.lmm.rb +12 -2
  195. data/Plugins/Platforms/porkbun_spec.rb +2 -2
  196. data/Plugins/Services/DNS/AmberBit.lmm.rb +1 -1
  197. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +1 -1
  198. data/Plugins/Services/DNS/NICLV.lmm.rb +1 -1
  199. data/Plugins/Services/DNS/PowerDNS.lmm.rb +70 -68
  200. data/Plugins/Services/DNS/tonic.lmm.rb +22 -12
  201. data/lib/ConfigLMM/Framework/plugins/dns.rb +4 -3
  202. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +145 -184
  203. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -17
  204. data/lib/ConfigLMM/Framework/plugins/plugin.rb +53 -181
  205. data/lib/ConfigLMM/Framework/plugins/store.rb +4 -4
  206. data/lib/ConfigLMM/Framework/variables.rb +75 -0
  207. data/lib/ConfigLMM/Framework.rb +1 -0
  208. data/lib/ConfigLMM/cli.rb +12 -6
  209. data/lib/ConfigLMM/commands/configsCommand.rb +37 -6
  210. data/lib/ConfigLMM/commands/diff.rb +33 -9
  211. data/lib/ConfigLMM/context.rb +22 -3
  212. data/lib/ConfigLMM/io/configList.rb +82 -6
  213. data/lib/ConfigLMM/io/connection.rb +143 -0
  214. data/lib/ConfigLMM/io/dhcp.rb +330 -0
  215. data/lib/ConfigLMM/io/http.rb +78 -0
  216. data/lib/ConfigLMM/io/local.rb +207 -0
  217. data/lib/ConfigLMM/io/pxe.rb +92 -0
  218. data/lib/ConfigLMM/io/ssh.rb +156 -0
  219. data/lib/ConfigLMM/io/tftp.rb +105 -0
  220. data/lib/ConfigLMM/io.rb +2 -0
  221. data/lib/ConfigLMM/secrets/envStore.rb +39 -0
  222. data/lib/ConfigLMM/secrets/fileStore.rb +43 -0
  223. data/lib/ConfigLMM/state.rb +2 -1
  224. data/lib/ConfigLMM/version.rb +2 -1
  225. data/lib/ConfigLMM.rb +1 -0
  226. data/{Examples → scripts}/configlmmAuth.sh +7 -5
  227. 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 shouldMatch(id, targetKey, stateKey, target, activeState)
96
- if target[targetKey] != activeState[stateKey]
97
- @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]]})
98
105
  end
99
106
  end
100
107
 
101
108
  def fileWrite(target, data, dry)
102
- if dry
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
- if dry
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
- if !File.exist?(target + File.basename(source))
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 dry
129
- if ssh
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
- if ssh
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
- if dry
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
- if dry
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
- File.write(file, '') unless File.exist?(file)
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
- localFile = options['output'] + '/' + SecureRandom.alphanumeric(10)
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
- self.sshExec!(ssh, "mkdir -p #{target}")
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 self.exec(command, ssh = nil, allowFailure = false, dry = false)
260
- if ssh.nil?
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 self.toSSHparams(locationUri)
286
- server = locationUri.hostname
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
- def self.cmdSSH(uri)
294
- uri = Addressable::URI.parse(uri) if uri.is_a?(String)
295
- server, sshParams = self.toSSHparams(uri)
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
- def self.sshStart(uri)
303
- uri = Addressable::URI.parse(uri) if uri.is_a?(String)
304
- server, sshParams = self.toSSHparams(uri)
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
- if dry
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
- if item['Location'].nil? || item['Location'] == '@me'
352
- yield(item, id, state, context, options, nil)
353
- else
354
- uri = Addressable::URI.parse(item['Location'])
355
- self.class.sshStart(uri) do |ssh|
356
- yield(item, id, state, context, options, ssh)
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
@@ -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
@@ -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, 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 :destroy, aliases: '-d', type: :boolean,
103
- desc: 'Destroy all data (eg. even DB)'
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[: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
@@ -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
- activeState['Location'] = singleTarget['Location']
128
- activeState['Proxy'] = singleTarget['Proxy']
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
- # Prevent others accessing it
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
- 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