dust-deploy 0.16.4 → 0.16.5
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 +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
|