ConfigLMM 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Yo Dawg I Heard you like config so I put a config in your Config](/Images/configINconfig.png)
|
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
|
+
![One Config to Rule Them All](/Images/singleConfig.png)
|
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
|