ConfigLMM 0.1.0 → 0.2.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 +32 -1
- data/Examples/Implemented.mm.yaml +46 -0
- data/Examples/Keys.ini +2 -0
- data/Examples/Linux.mm.yaml +14 -3
- data/Images/configINconfig.png +0 -0
- data/Images/singleConfig.png +0 -0
- data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +17 -0
- data/Plugins/Apps/Nginx/config-lmm/errors.conf +1 -1
- data/Plugins/Apps/Nginx/config-lmm/security.conf +4 -0
- data/Plugins/Apps/Postfix/Postfix.lmm.rb +76 -0
- data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +76 -0
- data/Plugins/Apps/Valkey/Valkey.lmm.rb +55 -0
- data/Plugins/OS/Linux/Distributions.yaml +6 -0
- data/Plugins/OS/Linux/Linux.lmm.rb +256 -0
- data/Plugins/OS/Linux/Packages.yaml +13 -0
- data/Plugins/OS/Linux/openSUSE/autoinst.xml.erb +87 -0
- data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +2 -1
- data/Plugins/Platforms/libvirt/libvirt.lmm.rb +103 -0
- data/README.md +4 -0
- data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +115 -0
- data/lib/ConfigLMM/Framework/plugins/plugin.rb +110 -0
- data/lib/ConfigLMM/Framework/plugins.rb +1 -0
- data/lib/ConfigLMM/commands/configsCommand.rb +3 -0
- data/lib/ConfigLMM/version.rb +1 -1
- metadata +58 -4
- data/Plugins/OS/Linux.lmm.rb +0 -64
@@ -0,0 +1,256 @@
|
|
1
|
+
|
2
|
+
require 'addressable/uri'
|
3
|
+
require 'http'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
module ConfigLMM
|
8
|
+
module LMM
|
9
|
+
class Linux < Framework::LinuxApp
|
10
|
+
|
11
|
+
ISO_LOCATION = '~/.cache/configlmm/images/'
|
12
|
+
HOSTS_FILE = '/etc/hosts'
|
13
|
+
SSH_CONFIG = '~/.ssh/config'
|
14
|
+
SYSCTL_FILE = '/etc/sysctl.d/10-configlmm.conf'
|
15
|
+
|
16
|
+
def actionLinuxBuild(id, target, activeState, context, options)
|
17
|
+
prepareConfig(target)
|
18
|
+
buildHostsFile(id, target, options)
|
19
|
+
buildSSHConfig(id, target, options)
|
20
|
+
buildAutoYaST(id, target, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def actionLinuxDeploy(id, target, activeState, context, options)
|
24
|
+
prepareConfig(target)
|
25
|
+
if target['Location'] && target['Location'] != '@me'
|
26
|
+
uri = Addressable::URI.parse(target['Location'])
|
27
|
+
case uri.scheme
|
28
|
+
when 'qemu'
|
29
|
+
deployOverLibvirt(id, target, activeState, context, options)
|
30
|
+
when 'ssh'
|
31
|
+
deployOverSSH(uri, id, target, activeState, context, options)
|
32
|
+
else
|
33
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown protocol: #{uri.scheme}!")
|
34
|
+
end
|
35
|
+
else
|
36
|
+
deployLocalHostsFile(target, options)
|
37
|
+
deployLocalSSHConfig(target, options)
|
38
|
+
end
|
39
|
+
if target['AlternativeLocation']
|
40
|
+
uri = Addressable::URI.parse(target['AlternativeLocation'])
|
41
|
+
raise Framework::PluginProcessError.new("#{id}: Unsupported protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
|
42
|
+
deployOverSSH(uri, id, target, activeState, context, options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def deployOverSSH(locationUri, id, target, activeState, context, options)
|
47
|
+
if target['Domain'] || target['Hosts']
|
48
|
+
hostsLines = []
|
49
|
+
if target['Domain']
|
50
|
+
self.class.sshStart(locationUri) do |ssh|
|
51
|
+
envs = self.class.sshExec!(ssh, "env").split("\n")
|
52
|
+
envVars = Hash[envs.map { |vars| vars.split('=', 2) }]
|
53
|
+
ipAddr = envVars['SSH_CONNECTION'].split[-2]
|
54
|
+
hostsLines << ipAddr.ljust(16) + target['Domain'] + ' ' + target['Name'] + "\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
target['Hosts'].to_a.each do |ip, entries|
|
58
|
+
hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
|
59
|
+
end
|
60
|
+
updateRemoteFile(locationUri, HOSTS_FILE, options, false) do |fileLines|
|
61
|
+
fileLines + hostsLines
|
62
|
+
end
|
63
|
+
end
|
64
|
+
if target['Sysctl']
|
65
|
+
updateRemoteFile(locationUri, SYSCTL_FILE, options, false) do |fileLines|
|
66
|
+
target['Sysctl'].each do |name, value|
|
67
|
+
fileLines << "#{name} = #{value}\n"
|
68
|
+
end
|
69
|
+
fileLines
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def deployOverLibvirt(id, target, activeState, context, options)
|
75
|
+
location = Libvirt.getLocation(target['Location'])
|
76
|
+
iso = installationISO(target['Distro'], location)
|
77
|
+
iso = buildISOAutoYaST(id, iso, target, options) if target['Distro'] == SUSE_NAME
|
78
|
+
plugins[:Libvirt].createVM(target['Name'], target, target['Location'], iso, activeState)
|
79
|
+
end
|
80
|
+
|
81
|
+
def buildHostsFile(id, target, options)
|
82
|
+
if target['Hosts']
|
83
|
+
hosts = "#\n"
|
84
|
+
hosts += "# /etc/hosts: static lookup table for host names\n"
|
85
|
+
hosts += "#\n\n"
|
86
|
+
hosts += "#<ip-address> <hostname.domain.org> <hostname>\n"
|
87
|
+
hosts += "127.0.0.1 localhost\n"
|
88
|
+
hosts += "::1 localhost\n\n"
|
89
|
+
hosts += CONFIGLMM_SECTION_BEGIN
|
90
|
+
target['Hosts'].each do |ip, entries|
|
91
|
+
hosts += ip.ljust(16) + entries.join(' ') + "\n"
|
92
|
+
end
|
93
|
+
hosts += CONFIGLMM_SECTION_END
|
94
|
+
|
95
|
+
path = options['output'] + '/' + id
|
96
|
+
mkdir(path + '/etc', options[:dry])
|
97
|
+
fileWrite(path + HOSTS_FILE, hosts, options[:dry])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def buildSSHConfig(id, target, options)
|
102
|
+
if !target['SSH']['Config'].empty?
|
103
|
+
sshConfig = "\n"
|
104
|
+
sshConfig += CONFIGLMM_SECTION_BEGIN
|
105
|
+
target['SSH']['Config'].each do |name, info|
|
106
|
+
sshConfig += "Host #{name} #{info['HostName']}\n"
|
107
|
+
sshConfig += " HostName " + info['HostName'] + "\n" if info['HostName']
|
108
|
+
sshConfig += " Port " + info['Port'] + "\n" if info['Port']
|
109
|
+
sshConfig += " User " + info['User'] + "\n" if info['User']
|
110
|
+
sshConfig += " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
111
|
+
sshConfig += "\n"
|
112
|
+
end
|
113
|
+
sshConfig += CONFIGLMM_SECTION_END
|
114
|
+
sshConfig += "\n"
|
115
|
+
|
116
|
+
configPath = options['output'] + '/' + id
|
117
|
+
mkdir(configPath + '/root/.ssh', options[:dry])
|
118
|
+
fileWrite(configPath + SSH_CONFIG.gsub('~', '/root'), sshConfig, options[:dry])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def buildAutoYaST(id, target, options)
|
123
|
+
if target['Distro'] == SUSE_NAME
|
124
|
+
outputFolder = options['output'] + '/' + id + '/'
|
125
|
+
template = ERB.new(File.read(__dir__ + '/openSUSE/autoinst.xml.erb'))
|
126
|
+
renderTemplate(template, target, outputFolder + 'autoinst.xml', options)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def deployLocalHostsFile(target, options)
|
131
|
+
if target['Hosts']
|
132
|
+
updateLocalFile(HOSTS_FILE, options) do |hostsLines|
|
133
|
+
target['Hosts'].each do |ip, entries|
|
134
|
+
hostsLines << ip.ljust(16) + entries.join(' ') + "\n"
|
135
|
+
end
|
136
|
+
hostsLines
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def deployLocalSSHConfig(target, options)
|
142
|
+
if !target['SSH']['Config'].empty?
|
143
|
+
updateLocalFile(File.expand_path(SSH_CONFIG), options) do |configLines|
|
144
|
+
target['SSH']['Config'].each do |name, info|
|
145
|
+
configLines << "Host #{name} #{info['HostName']}\n"
|
146
|
+
configLines << " HostName " + info['HostName'] + "\n" if info['HostName']
|
147
|
+
configLines << " Port " + info['Port'] + "\n" if info['Port']
|
148
|
+
configLines << " User " + info['User'] + "\n" if info['User']
|
149
|
+
configLines << " IdentityFile " + info['IdentityFile'] + "\n" if info['IdentityFile']
|
150
|
+
end
|
151
|
+
configLines
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def installationISO(distro, location)
|
157
|
+
url = nil
|
158
|
+
case distro
|
159
|
+
when SUSE_NAME
|
160
|
+
if location.empty?
|
161
|
+
# TODO automatically fetch latest version from website
|
162
|
+
url = 'https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso'
|
163
|
+
else
|
164
|
+
raise Framework::PluginProcessError.new("#{id}: Unimplemented!")
|
165
|
+
end
|
166
|
+
else
|
167
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Linux Distro: #{distro}!")
|
168
|
+
end
|
169
|
+
|
170
|
+
filename = File.basename(Addressable::URI.parse(url).path)
|
171
|
+
iso = File.expand_path(ISO_LOCATION + filename)
|
172
|
+
if !File.exist?(iso)
|
173
|
+
mkdir(File.expand_path(ISO_LOCATION), false)
|
174
|
+
prompt.say('Downloading... ' + url)
|
175
|
+
response = HTTP.follow.get(url)
|
176
|
+
raise "Failed to download file: #{response.status}" unless response.status.success?
|
177
|
+
File.open(iso, 'wb') do |file|
|
178
|
+
response.body.each do |chunk|
|
179
|
+
file.write(chunk)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
iso
|
184
|
+
end
|
185
|
+
|
186
|
+
def buildISOAutoYaST(id, iso, target, options)
|
187
|
+
outputFolder = options['output'] + '/iso/'
|
188
|
+
mkdir(outputFolder, false)
|
189
|
+
`xorriso -osirrox on -indev #{iso} -extract / #{outputFolder} 2>&1 >/dev/null`
|
190
|
+
FileUtils.chmod_R(0750, outputFolder) # Need to make it writeable so it can be deleted
|
191
|
+
copy(options['output'] + '/' + id + '/autoinst.xml', outputFolder, false)
|
192
|
+
|
193
|
+
cfg = outputFolder + "boot/x86_64/loader/isolinux.cfg"
|
194
|
+
`sed -i 's|default harddisk|default linux|' #{cfg}`
|
195
|
+
`sed -i 's|append initrd=initrd splash=silent showopts|append initrd=initrd splash=silent autoyast=device://sr0/autoinst.xml|' #{cfg}`
|
196
|
+
`sed -i 's|prompt 1|prompt 0|' #{cfg}`
|
197
|
+
`sed -i 's|timeout 600|timeout 1|' #{cfg}`
|
198
|
+
|
199
|
+
patchedIso = File.dirname(iso) + '/patched.iso'
|
200
|
+
`xorriso -as mkisofs -no-emul-boot -boot-load-size 4 -boot-info-table -iso-level 4 -b boot/x86_64/loader/isolinux.bin -c boot/x86_64/loader/boot.cat -eltorito-alt-boot -e boot/x86_64/efi -no-emul-boot -o #{patchedIso} #{outputFolder} 2>&1 >/dev/null`
|
201
|
+
patchedIso
|
202
|
+
end
|
203
|
+
|
204
|
+
def prepareConfig(target)
|
205
|
+
target['SSH'] ||= {}
|
206
|
+
target['SSH']['Config'] ||= {}
|
207
|
+
target['Users'] ||= {}
|
208
|
+
target['HostName'] = target['Name'] unless target['HostName']
|
209
|
+
|
210
|
+
if ENV['LINUX_ROOT_PASSWORD_HASH']
|
211
|
+
target['Users']['root'] ||= {}
|
212
|
+
target['Users']['root']['PasswordHash'] = ENV['LINUX_ROOT_PASSWORD_HASH']
|
213
|
+
elsif ENV['LINUX_ROOT_PASSWORD']
|
214
|
+
target['Users']['root'] ||= {}
|
215
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(ENV['LINUX_ROOT_PASSWORD'])
|
216
|
+
elsif target['Users'].key?('root')
|
217
|
+
if !target['Users']['root']['Password'] &&
|
218
|
+
!target['Users']['root']['PasswordHash']
|
219
|
+
rootPassword = SecureRandom.urlsafe_base64(12)
|
220
|
+
prompt.say("Root password: #{rootPassword}", :color => :magenta)
|
221
|
+
target['Users']['root']['PasswordHash'] = self.class.linuxPasswordHash(rootPassword)
|
222
|
+
elsif target['Users']['root']['Password'] == 'no'
|
223
|
+
target['Users']['root'].delete('Password')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
target['Users'].each do |user, info|
|
228
|
+
newKeys = []
|
229
|
+
info['AuthorizedKeys'].to_a.each do |key|
|
230
|
+
if key.start_with?('/') || key.start_with?('~')
|
231
|
+
newKeys << File.read(File.expand_path(key)).strip
|
232
|
+
else
|
233
|
+
newKeys << key
|
234
|
+
end
|
235
|
+
end
|
236
|
+
info['AuthorizedKeys'] = newKeys
|
237
|
+
end
|
238
|
+
|
239
|
+
packages = YAML.load_file(__dir__ + '/Packages.yaml')
|
240
|
+
newApps = []
|
241
|
+
target['Services'] ||= []
|
242
|
+
if target['Apps'].to_a.include?('sshd')
|
243
|
+
target['Services'] << 'sshd'
|
244
|
+
target['Services'].uniq!
|
245
|
+
end
|
246
|
+
target['Apps'] = self.class.mapPackages(target['Apps'], target['Distro'])
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.linuxPasswordHash(password)
|
250
|
+
salt = SecureRandom.alphanumeric(16)
|
251
|
+
password.crypt('$6$' + salt)
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE profile>
|
3
|
+
<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
|
4
|
+
<general config:type="map">
|
5
|
+
<mode config:type="map">
|
6
|
+
<confirm config:type="boolean">false</confirm>
|
7
|
+
</mode>
|
8
|
+
</general>
|
9
|
+
<bootloader t="map">
|
10
|
+
<global t="map">
|
11
|
+
<append>splash=silent preempt=full mitigations=auto quiet security=apparmor console=ttyS0,115200</append>
|
12
|
+
</global>
|
13
|
+
</bootloader>
|
14
|
+
<host t="map">
|
15
|
+
<% if !config['Hosts'].to_a.empty? %>
|
16
|
+
<hosts t="list">
|
17
|
+
<% config['Hosts'].each do |address, names| %>
|
18
|
+
<hosts_entry t="map">
|
19
|
+
<host_address><%= address %></host_address>
|
20
|
+
<names t="list">
|
21
|
+
<% names.each do |name| %>
|
22
|
+
<name><%= name %></name>
|
23
|
+
<% end %>
|
24
|
+
</names>
|
25
|
+
</hosts_entry>
|
26
|
+
<% end %>
|
27
|
+
</hosts>
|
28
|
+
<% end %>
|
29
|
+
</host>
|
30
|
+
<networking t="map">
|
31
|
+
<dns t="map">
|
32
|
+
<hostname><%= config['HostName'] %></hostname>
|
33
|
+
<% if config['Domain'] %>
|
34
|
+
<domain><%= config['Domain'] %></domain>
|
35
|
+
<% end %>
|
36
|
+
</dns>
|
37
|
+
</networking>
|
38
|
+
<software t="map">
|
39
|
+
<% if !config['Apps'].to_a.empty? %>
|
40
|
+
<packages t="list">
|
41
|
+
<% config['Apps'].each do |app| %>
|
42
|
+
<package><%= app.downcase %></package>
|
43
|
+
<% end %>
|
44
|
+
</packages>
|
45
|
+
<% end %>
|
46
|
+
</software>
|
47
|
+
<services-manager t="map">
|
48
|
+
<services t="map">
|
49
|
+
<% if !config['Services'].to_a.empty? %>
|
50
|
+
<enable t="list">
|
51
|
+
<% config['Services'].each do |service| %>
|
52
|
+
<service><%= service %></service>
|
53
|
+
<% end %>
|
54
|
+
</enable>
|
55
|
+
<% end %>
|
56
|
+
</services>
|
57
|
+
</services-manager>
|
58
|
+
<timezone t="map">
|
59
|
+
<timezone>Etc/UTC</timezone>
|
60
|
+
</timezone>
|
61
|
+
<% if !config['Users'].to_h.empty? %>
|
62
|
+
<users config:type="list">
|
63
|
+
<% config['Users'].each do |user, info| %>
|
64
|
+
<user>
|
65
|
+
<username>root</username>
|
66
|
+
<% if info['PasswordHash'] %>
|
67
|
+
<encrypted config:type="boolean">true</encrypted>
|
68
|
+
<user_password><%= info['PasswordHash'] %></user_password>
|
69
|
+
<% elsif info['Password'] %>
|
70
|
+
<encrypted config:type="boolean">false</encrypted>
|
71
|
+
<user_password><%= info['Password'] %></user_password>
|
72
|
+
<% end %>
|
73
|
+
<% if info['Shell'] %>
|
74
|
+
<shell>/usr/bin/<%= info['Shell'] %></shell>
|
75
|
+
<% end %>
|
76
|
+
<% if !info['AuthorizedKeys'].to_a.empty? %>
|
77
|
+
<authorized_keys config:type="list">
|
78
|
+
<% info['AuthorizedKeys'].each do |entry| %>
|
79
|
+
<listentry><%= entry %></listentry>
|
80
|
+
<% end %>
|
81
|
+
</authorized_keys>
|
82
|
+
<% end %>
|
83
|
+
</user>
|
84
|
+
<% end %>
|
85
|
+
</users>
|
86
|
+
<% end %>
|
87
|
+
</profile>
|
@@ -26,7 +26,8 @@ module ConfigLMM
|
|
26
26
|
data.each do |name, data|
|
27
27
|
self.processDNS(domain, data).each do |type, records|
|
28
28
|
records.each do |record|
|
29
|
-
shortName = name
|
29
|
+
shortName = Addressable::IDNA.to_ascii(name)
|
30
|
+
record[:content] = Addressable::IDNA.to_ascii(record[:content]) if record[:type] == 'CNAME'
|
30
31
|
config['Records'] += [shortName, record[:ttl], ' IN ', record[:type], record[:content]].join("\t") + "\n"
|
31
32
|
end
|
32
33
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
require 'fog/libvirt'
|
3
|
+
require 'filesize'
|
4
|
+
|
5
|
+
module ConfigLMM
|
6
|
+
module LMM
|
7
|
+
class Libvirt < Framework::Plugin
|
8
|
+
|
9
|
+
DEFAULT_IMAGE_PATH = '~/.local/share/libvirt/images/'
|
10
|
+
DEFAULT_VRAM = 16 * 1024 # 16 MiB
|
11
|
+
|
12
|
+
def actionLibvirtDeploy(id, target, activeState, context, options)
|
13
|
+
if !target['Location']
|
14
|
+
target['Location'] = 'qemu:///session'
|
15
|
+
end
|
16
|
+
compute = Fog::Compute.new(provider: :libvirt, libvirt_uri: target['Location'])
|
17
|
+
createPools(target, compute, self.class.isLocal?(target['Location']))
|
18
|
+
end
|
19
|
+
|
20
|
+
def createPools(target, compute, isLocal)
|
21
|
+
if target['Pools']
|
22
|
+
allPools = compute.pools.all
|
23
|
+
target['Pools'].each do |name, location|
|
24
|
+
next if allPools.find { |pool| pool.name == name }
|
25
|
+
location = DEFAULT_IMAGE_PATH unless location
|
26
|
+
location = File.expand_path(location) if isLocal
|
27
|
+
xml = dirPoolXML(name, location)
|
28
|
+
pool = compute.pools.create(persistent: true, autostart: true, xml: xml)
|
29
|
+
pool.build
|
30
|
+
pool.start
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def createVM(serverName, serverInfo, targetUri, iso, activeState)
|
36
|
+
compute = Fog::Compute.new(provider: :libvirt, libvirt_uri: targetUri)
|
37
|
+
server = compute.servers.all.find { |server| server.name == serverName }
|
38
|
+
if server
|
39
|
+
server.start
|
40
|
+
return
|
41
|
+
end
|
42
|
+
settings = {
|
43
|
+
name: serverName,
|
44
|
+
cpu: {
|
45
|
+
mode: 'host-passthrough'
|
46
|
+
},
|
47
|
+
video: { type: 'qxl', vram: DEFAULT_VRAM }
|
48
|
+
}
|
49
|
+
if serverInfo['CPU']
|
50
|
+
settings[:cpus] = serverInfo['CPU']
|
51
|
+
end
|
52
|
+
if serverInfo['RAM']
|
53
|
+
settings[:memory_size] = Filesize.from(serverInfo['RAM']).to_f('KiB').to_i
|
54
|
+
end
|
55
|
+
volumeName = serverName + '.img'
|
56
|
+
volume = compute.volumes.all.find { |volume| volume.name == volumeName }
|
57
|
+
if volume
|
58
|
+
settings[:volumes] = [volume]
|
59
|
+
elsif serverInfo['Storage']
|
60
|
+
storage = Filesize.from(serverInfo['Storage']).to_f('GiB').to_i
|
61
|
+
volume = compute.volumes.create(
|
62
|
+
name: volumeName,
|
63
|
+
pool_name: compute.pools.first.name,
|
64
|
+
capacity: storage
|
65
|
+
)
|
66
|
+
settings[:volumes] = [volume]
|
67
|
+
end
|
68
|
+
if serverInfo['NetworkBridge']
|
69
|
+
nic = {
|
70
|
+
bridge: serverInfo['NetworkBridge'],
|
71
|
+
}
|
72
|
+
settings[:nics] = [nic]
|
73
|
+
end
|
74
|
+
server = compute.servers.new(**settings)
|
75
|
+
if iso
|
76
|
+
server.iso_dir = File.dirname(iso)
|
77
|
+
server.iso_file = File.basename(iso)
|
78
|
+
end
|
79
|
+
server.save
|
80
|
+
activeState['Status'] = 'CREATED'
|
81
|
+
state.save
|
82
|
+
server.start
|
83
|
+
end
|
84
|
+
|
85
|
+
def dirPoolXML(name, path)
|
86
|
+
xml = '<pool type="dir">'
|
87
|
+
xml += " <name>#{name.encode(:xml => :text)}</name>"
|
88
|
+
xml += "<target><path>#{path.encode(:xml => :text)}</path></target>"
|
89
|
+
xml += '</pool>'
|
90
|
+
xml
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.isLocal?(location)
|
94
|
+
self.getLocation(location).empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.getLocation(location)
|
98
|
+
uri = Addressable::URI.parse(location)
|
99
|
+
uri.hostname
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# ConfigLMM - Large Configuration Management Manager
|
2
2
|
|
3
|
+

|
4
|
+
|
3
5
|
## Manage The Management with ease!
|
4
6
|
|
5
7
|
You define how you want your applications/systems/containers/services/servers
|
@@ -57,6 +59,8 @@ to work without being vendor locked into any particular implementation or provid
|
|
57
59
|
|
58
60
|
The true [GitOps](https://en.wikipedia.org/wiki/DevOps#GitOps)/DevOps/DevSecOps/[TestOps](https://en.wikipedia.org/wiki/TestOps)/SysOps/[AIOps](https://en.wikipedia.org/wiki/Artificial_Intelligence_for_IT_Operations)[DataOps](https://en.wikipedia.org/wiki/DataOps) which I call AllTheOps :)
|
59
61
|
|
62
|
+

|
63
|
+
|
60
64
|
## Benefits
|
61
65
|
|
62
66
|
* Compare performance and price among different providers
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConfigLMM
|
4
|
+
module Framework
|
5
|
+
|
6
|
+
class LinuxApp < Framework::Plugin
|
7
|
+
|
8
|
+
LINUX_FOLDER = __dir__ + '/../../../../Plugins/OS/Linux/'
|
9
|
+
SUSE_NAME = 'openSUSE Leap'
|
10
|
+
SUSE_ID = 'opensuse-leap'
|
11
|
+
|
12
|
+
def ensurePackage(name, location)
|
13
|
+
self.class.ensurePackage(name, location)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ensurePackage(name, location)
|
17
|
+
if location && location != '@me'
|
18
|
+
uri = Addressable::URI.parse(location)
|
19
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
|
20
|
+
self.ensurePackageOverSSH(name, uri)
|
21
|
+
else
|
22
|
+
# TODO
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.ensurePackageOverSSH(name, locationOrSSH)
|
27
|
+
|
28
|
+
closure = Proc.new do |ssh|
|
29
|
+
distroInfo = self.distroInfoFromSSH(ssh)
|
30
|
+
pkgs = self.mapPackages([name], distroInfo['Name']).first
|
31
|
+
|
32
|
+
pkgs = [pkgs] unless pkgs.is_a?(Array)
|
33
|
+
command = distroInfo['InstallPackage'] + ' ' + pkgs.map { |pkg| pkg.shellescape }.join(' ')
|
34
|
+
self.sshExec!(ssh, command)
|
35
|
+
end
|
36
|
+
|
37
|
+
if locationOrSSH.is_a?(String) || locationOrSSH.is_a?(Addressable::URI)
|
38
|
+
self.sshStart(locationOrSSH) do |ssh|
|
39
|
+
closure.call(ssh)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
closure.call(locationOrSSH)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensureServiceAutoStart(name, location)
|
48
|
+
if location && location != '@me'
|
49
|
+
uri = Addressable::URI.parse(location)
|
50
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
|
51
|
+
self.class.sshStart(uri) do |ssh|
|
52
|
+
distroInfo = self.class.distroInfoFromSSH(ssh)
|
53
|
+
|
54
|
+
command = distroInfo['AutoStartService'] + ' ' + name.shellescape
|
55
|
+
self.class.sshExec!(ssh, command)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# TODO
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def startService(name, location)
|
63
|
+
if location && location != '@me'
|
64
|
+
uri = Addressable::URI.parse(location)
|
65
|
+
raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
|
66
|
+
self.class.sshStart(uri) do |ssh|
|
67
|
+
distroInfo = self.class.distroInfoFromSSH(ssh)
|
68
|
+
|
69
|
+
command = distroInfo['StartService'] + ' ' + name.shellescape
|
70
|
+
self.class.sshExec!(ssh, command)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
# TODO
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.mapPackages(packages, distroName)
|
78
|
+
distroPackages = YAML.load_file(LINUX_FOLDER + 'Packages.yaml')
|
79
|
+
names = []
|
80
|
+
packages.to_a.each do |pkg|
|
81
|
+
packageName = distroPackages[distroName][pkg]
|
82
|
+
if packageName
|
83
|
+
names << packageName
|
84
|
+
else
|
85
|
+
names << pkg.downcase
|
86
|
+
end
|
87
|
+
end
|
88
|
+
names
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.createSubuidsOverSSH(user, distroInfo, ssh)
|
92
|
+
self.sshExec!(ssh, "#{distroInfo['ModifyUser']} --add-subuids 100000-165535 --add-subgids 100000-165535 #{user}")
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.distroID
|
96
|
+
`cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2`.strip.gsub('"', '')
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.distroIDfromSSH(ssh)
|
100
|
+
ssh.exec!('cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2').strip.gsub('"', '')
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.distroInfoFromSSH(ssh)
|
104
|
+
distroInfo = self.distroInfo(self.distroIDfromSSH(ssh))
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.distroInfo(distroID)
|
108
|
+
distributions = YAML.load_file(LINUX_FOLDER + 'Distributions.yaml')
|
109
|
+
raise Framework::PluginProcessError.new("Unknown Linux Distro: #{distroID}!") unless distributions.key?(distroID)
|
110
|
+
distributions[distroID]
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|