dust-deploy 0.16.4 → 0.16.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +3 -3
- data/README.md +1 -1
- data/changelog.md +14 -0
- data/dust.gemspec +8 -7
- data/lib/dust/examples/nodes/postgresql.yaml +4 -3
- data/lib/dust/examples/templates/duplicity/cronjob.erb +2 -2
- data/lib/dust/helper.rb +0 -7
- data/lib/dust/recipes/apt.rb +4 -4
- data/lib/dust/recipes/debsecan.rb +26 -30
- data/lib/dust/recipes/dnsmasq.rb +1 -1
- data/lib/dust/recipes/dovecot.rb +1 -1
- data/lib/dust/recipes/duplicity.rb +1 -1
- data/lib/dust/recipes/iptables.rb +1 -1
- data/lib/dust/recipes/limits.rb +1 -1
- data/lib/dust/recipes/nginx.rb +2 -2
- data/lib/dust/recipes/packages.rb +1 -1
- data/lib/dust/recipes/postfix.rb +2 -2
- data/lib/dust/recipes/postgres.rb +6 -3
- data/lib/dust/recipes/rc_local.rb +1 -1
- data/lib/dust/recipes/repositories.rb +9 -4
- data/lib/dust/recipes/resolv_conf.rb +1 -1
- data/lib/dust/recipes/skel.rb +2 -2
- data/lib/dust/recipes/ssh_config.rb +2 -2
- data/lib/dust/recipes/sshd.rb +3 -4
- data/lib/dust/recipes/sysctl.rb +1 -1
- data/lib/dust/recipes/users.rb +1 -1
- data/lib/dust/recipes/zabbix_agent.rb +1 -1
- data/lib/dust/runner.rb +3 -3
- data/lib/dust/server.rb +3 -873
- data/lib/dust/server/facter.rb +33 -0
- data/lib/dust/server/file.rb +152 -0
- data/lib/dust/server/osdetect.rb +112 -0
- data/lib/dust/server/package.rb +215 -0
- data/lib/dust/server/selinux.rb +40 -0
- data/lib/dust/server/service.rb +82 -0
- data/lib/dust/server/ssh.rb +182 -0
- data/lib/dust/server/user.rb +117 -0
- data/lib/dust/version.rb +1 -1
- metadata +32 -39
data/lib/dust/recipes/sshd.rb
CHANGED
@@ -23,7 +23,6 @@ class Sshd < Recipe
|
|
23
23
|
def default_config
|
24
24
|
{ 'Port' => 22,
|
25
25
|
'Protocol' => 2,
|
26
|
-
'AcceptEnv' => 'LANG LC_*',
|
27
26
|
'HostKey' => [ '/etc/ssh/ssh_host_dsa_key',
|
28
27
|
'/etc/ssh/ssh_host_ecdsa_key',
|
29
28
|
'/etc/ssh/ssh_host_rsa_key' ],
|
@@ -38,7 +37,7 @@ class Sshd < Recipe
|
|
38
37
|
|
39
38
|
def generate_default_config
|
40
39
|
@config.boolean_to_string!
|
41
|
-
@config = default_config.merge
|
40
|
+
@config = default_config.merge(@config)
|
42
41
|
|
43
42
|
unless @config['sftp']
|
44
43
|
@config['Subsystem'] ||= 'sftp /usr/lib/openssh/sftp-server' if @node.uses_apt?
|
@@ -66,11 +65,11 @@ class Sshd < Recipe
|
|
66
65
|
if value.is_a? Hash
|
67
66
|
value.each do |k, v|
|
68
67
|
conditional_blocks << "#{key} #{k}\n"
|
69
|
-
v.
|
68
|
+
Array(v).each { |x, y| conditional_blocks << " #{x} #{y}\n" }
|
70
69
|
end
|
71
70
|
|
72
71
|
else
|
73
|
-
value.
|
72
|
+
Array(value).each { |value| @sshd_config << "#{key} #{value}\n" }
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
data/lib/dust/recipes/sysctl.rb
CHANGED
@@ -12,7 +12,7 @@ class Sysctl < Recipe
|
|
12
12
|
|
13
13
|
# apply template sysctls
|
14
14
|
if templates
|
15
|
-
templates.
|
15
|
+
Array(templates).each do |template|
|
16
16
|
@node.messages.add("configuring sysctls for template #{template}\n")
|
17
17
|
apply template, self.send(template)
|
18
18
|
end
|
data/lib/dust/recipes/users.rb
CHANGED
@@ -67,7 +67,7 @@ class Users < Recipe
|
|
67
67
|
authorized_keys = ''
|
68
68
|
|
69
69
|
# create the authorized_keys hash for this user
|
70
|
-
ssh_users.
|
70
|
+
Array(ssh_users).each do |ssh_user|
|
71
71
|
unless users[ssh_user]
|
72
72
|
return @node.messages.add("#{ssh_user} cannot be found in #{@template_path}/public_keys.yaml").failed
|
73
73
|
end
|
@@ -134,7 +134,7 @@ class ZabbixAgent < Recipe
|
|
134
134
|
|
135
135
|
# check for security patches and system updates on emerge systems
|
136
136
|
def enable_apt
|
137
|
-
updates = [ 'apt.updates,
|
137
|
+
updates = [ 'apt.updates,apt-cache search \'~U\' |wc -l' ]
|
138
138
|
if @node.is_debian?
|
139
139
|
@node.collect_facts
|
140
140
|
updates << "debian.security,debsecan --suite #{@node['lsbdistcodename']} --only-fixed --format packages |wc -l"
|
data/lib/dust/runner.rb
CHANGED
@@ -104,7 +104,7 @@ module Dust
|
|
104
104
|
end
|
105
105
|
|
106
106
|
|
107
|
-
desc 'system_update', 'perform a full system upgrade (using
|
107
|
+
desc 'system_update', 'perform a full system upgrade (using apt-get, emerge, yum)'
|
108
108
|
default_options
|
109
109
|
|
110
110
|
def system_update
|
@@ -321,7 +321,7 @@ module Dust
|
|
321
321
|
exit
|
322
322
|
end
|
323
323
|
|
324
|
-
yaml_files.
|
324
|
+
Array(yaml_files).each do |file|
|
325
325
|
node = YAML.load ERB.new( File.read(file), nil, '%<>').result
|
326
326
|
|
327
327
|
# if the file is empty, just skip it
|
@@ -344,7 +344,7 @@ module Dust
|
|
344
344
|
|
345
345
|
# if more than one hostname is specified, create a node
|
346
346
|
# with the same settings for each hostname
|
347
|
-
node['hostname'].
|
347
|
+
Array(node['hostname']).each do |hostname|
|
348
348
|
n = node.clone
|
349
349
|
|
350
350
|
# overwrite hostname with single hostname (in case there are multiple)
|
data/lib/dust/server.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require 'net/ssh/proxy/socks5'
|
4
|
-
require 'erb'
|
5
|
-
require 'tempfile'
|
6
|
-
require 'colorize'
|
1
|
+
# include all extensions
|
2
|
+
Dir["#{File.dirname(__FILE__)}/server/*.rb"].each { |file| require file }
|
7
3
|
|
8
4
|
module Dust
|
9
5
|
class Server
|
10
|
-
attr_reader :
|
6
|
+
attr_reader :messages
|
11
7
|
|
12
8
|
def default_options options = {}
|
13
9
|
{ :quiet => false, :indent => 1 }.merge options
|
@@ -23,871 +19,6 @@ module Dust
|
|
23
19
|
@messages = Messages.new
|
24
20
|
end
|
25
21
|
|
26
|
-
def connect
|
27
|
-
messages.print_hostname_header(@node['hostname']) unless $parallel
|
28
|
-
|
29
|
-
begin
|
30
|
-
# connect to proxy if given
|
31
|
-
if @node['proxy']
|
32
|
-
host, port = @node['proxy'].split ':'
|
33
|
-
proxy = Net::SSH::Proxy::SOCKS5.new(host, port)
|
34
|
-
else
|
35
|
-
proxy = nil
|
36
|
-
end
|
37
|
-
|
38
|
-
@ssh = Net::SSH.start @node['fqdn'], @node['user'],
|
39
|
-
{ :password => @node['password'],
|
40
|
-
:port => @node['port'],
|
41
|
-
:proxy => proxy }
|
42
|
-
rescue Exception
|
43
|
-
error_message = "coudln't connect to #{@node['fqdn']}"
|
44
|
-
error_message << " (via socks5 proxy #{@node['proxy']})" if proxy
|
45
|
-
messages.add(error_message, :indent => 0).failed
|
46
|
-
return false
|
47
|
-
end
|
48
|
-
|
49
|
-
true
|
50
|
-
end
|
51
|
-
|
52
|
-
def disconnect
|
53
|
-
@ssh.close
|
54
|
-
end
|
55
|
-
|
56
|
-
def exec command, options={:live => false, :as_user => false}
|
57
|
-
sudo_authenticated = false
|
58
|
-
stdout = ''
|
59
|
-
stderr = ''
|
60
|
-
exit_code = nil
|
61
|
-
exit_signal = nil
|
62
|
-
|
63
|
-
# prepend a newline, if output is live
|
64
|
-
messages.add("\n", :indent => 0) if options[:live]
|
65
|
-
|
66
|
-
@ssh.open_channel do |channel|
|
67
|
-
|
68
|
-
# if :as_user => user is given, execute as user (be aware of ' usage)
|
69
|
-
command = "su #{options[:as_user]} -l -c '#{command}'" if options[:as_user]
|
70
|
-
|
71
|
-
# request a terminal (sudo needs it)
|
72
|
-
# and prepend "sudo"
|
73
|
-
# command is wrapped in ", escapes " in the command string
|
74
|
-
# and then executed using "sh -c", so that
|
75
|
-
# the use of > < && || | and ; doesn't screw things up
|
76
|
-
if @node['sudo']
|
77
|
-
channel.request_pty
|
78
|
-
command = "sudo -k -- sh -c \"#{command.gsub('"','\\"')}\""
|
79
|
-
end
|
80
|
-
|
81
|
-
channel.exec command do |ch, success|
|
82
|
-
abort "FAILED: couldn't execute command (ssh.channel.exec)" unless success
|
83
|
-
|
84
|
-
channel.on_data do |ch, data|
|
85
|
-
# only send password if sudo mode is enabled,
|
86
|
-
# and only send password once in a session (trying to prevent attacks reading out the password)
|
87
|
-
if data =~ /\[sudo\] password for #{@node['user']}/
|
88
|
-
|
89
|
-
raise 'password requested, but none given in config!' if @node['password'].empty?
|
90
|
-
raise 'already sent password, but sudo requested the password again. (wrong password?)' if sudo_authenticated
|
91
|
-
|
92
|
-
# we're not authenticated yet, send password
|
93
|
-
channel.send_data "#{@node['password']}\n"
|
94
|
-
sudo_authenticated = true
|
95
|
-
|
96
|
-
else
|
97
|
-
# skip everything util authenticated (if sudo is used and password given in config)
|
98
|
-
next if @node['sudo'] and not @node['password'].empty? and not sudo_authenticated
|
99
|
-
|
100
|
-
stdout += data
|
101
|
-
messages.add(data.green, :indent => 0) if options[:live] and not data.empty?
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
channel.on_extended_data do |ch, type, data|
|
106
|
-
stderr += data
|
107
|
-
messages.add(data.red, :indent => 0) if options[:live] and not data.empty?
|
108
|
-
end
|
109
|
-
|
110
|
-
channel.on_request('exit-status') { |ch, data| exit_code = data.read_long }
|
111
|
-
channel.on_request('exit-signal') { |ch, data| exit_signal = data.read_long }
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
@ssh.loop
|
116
|
-
|
117
|
-
# sudo usage provokes a heading newline that's unwanted.
|
118
|
-
stdout.sub! /^(\r\n|\n|\r)/, '' if @node['sudo']
|
119
|
-
|
120
|
-
{ :stdout => stdout, :stderr => stderr, :exit_code => exit_code, :exit_signal => exit_signal }
|
121
|
-
end
|
122
|
-
|
123
|
-
def write destination, content, options = {}
|
124
|
-
options = default_options.merge options
|
125
|
-
|
126
|
-
msg = messages.add("deploying #{File.basename destination}", options)
|
127
|
-
|
128
|
-
f = Tempfile.new 'dust-write'
|
129
|
-
f.print content
|
130
|
-
f.close
|
131
|
-
|
132
|
-
ret = msg.parse_result(scp(f.path, destination, :quiet => true))
|
133
|
-
f.unlink
|
134
|
-
|
135
|
-
ret
|
136
|
-
end
|
137
|
-
|
138
|
-
def append destination, newcontent, options = {}
|
139
|
-
options = default_options.merge options
|
140
|
-
|
141
|
-
msg = messages.add("appending to #{File.basename destination}", options)
|
142
|
-
|
143
|
-
content = exec("cat #{destination}")[:stdout]
|
144
|
-
content.concat newcontent
|
145
|
-
|
146
|
-
msg.parse_result(write(destination, content, :quiet => true))
|
147
|
-
end
|
148
|
-
|
149
|
-
def scp(source, destination, options = {})
|
150
|
-
options = default_options.merge options
|
151
|
-
|
152
|
-
# make sure scp is installed on client
|
153
|
-
install_package('openssh-clients', :quiet => true) if uses_rpm?
|
154
|
-
|
155
|
-
msg = messages.add("deploying #{File.basename source}", options)
|
156
|
-
|
157
|
-
# check if destination is a directory
|
158
|
-
is_dir = dir_exists?(destination, :quiet => true)
|
159
|
-
|
160
|
-
# save permissions if the file already exists
|
161
|
-
ret = exec("stat -c %a:%u:%g #{destination}")
|
162
|
-
if ret[:exit_code] == 0 and not is_dir
|
163
|
-
permissions, user, group = ret[:stdout].chomp.split(':')
|
164
|
-
else
|
165
|
-
# files = 644, dirs = 755
|
166
|
-
permissions = 'ug-x,o-wx,u=rwX,g=rX,o=rX'
|
167
|
-
user = 'root'
|
168
|
-
group = 'root'
|
169
|
-
end
|
170
|
-
|
171
|
-
# if in sudo mode, copy file to temporary place, then move using sudo
|
172
|
-
if @node['sudo']
|
173
|
-
tmpdir = mktemp(:type => 'directory')
|
174
|
-
return msg.failed('could not create temporary directory (needed for sudo)') unless tmpdir
|
175
|
-
|
176
|
-
# temporary destination in tmpdir
|
177
|
-
tmpdest = "#{tmpdir}/#{File.basename(destination)}"
|
178
|
-
|
179
|
-
# allow user to write file without sudo (for scp)
|
180
|
-
# then change file back to root, and copy to the destination
|
181
|
-
chown(@node['user'], tmpdir, :quiet => true)
|
182
|
-
@ssh.scp.upload!(source, tmpdest, :recursive => true)
|
183
|
-
|
184
|
-
# set file permissions
|
185
|
-
chown("#{user}:#{group}", tmpdest, :quiet => true) if user and group
|
186
|
-
chmod(permissions, tmpdest, :quiet => true)
|
187
|
-
|
188
|
-
# if destination is a directory, append real filename
|
189
|
-
destination = "#{destination}/#{File.basename(source)}" if is_dir
|
190
|
-
|
191
|
-
# move the file from the temporary location to where it actually belongs
|
192
|
-
msg.parse_result(exec("mv -f #{tmpdest} #{destination}")[:exit_code])
|
193
|
-
|
194
|
-
# remove temporary directory
|
195
|
-
rm(tmpdir, :quiet => true)
|
196
|
-
|
197
|
-
else
|
198
|
-
@ssh.scp.upload!(source, destination, :recursive => true)
|
199
|
-
msg.ok
|
200
|
-
|
201
|
-
# set file permissions
|
202
|
-
chown("#{user}:#{group}", destination, :quiet => true) if user and group
|
203
|
-
chmod(permissions, destination, :quiet => true)
|
204
|
-
end
|
205
|
-
|
206
|
-
restorecon(destination, options) # restore SELinux labels
|
207
|
-
end
|
208
|
-
|
209
|
-
# download a file (sudo not yet supported)
|
210
|
-
def download source, destination, options = {}
|
211
|
-
options = default_options.merge options
|
212
|
-
|
213
|
-
# make sure scp is installed on client
|
214
|
-
install_package 'openssh-clients', :quiet => true if uses_rpm?
|
215
|
-
|
216
|
-
msg = messages.add("downloading #{File.basename source}", options)
|
217
|
-
msg.parse_result(@ssh.scp.download!(source, destination))
|
218
|
-
end
|
219
|
-
|
220
|
-
def symlink source, destination, options = {}
|
221
|
-
options = default_options.merge options
|
222
|
-
|
223
|
-
msg = messages.add("symlinking #{File.basename source} to '#{destination}'", options)
|
224
|
-
ret = msg.parse_result(exec("ln -s #{source} #{destination}")[:exit_code])
|
225
|
-
restorecon destination, options # restore SELinux labels
|
226
|
-
ret
|
227
|
-
end
|
228
|
-
|
229
|
-
def chmod mode, file, options = {}
|
230
|
-
options = default_options.merge options
|
231
|
-
|
232
|
-
msg = messages.add("setting mode of #{File.basename file} to #{mode}", options)
|
233
|
-
msg.parse_result(exec("chmod -R #{mode} #{file}")[:exit_code])
|
234
|
-
end
|
235
|
-
|
236
|
-
def chown user, file, options = {}
|
237
|
-
options = default_options.merge options
|
238
|
-
|
239
|
-
msg = messages.add("setting owner of #{File.basename file} to #{user}", options)
|
240
|
-
msg.parse_result(exec("chown -R #{user} #{file}")[:exit_code])
|
241
|
-
end
|
242
|
-
|
243
|
-
def chcon(permissions, file, options = {})
|
244
|
-
options = default_options.merge(options)
|
245
|
-
|
246
|
-
# just return if selinux is not enabled
|
247
|
-
return true unless selinuxenabled?
|
248
|
-
|
249
|
-
args = ""
|
250
|
-
args << " --type #{permissions['type']}" if permissions['type']
|
251
|
-
args << " --recursive #{permissions['recursive']}" if permissions['recursive']
|
252
|
-
args << " --user #{permissions['user']}" if permissions['user']
|
253
|
-
args << " --range #{permissions['range']}" if permissions['range']
|
254
|
-
args << " --role #{permissions['role']}" if permissions['role']
|
255
|
-
|
256
|
-
msg = messages.add("setting selinux permissions of #{File.basename file}", options)
|
257
|
-
msg.parse_result(exec("chcon #{args} #{file}")[:exit_code])
|
258
|
-
end
|
259
|
-
|
260
|
-
def selinuxenabled?
|
261
|
-
return true if exec('selinuxenabled')[:exit_code] == 0
|
262
|
-
false
|
263
|
-
end
|
264
|
-
|
265
|
-
def rm file, options = {}
|
266
|
-
options = default_options.merge options
|
267
|
-
|
268
|
-
msg = messages.add("deleting #{file}", options)
|
269
|
-
msg.parse_result(exec("rm -rf #{file}")[:exit_code])
|
270
|
-
end
|
271
|
-
|
272
|
-
def cp source, destination, options = {}
|
273
|
-
options = default_options.merge options
|
274
|
-
|
275
|
-
# get rid of overly careful aliases
|
276
|
-
exec 'unalias -a'
|
277
|
-
|
278
|
-
msg = messages.add("copying #{source} to #{destination}", options)
|
279
|
-
msg.parse_result(exec("cp -a #{source} #{destination}")[:exit_code])
|
280
|
-
end
|
281
|
-
|
282
|
-
def mv source, destination, options = {}
|
283
|
-
options = default_options.merge options
|
284
|
-
|
285
|
-
# get rid of overly careful aliases
|
286
|
-
exec 'unalias -a'
|
287
|
-
|
288
|
-
msg = messages.add("moving #{source} to #{destination}", options)
|
289
|
-
msg.parse_result(exec("mv #{source} #{destination}")[:exit_code])
|
290
|
-
end
|
291
|
-
|
292
|
-
def mkdir dir, options = {}
|
293
|
-
options = default_options.merge options
|
294
|
-
|
295
|
-
return true if dir_exists? dir, :quiet => true
|
296
|
-
|
297
|
-
msg = messages.add("creating directory #{dir}", options)
|
298
|
-
ret = msg.parse_result(exec("mkdir -p #{dir}")[:exit_code])
|
299
|
-
restorecon dir, options # restore SELinux labels
|
300
|
-
ret
|
301
|
-
end
|
302
|
-
|
303
|
-
# check if restorecon (selinux) is available
|
304
|
-
# if so, run it on "path" recursively
|
305
|
-
def restorecon path, options = {}
|
306
|
-
options = default_options.merge options
|
307
|
-
|
308
|
-
# if selinux is not enabled, just return
|
309
|
-
return true unless selinuxenabled?
|
310
|
-
|
311
|
-
msg = messages.add("restoring selinux labels for #{path}", options)
|
312
|
-
msg.parse_result(exec("restorecon -R #{path}")[:exit_code])
|
313
|
-
end
|
314
|
-
|
315
|
-
def get_system_users options = {}
|
316
|
-
options = default_options.merge options
|
317
|
-
|
318
|
-
msg = messages.add("getting all system users", options)
|
319
|
-
ret = exec 'getent passwd |cut -d: -f1'
|
320
|
-
msg.parse_result(ret[:exit_code])
|
321
|
-
|
322
|
-
users = []
|
323
|
-
ret[:stdout].each do |user|
|
324
|
-
users.push user.chomp
|
325
|
-
end
|
326
|
-
users
|
327
|
-
end
|
328
|
-
|
329
|
-
# checks if one of the packages is installed
|
330
|
-
def package_installed? packages, options = {}
|
331
|
-
options = default_options.merge options
|
332
|
-
|
333
|
-
packages = [ packages ] if packages.is_a? String
|
334
|
-
|
335
|
-
msg = messages.add("checking if #{packages.join(' or ')} is installed", options)
|
336
|
-
|
337
|
-
packages.each do |package|
|
338
|
-
if uses_apt?
|
339
|
-
return msg.ok if exec("dpkg -l #{package} |grep '^ii'")[:exit_code] == 0
|
340
|
-
elsif uses_emerge?
|
341
|
-
return msg.ok unless exec("qlist -I #{package}")[:stdout].empty?
|
342
|
-
elsif uses_rpm?
|
343
|
-
return msg.ok if exec("rpm -q #{package}")[:exit_code] == 0
|
344
|
-
elsif uses_pacman?
|
345
|
-
return msg.ok if exec("pacman -Q #{package}")[:exit_code] == 0
|
346
|
-
elsif uses_opkg?
|
347
|
-
return msg.ok unless exec("opkg status #{package}")[:stdout].empty?
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
msg.failed
|
352
|
-
end
|
353
|
-
|
354
|
-
def install_package(package, options = {})
|
355
|
-
options = default_options.merge(options)
|
356
|
-
options[:env] ||= ''
|
357
|
-
|
358
|
-
if package_installed?(package, :quiet => true)
|
359
|
-
return messages.add("package #{package} already installed", options).ok
|
360
|
-
end
|
361
|
-
|
362
|
-
# if package is an url, download and install the package file
|
363
|
-
if package =~ /^(http:\/\/|https:\/\/|ftp:\/\/)/
|
364
|
-
if uses_apt?
|
365
|
-
messages.add("installing #{package}\n", options)
|
366
|
-
return false unless install_package('wget')
|
367
|
-
|
368
|
-
msg = messages.add('downloading package', options.merge(:indent => options[:indent] + 1))
|
369
|
-
|
370
|
-
# creating temporary file
|
371
|
-
tmpfile = mktemp
|
372
|
-
return msg.failed('could not create temporary file') unless tmpfile
|
373
|
-
|
374
|
-
msg.parse_result(exec("wget #{package} -O #{tmpfile}")[:exit_code])
|
375
|
-
|
376
|
-
msg = messages.add('installing package', options.merge(:indent => options[:indent] + 1))
|
377
|
-
ret = msg.parse_result(exec("dpkg -i #{tmpfile}")[:exit_code])
|
378
|
-
|
379
|
-
msg = messages.add('deleting downloaded file', options.merge(:indent => options[:indent] + 1))
|
380
|
-
msg.parse_result(rm(tmpfile, :quiet => true))
|
381
|
-
|
382
|
-
return ret
|
383
|
-
|
384
|
-
elsif uses_rpm?
|
385
|
-
msg = messages.add("installing #{package}", options)
|
386
|
-
return msg.parse_result(exec("rpm -U #{package}")[:exit_code])
|
387
|
-
|
388
|
-
else
|
389
|
-
return msg.failed("\ninstalling packages from url not yet supported " +
|
390
|
-
"for your distribution. feel free to contribute!").failed
|
391
|
-
end
|
392
|
-
|
393
|
-
# package is not an url, use package manager
|
394
|
-
else
|
395
|
-
msg = messages.add("installing #{package}", options)
|
396
|
-
|
397
|
-
if uses_apt?
|
398
|
-
exec "DEBIAN_FRONTEND=noninteractive aptitude install -y #{package}"
|
399
|
-
elsif uses_emerge?
|
400
|
-
exec "#{options[:env]} emerge #{package}"
|
401
|
-
elsif uses_rpm?
|
402
|
-
exec "yum install -y #{package}"
|
403
|
-
elsif uses_pacman?
|
404
|
-
exec "echo y |pacman -S #{package}"
|
405
|
-
elsif uses_opkg?
|
406
|
-
exec "opkg install #{package}"
|
407
|
-
else
|
408
|
-
return msg.failed("\ninstall_package only supports apt, emerge and yum systems at the moment")
|
409
|
-
end
|
410
|
-
|
411
|
-
# check if package actually was installed
|
412
|
-
return msg.parse_result(package_installed?(package, :quiet => true))
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
# check if installed package is at least version min_version
|
417
|
-
def package_min_version?(package, min_version, options = {})
|
418
|
-
msg = messages.add("checking if #{package} is at least version #{min_version}", options)
|
419
|
-
return msg.failed unless package_installed?(package, :quiet => true)
|
420
|
-
|
421
|
-
if uses_apt?
|
422
|
-
v = exec("dpkg --list |grep #{package}")[:stdout].chomp
|
423
|
-
elsif uses_rpm?
|
424
|
-
v = exec("rpm -q #{package}")[:stdout].chomp
|
425
|
-
elsif uses_pacman?
|
426
|
-
v = exec("pacman -Q #{package}")[:stdout].chomp
|
427
|
-
else
|
428
|
-
return msg.failed('os not supported')
|
429
|
-
end
|
430
|
-
|
431
|
-
# convert version numbers to arrays
|
432
|
-
current_version = v.to_s.split(/[-. ]/ ).select {|j| j =~ /^[0-9]+$/ }
|
433
|
-
min_version = min_version.to_s.split(/[-. ]/ ).select {|j| j =~ /^[0-9]+$/ }
|
434
|
-
|
435
|
-
# compare
|
436
|
-
min_version.each_with_index do |i, pos|
|
437
|
-
break unless current_version[pos]
|
438
|
-
return msg.failed if i.to_i < current_version[pos].to_i
|
439
|
-
end
|
440
|
-
|
441
|
-
msg.ok
|
442
|
-
end
|
443
|
-
|
444
|
-
def remove_package package, options = {}
|
445
|
-
options = default_options.merge options
|
446
|
-
|
447
|
-
unless package_installed? package, :quiet => true
|
448
|
-
return messages.add("package #{package} not installed", options).ok
|
449
|
-
end
|
450
|
-
|
451
|
-
msg = messages.add("removing #{package}", options)
|
452
|
-
if uses_apt?
|
453
|
-
msg.parse_result(exec("DEBIAN_FRONTEND=noninteractive aptitude purge -y #{package}")[:exit_code])
|
454
|
-
elsif uses_emerge?
|
455
|
-
msg.parse_result(exec("emerge --unmerge #{package}")[:exit_code])
|
456
|
-
elsif uses_rpm?
|
457
|
-
msg.parse_result(exec("yum erase -y #{package}")[:exit_code])
|
458
|
-
elsif uses_pacman?
|
459
|
-
msg.parse_result(exec("echo y |pacman -R #{package}")[:exit_code])
|
460
|
-
elsif uses_opkg?
|
461
|
-
msg.parse_result(exec("opkg remove #{package}")[:exit_code])
|
462
|
-
else
|
463
|
-
msg.failed
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
|
-
def update_repos options = {}
|
468
|
-
options = default_options.merge options
|
469
|
-
|
470
|
-
msg = messages.add('updating system repositories', options)
|
471
|
-
|
472
|
-
if uses_apt?
|
473
|
-
ret = exec 'aptitude update', options
|
474
|
-
elsif uses_emerge?
|
475
|
-
ret = exec 'emerge --sync', options
|
476
|
-
elsif uses_rpm?
|
477
|
-
ret = exec 'yum check-update', options
|
478
|
-
|
479
|
-
# yum returns != 0 if packages that need to be updated are found
|
480
|
-
# we don't want that this is producing an error
|
481
|
-
ret[:exit_code] = 0 if ret[:exit_code] == 100
|
482
|
-
elsif uses_pacman?
|
483
|
-
ret = exec 'pacman -Sy', options
|
484
|
-
elsif uses_opkg?
|
485
|
-
ret = exec 'opkg update', options
|
486
|
-
else
|
487
|
-
return msg.failed
|
488
|
-
end
|
489
|
-
|
490
|
-
unless options[:live]
|
491
|
-
msg.parse_result(ret[:exit_code])
|
492
|
-
end
|
493
|
-
|
494
|
-
ret[:exit_code]
|
495
|
-
end
|
496
|
-
|
497
|
-
def system_update options = {}
|
498
|
-
options = default_options.merge(:live => true).merge(options)
|
499
|
-
|
500
|
-
update_repos
|
501
|
-
|
502
|
-
msg = messages.add('installing system updates', options)
|
503
|
-
|
504
|
-
if uses_apt?
|
505
|
-
ret = exec 'DEBIAN_FRONTEND=noninteractive aptitude full-upgrade -y', options
|
506
|
-
elsif uses_emerge?
|
507
|
-
ret = exec 'emerge -uND @world', options
|
508
|
-
elsif uses_rpm?
|
509
|
-
ret = exec 'yum upgrade -y', options
|
510
|
-
elsif uses_pacman?
|
511
|
-
# pacman has no --yes option that i know of, so echoing y
|
512
|
-
ret = exec 'echo y |pacman -Su', options
|
513
|
-
elsif uses_opkg?
|
514
|
-
# upgrading openwrt is very experimental, and should not used normally
|
515
|
-
ret = exec 'opkg upgrade $(echo $(opkg list-upgradable |cut -d' ' -f1 |grep -v Multiple))', options
|
516
|
-
else
|
517
|
-
msg.failed('system not (yet) supported')
|
518
|
-
return false
|
519
|
-
end
|
520
|
-
|
521
|
-
unless options[:live]
|
522
|
-
msg.parse_result(ret[:exit_code])
|
523
|
-
end
|
524
|
-
|
525
|
-
ret[:exit_code]
|
526
|
-
end
|
527
|
-
|
528
|
-
# determining the system packet manager has to be done without facter
|
529
|
-
# because it's used to find out whether facter is installed / install facter
|
530
|
-
def uses_apt? options = {}
|
531
|
-
options = default_options(:quiet => true).merge options
|
532
|
-
|
533
|
-
return @uses_apt if defined? @uses_apt
|
534
|
-
msg = messages.add('determining whether node uses apt', options)
|
535
|
-
@uses_apt = msg.parse_result(exec('test -e /etc/debian_version')[:exit_code])
|
536
|
-
end
|
537
|
-
|
538
|
-
def uses_rpm? options = {}
|
539
|
-
options = default_options(:quiet => true).merge options
|
540
|
-
|
541
|
-
return @uses_rpm if defined? @uses_rpm
|
542
|
-
msg = messages.add('determining whether node uses rpm', options)
|
543
|
-
@uses_rpm = msg.parse_result(exec('test -e /etc/redhat-release')[:exit_code])
|
544
|
-
end
|
545
|
-
|
546
|
-
def uses_emerge? options = {}
|
547
|
-
options = default_options(:quiet => true).merge options
|
548
|
-
|
549
|
-
return @uses_emerge if defined? @uses_emerge
|
550
|
-
msg = messages.add('determining whether node uses emerge', options)
|
551
|
-
@uses_emerge = msg.parse_result(exec('test -e /etc/gentoo-release')[:exit_code])
|
552
|
-
end
|
553
|
-
|
554
|
-
def uses_pacman? options = {}
|
555
|
-
options = default_options(:quiet => true).merge options
|
556
|
-
|
557
|
-
return @uses_pacman if defined? @uses_pacman
|
558
|
-
msg = messages.add('determining whether node uses pacman', options)
|
559
|
-
@uses_pacman = msg.parse_result(exec('test -e /etc/arch-release')[:exit_code])
|
560
|
-
end
|
561
|
-
|
562
|
-
def uses_opkg? options = {}
|
563
|
-
options = default_options(:quiet => true).merge options
|
564
|
-
|
565
|
-
return @uses_opkg if defined? @uses_opkg
|
566
|
-
msg = messages.add('determining whether node uses opkg', options)
|
567
|
-
@uses_opkg = msg.parse_result(exec('test -e /etc/opkg.conf')[:exit_code])
|
568
|
-
end
|
569
|
-
|
570
|
-
def is_os? os_list, options = {}
|
571
|
-
options = default_options(:quiet => true).merge options
|
572
|
-
|
573
|
-
msg = messages.add("checking if this machine runs #{os_list.join(' or ')}", options)
|
574
|
-
return msg.failed unless collect_facts options
|
575
|
-
|
576
|
-
os_list.each do |os|
|
577
|
-
if @node['operatingsystem'].downcase == os.downcase
|
578
|
-
return msg.ok
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
msg.failed
|
583
|
-
false
|
584
|
-
end
|
585
|
-
|
586
|
-
def is_debian? options = {}
|
587
|
-
options = default_options(:quiet => true).merge options
|
588
|
-
|
589
|
-
return false unless uses_apt?
|
590
|
-
is_os? ['debian'], options
|
591
|
-
end
|
592
|
-
|
593
|
-
def is_ubuntu? options = {}
|
594
|
-
options = default_options(:quiet => true).merge options
|
595
|
-
|
596
|
-
return false unless uses_apt?
|
597
|
-
is_os? ['ubuntu'], options
|
598
|
-
end
|
599
|
-
|
600
|
-
def is_gentoo? options = {}
|
601
|
-
options = default_options(:quiet => true).merge options
|
602
|
-
|
603
|
-
return false unless uses_emerge?
|
604
|
-
is_os? ['gentoo'], options
|
605
|
-
end
|
606
|
-
|
607
|
-
def is_centos? options = {}
|
608
|
-
options = default_options(:quiet => true).merge options
|
609
|
-
|
610
|
-
return false unless uses_rpm?
|
611
|
-
is_os? ['centos'], options
|
612
|
-
end
|
613
|
-
|
614
|
-
def is_scientific? options = {}
|
615
|
-
options = default_options(:quiet => true).merge options
|
616
|
-
|
617
|
-
return false unless uses_rpm?
|
618
|
-
is_os? ['scientific'], options
|
619
|
-
end
|
620
|
-
|
621
|
-
def is_fedora? options = {}
|
622
|
-
options = default_options(:quiet => true).merge options
|
623
|
-
|
624
|
-
return false unless uses_rpm?
|
625
|
-
is_os? ['fedora'], options
|
626
|
-
end
|
627
|
-
|
628
|
-
def is_arch? options = {}
|
629
|
-
options = default_options(:quiet => true).merge options
|
630
|
-
|
631
|
-
return false unless uses_pacman?
|
632
|
-
is_os? ['archlinux'], options
|
633
|
-
end
|
634
|
-
|
635
|
-
def is_executable? file, options = {}
|
636
|
-
options = default_options.merge options
|
637
|
-
|
638
|
-
msg = messages.add("checking if file #{file} exists and is executeable", options)
|
639
|
-
msg.parse_result(exec("test -x $(which #{file})")[:exit_code])
|
640
|
-
end
|
641
|
-
|
642
|
-
def file_exists? file, options = {}
|
643
|
-
options = default_options.merge options
|
644
|
-
|
645
|
-
msg = messages.add("checking if file #{file} exists", options)
|
646
|
-
|
647
|
-
# don't treat directories as files
|
648
|
-
return msg.failed if dir_exists?(file, :quiet => true)
|
649
|
-
|
650
|
-
msg.parse_result(exec("test -e #{file}")[:exit_code])
|
651
|
-
end
|
652
|
-
|
653
|
-
def dir_exists? dir, options = {}
|
654
|
-
options = default_options.merge options
|
655
|
-
|
656
|
-
msg = messages.add("checking if directory #{dir} exists", options)
|
657
|
-
msg.parse_result(exec("test -d #{dir}")[:exit_code])
|
658
|
-
end
|
659
|
-
|
660
|
-
def autostart_service service, options = {}
|
661
|
-
options = default_options.merge options
|
662
|
-
|
663
|
-
msg = messages.add("autostart #{service} on boot", options)
|
664
|
-
|
665
|
-
if uses_rpm?
|
666
|
-
if file_exists? '/bin/systemctl', :quiet => true
|
667
|
-
msg.parse_result(exec("systemctl enable #{service}.service")[:exit_code])
|
668
|
-
else
|
669
|
-
msg.parse_result(exec("chkconfig #{service} on")[:exit_code])
|
670
|
-
end
|
671
|
-
|
672
|
-
elsif uses_apt?
|
673
|
-
msg.parse_result(exec("update-rc.d #{service} defaults")[:exit_code])
|
674
|
-
|
675
|
-
elsif uses_emerge?
|
676
|
-
msg.parse_result(exec("rc-update add #{service} default")[:exit_code])
|
677
|
-
|
678
|
-
# archlinux needs his autostart daemons in /etc/rc.conf, in the DAEMONS line
|
679
|
-
#elsif uses_pacman?
|
680
|
-
|
681
|
-
else
|
682
|
-
msg.failed
|
683
|
-
end
|
684
|
-
end
|
685
|
-
|
686
|
-
# invoke 'command' on the service (e.g. @node.service 'postgresql', 'restart')
|
687
|
-
def service service, command, options = {}
|
688
|
-
options = default_options.merge options
|
689
|
-
|
690
|
-
return messages.add("service: '#{service}' unknown", options).failed unless service.is_a? String
|
691
|
-
|
692
|
-
# try systemd, then upstart, then sysvconfig, then rc.d, then initscript
|
693
|
-
if file_exists? '/bin/systemctl', :quiet => true
|
694
|
-
msg = messages.add("#{command}ing #{service} (via systemd)", options)
|
695
|
-
ret = exec("systemctl #{command} #{service}.service")
|
696
|
-
|
697
|
-
elsif file_exists? "/etc/init/#{service}", :quiet => true
|
698
|
-
msg = messages.add("#{command}ing #{service} (via upstart)", options)
|
699
|
-
ret = exec("#{command} #{service}")
|
700
|
-
|
701
|
-
elsif file_exists? '/sbin/service', :quiet => true or file_exists? '/usr/sbin/service', :quiet => true
|
702
|
-
msg = messages.add("#{command}ing #{service} (via sysvconfig)", options)
|
703
|
-
ret = exec("service #{service} #{command}")
|
704
|
-
|
705
|
-
elsif file_exists? '/usr/sbin/rc.d', :quiet => true
|
706
|
-
msg = messages.add("#{command}ing #{service} (via rc.d)", options)
|
707
|
-
ret = exec("rc.d #{command} #{service}")
|
708
|
-
|
709
|
-
else
|
710
|
-
msg = messages.add("#{command}ing #{service} (via initscript)", options)
|
711
|
-
ret = exec("/etc/init.d/#{service} #{command}")
|
712
|
-
end
|
713
|
-
|
714
|
-
msg.parse_result(ret[:exit_code])
|
715
|
-
ret
|
716
|
-
end
|
717
|
-
|
718
|
-
def restart_service service, options = {}
|
719
|
-
options = default_options.merge options
|
720
|
-
|
721
|
-
service service, 'restart', options
|
722
|
-
end
|
723
|
-
|
724
|
-
def reload_service service, options = {}
|
725
|
-
options = default_options.merge options
|
726
|
-
|
727
|
-
service service, 'reload', options
|
728
|
-
end
|
729
|
-
|
730
|
-
def print_service_status(service, options = {})
|
731
|
-
options = default_options.merge(:indent => 0).merge(options)
|
732
|
-
ret = service(service, 'status', options)
|
733
|
-
messages.add('', options).print_output(ret)
|
734
|
-
ret
|
735
|
-
end
|
736
|
-
|
737
|
-
# check whether a user exists on this node
|
738
|
-
def user_exists? user, options = {}
|
739
|
-
options = default_options.merge options
|
740
|
-
|
741
|
-
msg = messages.add("checking if user #{user} exists", options)
|
742
|
-
msg.parse_result(exec("id #{user}")[:exit_code])
|
743
|
-
end
|
744
|
-
|
745
|
-
# manages users (create, modify)
|
746
|
-
def manage_user(user, options = {})
|
747
|
-
options = default_options.merge(options)
|
748
|
-
options = { 'home' => nil, 'shell' => nil, 'uid' => nil, 'remove' => false,
|
749
|
-
'gid' => nil, 'groups' => nil, 'system' => false }.merge(options)
|
750
|
-
|
751
|
-
# delete user from system
|
752
|
-
if options['remove']
|
753
|
-
if user_exists?(user, :quiet => true)
|
754
|
-
msg = messages.add("deleting user #{user} from system", { :indent => options[:indent] }.merge(options))
|
755
|
-
return msg.parse_result(exec("userdel --remove #{user}")[:exit_code])
|
756
|
-
end
|
757
|
-
|
758
|
-
return messages.add("user #{user} not present in system", options).ok
|
759
|
-
end
|
760
|
-
|
761
|
-
if user_exists?(user, :quiet => true)
|
762
|
-
args = ""
|
763
|
-
args << " --move-home --home #{options['home']}" if options['home']
|
764
|
-
args << " --shell #{options['shell']}" if options['shell']
|
765
|
-
args << " --uid #{options['uid']}" if options['uid']
|
766
|
-
args << " --gid #{options['gid']}" if options['gid']
|
767
|
-
args << " --append --groups #{Array(options['groups']).join(',')}" if options['groups']
|
768
|
-
|
769
|
-
if args.empty?
|
770
|
-
ret = messages.add("user #{user} already set up correctly", options).ok
|
771
|
-
else
|
772
|
-
msg = messages.add("modifying user #{user}", { :indent => options[:indent] }.merge(options))
|
773
|
-
ret = msg.parse_result(exec("usermod #{args} #{user}")[:exit_code])
|
774
|
-
end
|
775
|
-
|
776
|
-
else
|
777
|
-
args = ""
|
778
|
-
args = "--create-home" unless options['system']
|
779
|
-
args << " --system" if options['system']
|
780
|
-
args << " --home #{options['home']}" if options['home'] and not options['system']
|
781
|
-
args << " --shell #{options['shell']}" if options['shell']
|
782
|
-
args << " --uid #{options['uid']}" if options['uid']
|
783
|
-
args << " --gid #{options['gid']}" if options['gid']
|
784
|
-
args << " --groups #{Array(options['groups']).join(',')}" if options['groups']
|
785
|
-
|
786
|
-
msg = messages.add("creating user #{user}", { :indent => options[:indent] }.merge(options))
|
787
|
-
ret = msg.parse_result(exec("useradd #{user} #{args}")[:exit_code])
|
788
|
-
end
|
789
|
-
|
790
|
-
# set selinux permissions
|
791
|
-
chcon({ 'type' => 'user_home_dir_t' }, get_home(user), options)
|
792
|
-
return ret
|
793
|
-
end
|
794
|
-
|
795
|
-
# returns the home directory of this user
|
796
|
-
def get_home(user, options = {})
|
797
|
-
options = default_options(:quiet => true).merge(options)
|
798
|
-
|
799
|
-
msg = messages.add("getting home directory of #{user}", options)
|
800
|
-
ret = exec("getent passwd |cut -d':' -f1,6 |grep '^#{user}' |head -n1 |cut -d: -f2")
|
801
|
-
if msg.parse_result(ret[:exit_code]) and not ret[:stdout].chomp.empty?
|
802
|
-
return ret[:stdout].chomp
|
803
|
-
else
|
804
|
-
return false
|
805
|
-
end
|
806
|
-
end
|
807
|
-
|
808
|
-
# returns shell of this user
|
809
|
-
def get_shell(user, options = {})
|
810
|
-
options = default_options(:quiet => true).merge(options)
|
811
|
-
|
812
|
-
msg = messages.add("getting shell of #{user}", options)
|
813
|
-
ret = exec("getent passwd |cut -d':' -f1,7 |grep '^#{user}' |head -n1 |cut -d: -f2")
|
814
|
-
if msg.parse_result(ret[:exit_code])
|
815
|
-
return ret[:stdout].chomp
|
816
|
-
else
|
817
|
-
return false
|
818
|
-
end
|
819
|
-
end
|
820
|
-
|
821
|
-
# returns primary group id of this user
|
822
|
-
def get_gid(user, options = {})
|
823
|
-
options = default_options(:quiet => true).merge(options)
|
824
|
-
|
825
|
-
msg = messages.add("getting primary gid of #{user}", options)
|
826
|
-
ret = exec("id -g #{user}")
|
827
|
-
if msg.parse_result(ret[:exit_code])
|
828
|
-
return ret[:stdout].chomp
|
829
|
-
else
|
830
|
-
return false
|
831
|
-
end
|
832
|
-
end
|
833
|
-
|
834
|
-
# collect additional system facts using puppets facter
|
835
|
-
def collect_facts options = {}
|
836
|
-
options = default_options.merge options
|
837
|
-
|
838
|
-
# if facts already have been collected, just return
|
839
|
-
return true if @node['operatingsystem']
|
840
|
-
|
841
|
-
# check if lsb-release (on apt systems) and facter are installed
|
842
|
-
# and install them if not
|
843
|
-
if uses_apt? and not package_installed? 'lsb-release', :quiet => true
|
844
|
-
install_package 'lsb-release', :quiet => false
|
845
|
-
end
|
846
|
-
|
847
|
-
unless package_installed? 'facter', :quiet => true
|
848
|
-
return false unless install_package 'facter', :quiet => false
|
849
|
-
end
|
850
|
-
|
851
|
-
msg = messages.add("collecting additional system facts (using facter)", options)
|
852
|
-
|
853
|
-
# run facter with -y for yaml output, and merge results into @node
|
854
|
-
ret = exec 'facter -y'
|
855
|
-
@node = YAML.load(ret[:stdout]).merge(@node)
|
856
|
-
|
857
|
-
msg.parse_result(ret[:exit_code])
|
858
|
-
end
|
859
|
-
|
860
|
-
# if file is a regular file, copy it using scp
|
861
|
-
# if it's an file.erb exists, render template and push to server
|
862
|
-
def deploy_file file, destination, options = {}
|
863
|
-
options = default_options(:binding => binding).merge options
|
864
|
-
|
865
|
-
if File.exists? file
|
866
|
-
scp file, destination, options
|
867
|
-
|
868
|
-
elsif File.exists? "#{file}.erb"
|
869
|
-
template = ERB.new( File.read("#{file}.erb"), nil, '%<>')
|
870
|
-
write destination, template.result(options[:binding]), options
|
871
|
-
|
872
|
-
else
|
873
|
-
messages.add("'#{file}' was not found.", options).failed
|
874
|
-
end
|
875
|
-
end
|
876
|
-
|
877
|
-
# create a temporary file
|
878
|
-
def mktemp(options = { :type => 'file' })
|
879
|
-
if options[:type] == 'file'
|
880
|
-
ret = exec('mktemp --tmpdir dust.XXXXXXXXXX')
|
881
|
-
elsif options[:type] == 'directory'
|
882
|
-
ret = exec('mktemp -d --tmpdir dust.XXXXXXXXXX')
|
883
|
-
else
|
884
|
-
return messages.add("mktemp: unknown type '#{options[:type]}'").failed
|
885
|
-
end
|
886
|
-
|
887
|
-
return false if ret[:exit_code] != 0
|
888
|
-
ret[:stdout].chomp
|
889
|
-
end
|
890
|
-
|
891
22
|
|
892
23
|
private
|
893
24
|
|
@@ -905,6 +36,5 @@ module Dust
|
|
905
36
|
super
|
906
37
|
end
|
907
38
|
end
|
908
|
-
|
909
39
|
end
|
910
40
|
end
|