dust-deploy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ require 'yaml'
2
+
3
+ class SshAuthorizedKeys < Thor
4
+ desc 'ssh_authorized_keys:deploy', 'configures ssh authorized_keys'
5
+ def deploy node, ingredients, options
6
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
7
+
8
+ # load users and their ssh keys from yaml file
9
+ users = YAML.load_file "#{template_path}/users.yaml"
10
+
11
+ authorized_keys = {}
12
+ ingredients.each do |remote_user, ssh_users|
13
+ ::Dust.print_msg "generating authorized_keys for #{remote_user}\n"
14
+ authorized_keys = ''
15
+
16
+ # create the authorized_keys hash for this user
17
+ ssh_users.each do |ssh_user|
18
+ ::Dust.print_msg "adding user #{users[ssh_user]['name']}", 2
19
+ users[ssh_user]['keys'].each do |key|
20
+ authorized_keys += "#{key}"
21
+ authorized_keys += " #{users[ssh_user]['name']}" if users[ssh_user]['name']
22
+ authorized_keys += " <#{users[ssh_user]['email']}>" if users[ssh_user]['email']
23
+ authorized_keys += "\n"
24
+ end
25
+
26
+ ::Dust.print_ok
27
+ end
28
+
29
+ # create user, if not existent
30
+ next unless node.create_user remote_user
31
+
32
+ # check and create necessary directories
33
+ next unless node.mkdir("~#{remote_user}/.ssh")
34
+
35
+ # deploy authorized_keys
36
+ next unless node.write "~#{remote_user}/.ssh/authorized_keys", authorized_keys
37
+
38
+ # check permissions
39
+ node.chown "#{remote_user}:#{remote_user}", "~#{remote_user}/.ssh"
40
+ node.chmod '0644', "~#{remote_user}/.ssh/authorized_keys"
41
+
42
+
43
+ # TODO: add this option
44
+ # remove authorized_keys files for all other users
45
+ if options.cleanup?
46
+ ::Dust.print_msg "deleting other authorized_keys files\n"
47
+ node.get_system_users(true).each do |user|
48
+ next if users.keys.include? user
49
+ if node.file_exists? "~#{user}/.ssh/authorized_keys", true
50
+ node.rm "~#{user}/.ssh/authorized_keys", 2
51
+ end
52
+ end
53
+ end
54
+
55
+ puts
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ class UnattendedUpgrades < Thor
2
+ desc 'unattended_upgrades:deploy', 'installs and configures automatic system updates for debian and ubuntu'
3
+ def deploy node, config, options
4
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
5
+
6
+ return unless node.uses_apt?
7
+ node.install_package 'unattended-upgrades'
8
+
9
+ config = {} unless config.class == Hash
10
+
11
+ # set defaults for non-set config
12
+ config['enable'] ||= 1
13
+ config['update-package-lists'] ||= 1
14
+ config['unattended-upgrade'] ||= 1
15
+ config['autocleaninterval'] ||= 1
16
+ config['verbose'] ||= 0
17
+
18
+ # generate configuration file
19
+ periodic = ''
20
+ periodic += "APT::Periodic::Enable \"#{config['enable']}\";\n"
21
+ periodic += "APT::Periodic::Update-Package-Lists \"#{config['update-package-lists']}\";\n"
22
+ periodic += "APT::Periodic::Unattended-Upgrade \"#{config['unattended-upgrade']}\";\n"
23
+ periodic += "APT::Periodic::AutocleanInterval \"#{config['autocleaninterval']}\";\n"
24
+ periodic += "APT::Periodic::Verbose \"#{config['verbose']}\";\n"
25
+
26
+ node.write '/etc/apt/apt.conf.d/02periodic', periodic
27
+ end
28
+ end
29
+
@@ -0,0 +1,84 @@
1
+ require 'erb'
2
+
3
+ class ZabbixAgent < Thor
4
+ desc 'zabbix_agent:deploy', 'installs and configures zabbix agent'
5
+ def deploy node, ingredients, options
6
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
7
+
8
+ return unless install_zabbix node
9
+
10
+ # configure node using erb template
11
+ template = ERB.new File.read("#{template_path}/zabbix_agentd.conf.erb"), nil, '%<>'
12
+ ::Dust.print_msg 'adjusting and deploying zabbix_agentd.conf'
13
+ node.write '/etc/zabbix/zabbix_agentd.conf', template.result(binding), true
14
+ ::Dust.print_ok
15
+
16
+ # restart using new configuration
17
+ if node.uses_emerge? true
18
+ node.autostart_service 'zabbix-agentd'
19
+ node.restart_service 'zabbix-agentd' if options.restart?
20
+ else
21
+ node.autostart_service 'zabbix-agent'
22
+ node.restart_service 'zabbix-agent' if options.restart?
23
+ end
24
+ end
25
+
26
+ private
27
+ # installs zabbix and its dependencies
28
+ def install_zabbix node
29
+
30
+ if node.uses_apt? true
31
+ return false unless node.install_package('zabbix-agent')
32
+
33
+ # debsecan is needed for zabbix checks (security updates)
34
+ return false unless node.install_package('debsecan')
35
+
36
+ elsif node.uses_emerge? true
37
+ return false unless node.install_package('zabbix', false, 1, "USE=agent")
38
+
39
+ # glsa-check (part of gentoolkit) is needed for zabbix checks (security updates)
40
+ return false unless node.install_package('gentoolkit')
41
+
42
+ elsif node.uses_rpm? true
43
+ return false unless node.install_package('zabbix-agent')
44
+
45
+ else
46
+ ::Dust.print_msg 'os not supported'
47
+ ::Dust.print_failed
48
+ return false
49
+ end
50
+
51
+ true
52
+ end
53
+
54
+ # TODO (not yet finished)
55
+ desc 'zabbix_agent:postgres', 'configure postgres database for zabbix monitoring'
56
+ def postgres node, ingredients, options
57
+ next unless node.uses_emerge?
58
+ next unless node.package_installed?('postgresql-node')
59
+
60
+ ::Dust.print_msg 'add zabbix system user to postgres group'
61
+ ::Dust.print_result( node.exec('usermod -a -G postgres zabbix')[:exit_code] )
62
+
63
+ ::Dust.print_msg 'checking if zabbix user exists in postgres'
64
+ ret = ::Dust.print_result( node.exec('psql -U postgres -c ' +
65
+ ' "SELECT usename FROM pg_user WHERE usename = \'zabbix\'"' +
66
+ ' postgres |grep -q zabbix')[:exit_code] )
67
+
68
+ # if user was not found, create him
69
+ unless ret
70
+ ::Dust.print_msg 'create zabbix user in postgres', 2
71
+ ::Dust.print_result( node.exec('createuser -U postgres zabbix -RSD')[:exit_code] )
72
+ end
73
+
74
+ # TODO: only GRANT is this is a master
75
+ ::Dust.print_msg 'GRANT zabbix user access to postgres database'
76
+ ::Dust.print_result( node.exec('psql -U postgres -c "GRANT SELECT ON pg_stat_database TO zabbix" postgres')[:exit_code] )
77
+
78
+ # reload postgresql
79
+ node.reload_service('postgresql-9.0')
80
+
81
+ node.disconnect
82
+ puts
83
+ end
84
+ end
@@ -0,0 +1,382 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'net/scp'
4
+ require 'net/ssh/proxy/socks5'
5
+
6
+ module Dust
7
+ class Server
8
+ attr_reader :ssh
9
+
10
+ def initialize attr
11
+ @attr = attr
12
+
13
+ # store fully qualified domain name
14
+ @attr['fqdn'] = @attr['hostname']
15
+ @attr['fqdn'] += '.' + @attr['domain'] if @attr['domain']
16
+
17
+ @attr['user'] ||= 'root'
18
+ @attr['port'] ||= 22
19
+ @attr['password'] ||= ''
20
+ end
21
+
22
+ def connect
23
+ Dust.print_hostname @attr['hostname']
24
+ begin
25
+ # connect to proxy if given
26
+ proxy = @attr.has_key?('proxy') ? Net::SSH::Proxy::SOCKS5.new( @attr['proxy'].split(':')[0],
27
+ @attr['proxy'].split(':')[1] ) : nil
28
+
29
+ @ssh = Net::SSH.start(@attr['fqdn'], @attr['user'],
30
+ { :password => @attr['password'],
31
+ :port => @attr['port'],
32
+ :proxy => proxy } )
33
+ rescue Exception
34
+ error_message = " - coudln't connect to #{@attr['fqdn']}"
35
+ error_message += " (via socks5 proxy #{@attr['proxy']})" if proxy
36
+ Dust.print_failed error_message
37
+ return false
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+ def disconnect
44
+ @ssh.close
45
+ end
46
+
47
+ def exec command
48
+ stdout = ""
49
+ stderr = ""
50
+ exit_code = nil
51
+ exit_signal = nil
52
+
53
+ @ssh.open_channel do |channel|
54
+ channel.exec(command) do |ch, success|
55
+ unless success
56
+ abort "FAILED: couldn't execute command (ssh.channel.exec)"
57
+ end
58
+ channel.on_data do |ch, data|
59
+ stdout += data
60
+ end
61
+
62
+ channel.on_extended_data do |ch, type, data|
63
+ stderr += data
64
+ end
65
+
66
+ channel.on_request("exit-status") do |ch, data|
67
+ exit_code = data.read_long
68
+ end
69
+
70
+ channel.on_request("exit-signal") do |ch, data|
71
+ exit_signal = data.read_long
72
+ end
73
+ end
74
+ end
75
+
76
+ @ssh.loop
77
+
78
+ { :stdout => stdout, :stderr => stderr, :exit_code => exit_code, :exit_signal => exit_signal }
79
+ end
80
+
81
+ def write target, text, quiet=false, indent=1
82
+ Dust.print_msg("deploying #{File.basename(target)}", indent) unless quiet
83
+
84
+ # escape $ signs and \ at the end of line
85
+ text.gsub!('$','\$')
86
+ text.gsub!(/\\$/, '\\\\\\')
87
+
88
+ if exec("cat << EOF > #{target}\n#{text}\nEOF")[:exit_code] != 0
89
+ return Dust.print_result(false, quiet)
90
+ end
91
+
92
+ Dust.print_ok unless quiet
93
+ restorecon(target, quiet, indent) # restore SELinux labels
94
+ end
95
+
96
+ def append target, text, quiet=false, indent=1
97
+ Dust.print_msg("appending to #{File.basename(target)}", indent) unless quiet
98
+ Dust.print_result( exec("cat << EOF >> #{target}\n#{text}\nEOF")[:exit_code], quiet )
99
+ end
100
+
101
+ def scp source, destination, quiet=false, indent=1
102
+ Dust.print_msg("deploying #{File.basename(source)}", indent) unless quiet
103
+ @ssh.scp.upload!(source, destination)
104
+ Dust.print_result(true, quiet)
105
+ restorecon(destination, quiet, indent) # restore SELinux labels
106
+ end
107
+
108
+ def symlink source, destination, quiet=false, indent=1
109
+ Dust.print_msg("symlinking #{File.basename(source)} to '#{destination}'", indent) unless quiet
110
+ if exec("ln -s #{source} #{destination}")[:exit_code] != 0
111
+ return Dust.print_result(false, quiet)
112
+ end
113
+
114
+ Dust.print_ok unless quiet
115
+ restorecon(destination, quiet, indent) # restore SELinux labels
116
+ end
117
+
118
+ def chmod mode, file, quiet=false, indent=1
119
+ Dust.print_msg("setting mode of #{File.basename(file)} to #{mode}", indent) unless quiet
120
+ Dust.print_result( exec("chmod -R #{mode} #{file}")[:exit_code], quiet )
121
+ end
122
+
123
+ def chown user, file, quiet=false, indent=1
124
+ Dust.print_msg("setting owner of #{File.basename(file)} to #{user}", indent) unless quiet
125
+ Dust.print_result( exec("chown -R #{user} #{file}")[:exit_code], quiet )
126
+ end
127
+
128
+ def rm file, quiet=false, indent=1
129
+ Dust.print_msg("deleting #{file}", indent) unless quiet
130
+ Dust.print_result( exec("rm -rf #{file}")[:exit_code], quiet)
131
+ end
132
+
133
+ def mkdir dir, quiet=false, indent=1
134
+ return true if dir_exists? dir, true
135
+ Dust.print_msg("creating directory #{dir}", indent) unless quiet
136
+ if exec("mkdir -p #{dir}")[:exit_code] != 0
137
+ return Dust.print_result(false, quiet)
138
+ end
139
+
140
+ Dust.print_ok unless quiet
141
+ restorecon(dir, quiet, indent) # restore SELinux labels
142
+ end
143
+
144
+ # check if restorecon (selinux) is available
145
+ # if so, run it on "path" recursively
146
+ def restorecon path, quiet=false, indent=1
147
+
148
+ # if restorecon is not installed, just return true
149
+ ret = exec('which restorecon')
150
+ return true unless ret[:exit_code] == 0
151
+
152
+ Dust.print_msg("restoring selinux labels for #{path}", indent) unless quiet
153
+ Dust.print_result( exec("#{ret[:stdout].chomp} -R #{path}")[:exit_code], quiet )
154
+ end
155
+
156
+ def get_system_users quiet=false
157
+ Dust.print_msg("getting all system users", indent) unless quiet
158
+ ret = exec('getent passwd |cut -d: -f1')
159
+ Dust.print_result ret[:exit_code]
160
+
161
+ users = []
162
+ ret[:stdout].each do |user|
163
+ users.push user.chomp
164
+ end
165
+ users
166
+ end
167
+
168
+ # checks if one of the packages is installed
169
+ def package_installed? packages, quiet=false, indent=1
170
+ packages = [ packages ] if packages.class == String
171
+
172
+ Dust.print_msg("checking if #{packages.join(' or ')} is installed", indent) unless quiet
173
+
174
+ packages.each do |package|
175
+ if uses_apt? true
176
+ return Dust.print_result(true, quiet) unless exec("dpkg -s #{package} |grep 'install ok'")[:stdout].empty?
177
+ elsif uses_emerge? true
178
+ return Dust.print_result(true, quiet) unless exec("qlist -I #{package}")[:stdout].empty?
179
+ elsif uses_rpm? true
180
+ return Dust.print_result(true, quiet) if exec("rpm -q #{package}")[:exit_code] == 0
181
+ end
182
+ end
183
+
184
+ Dust.print_result(false, quiet)
185
+ end
186
+
187
+ def install_package package, quiet=false, indent=1, env=""
188
+ return true if package_installed? package, quiet, indent
189
+
190
+ Dust.print_msg("installing #{package}", indent + 1) unless quiet
191
+ if uses_apt? true
192
+ Dust.print_result exec("DEBIAN_FRONTEND=noninteractive aptitude install -y #{package}")[:exit_code], quiet
193
+ elsif uses_emerge? true
194
+ Dust.print_result exec("#{env} emerge #{package}")[:exit_code], quiet
195
+ elsif uses_rpm? true
196
+ Dust.print_result exec("yum install -y #{package}; rpm -q #{package}")[:exit_code], quiet
197
+ else
198
+ Dust.print_result false, quiet
199
+ end
200
+ end
201
+
202
+ def update_repos quiet=false, indent=1
203
+ Dust.print_msg('updating system repositories', indent) unless quiet
204
+ if uses_apt? true
205
+ Dust.print_result exec('DEBIAN_FRONTEND=noninteractive aptitude update')[:exit_code], quiet
206
+ elsif uses_emerge? true
207
+ Dust.print_result exec('emerge --sync')[:exit_code], quiet
208
+ elsif uses_rpm? true
209
+ Dust.print_result exec('yum check-update')[:exit_code], quiet
210
+ else
211
+ Dust.print_result false, quiet
212
+ end
213
+
214
+ end
215
+
216
+ def system_update quiet=false, indent=1
217
+ Dust.print_msg('installing system updates', indent) unless quiet
218
+
219
+ if uses_apt? true
220
+ Dust.print_result exec('DEBIAN_FRONTEND=noninteractive aptitude full-upgrade -y')[:exit_code], quiet
221
+ elsif uses_emerge? true
222
+ Dust.print_result exec('emerge -uND @world')[:exit_code], quiet
223
+ elsif uses_rpm? true
224
+ Dust.print_result exec('yum upgrade -y')[:exit_code], quiet
225
+ else
226
+ Dust.print_result false, quiet
227
+ end
228
+ end
229
+
230
+ # determining the system packet manager has to be done without facter
231
+ # because it's used to find out whether facter is installed / install facter
232
+ def uses_apt? quiet=false, indent=1
233
+ Dust.print_msg("determining whether node uses apt", indent) unless quiet
234
+ Dust.print_result exec('test -e /etc/debian_version')[:exit_code] == 0, quiet
235
+ end
236
+
237
+ def uses_rpm? quiet=false, indent=1
238
+ Dust.print_msg("determining whether node uses rpm", indent) unless quiet
239
+ Dust.print_result exec('test -e /etc/redhat-release')[:exit_code] == 0, quiet
240
+ end
241
+
242
+ def uses_emerge? quiet=false, indent=1
243
+ Dust.print_msg("determining whether node uses emerge", indent) unless quiet
244
+ Dust.print_result exec('test -e /etc/gentoo-release')[:exit_code] == 0, quiet
245
+ end
246
+
247
+ def is_os? os_list, quiet=false, indent=1
248
+ Dust.print_msg("checking if this machine runs #{os_list.join(' or ')}", indent) unless quiet
249
+ collect_facts quiet, indent unless @attr['operatingsystem']
250
+
251
+ os_list.each do |os|
252
+ return Dust.print_result(true, quiet) if @attr['operatingsystem'].downcase == os.downcase
253
+ end
254
+ Dust.print_result(false, quiet)
255
+ end
256
+
257
+ def is_debian? quiet=false, indent=1
258
+ is_os? [ 'debian' ], quiet, indent
259
+ end
260
+
261
+ def is_ubuntu? quiet=false, indent=1
262
+ is_os? [ 'ubuntu' ], quiet, indent
263
+ end
264
+
265
+ def is_gentoo? quiet=false, indent=1
266
+ is_os? [ 'gentoo' ], quiet, indent
267
+ end
268
+
269
+ def is_centos? quiet=false, indent=1
270
+ is_os? [ 'centos' ], quiet, indent
271
+ end
272
+
273
+ def is_scientific? quiet=false, indent=1
274
+ is_os? [ 'scientific' ], quiet, indent
275
+ end
276
+
277
+ def is_fedora? quiet=false, indent=1
278
+ is_os? [ 'fedora' ], quiet, indent
279
+ end
280
+
281
+ def is_executable? file, quiet=false, indent=1
282
+ Dust.print_msg("checking if file #{file} exists and is executeable", indent) unless quiet
283
+ Dust.print_result( exec("test -x $(which #{file})")[:exit_code], quiet )
284
+ end
285
+
286
+ def file_exists? file, quiet=false, indent=1
287
+ Dust.print_msg("checking if file #{file} exists", indent) unless quiet
288
+ Dust.print_result( exec("test -e #{file}")[:exit_code], quiet )
289
+ end
290
+
291
+ def dir_exists? dir, quiet=false, indent=1
292
+ Dust.print_msg("checking if directory #{dir} exists", indent) unless quiet
293
+ Dust.print_result( exec("test -d #{dir}")[:exit_code], quiet )
294
+ end
295
+
296
+ def autostart_service service, quiet=false, indent=1
297
+ Dust.print_msg("autostart #{service} on boot", indent) unless quiet
298
+ if uses_rpm? true
299
+ Dust.print_result( exec("chkconfig #{service} on")[:exit_code], quiet )
300
+ elsif uses_apt? true
301
+ Dust.print_result( exec("update-rc.d #{service} defaults")[:exit_code], quiet )
302
+ elsif uses_emerge? true
303
+ Dust.print_result( exec("rc-update add #{service} default")[:exit_code], quiet )
304
+ end
305
+ end
306
+
307
+ def restart_service service, quiet=false, indent=1
308
+ Dust.print_msg("restarting #{service}", indent) unless quiet
309
+ Dust.print_result( exec("/etc/init.d/#{service} restart")[:exit_code], quiet )
310
+ end
311
+
312
+ def reload_service service, quiet=false, indent=1
313
+ Dust.print_msg("reloading #{service}", indent) unless quiet
314
+ Dust.print_result( exec("/etc/init.d/#{service} reload")[:exit_code], quiet )
315
+ end
316
+
317
+ def qm_list name, quiet=false, indent=1
318
+ if name
319
+ Dust.print_msg("looking for a vm with name #{name}", indent) unless quiet
320
+ ret = exec("qm list |grep #{name}")
321
+ else
322
+ Dust.print_msg("looking for vms", indent) unless quiet
323
+ ret = exec('qm list |grep -v VMID')
324
+ end
325
+
326
+ if Dust.print_result(ret[:exit_code], quiet)
327
+ return "\t#{ret[:stdout].gsub(/\n/, "\n\t")}"
328
+ end
329
+
330
+ return ''
331
+ end
332
+
333
+ # check whether a user exists on this node
334
+ def user_exists? user, quiet=false, indent=1
335
+ Dust.print_msg "checking if user #{user} exists", indent unless quiet
336
+ Dust.print_result( exec("id #{user}")[:exit_code], quiet )
337
+ end
338
+
339
+ # create a user
340
+ def create_user user, home=nil, shell=nil, quiet=false, indent=1
341
+ return true if user_exists? user, quiet, indent
342
+
343
+ Dust.print_msg "creating user #{user}", indent + 1 unless quiet
344
+ cmd = "useradd #{user} -m"
345
+ cmd += " -d #{home}" if home
346
+ cmd += " -s #{home}" if shell
347
+ Dust.print_result( exec(cmd)[:exit_code], quiet )
348
+ end
349
+
350
+ def collect_facts quiet=false, indent=1
351
+ Dust.print_msg "collecting additional system facts\n" unless quiet
352
+
353
+ # collect system facts using puppets facter
354
+ install_package 'lsb-release', false, 2 if uses_apt?(true) and not package_installed?('lsb-release', true)
355
+ install_package 'facter', false, 2 unless package_installed? 'facter', true
356
+
357
+ Dust.print_msg 'running facter', 2 unless quiet
358
+ ret = exec 'facter -y'
359
+ @attr.merge! YAML.load ret[:stdout]
360
+ Dust.print_result ret[:exit_code], quiet
361
+ puts unless quiet
362
+ end
363
+
364
+ private
365
+
366
+ def method_missing method, *args, &block
367
+ # make server attributes accessible via server.attribute
368
+ if @attr[method.to_s]
369
+ @attr[method.to_s]
370
+
371
+ # and as server['attribute']
372
+ elsif @attr[args.first]
373
+ @attr[args.first]
374
+
375
+ # default to super
376
+ else
377
+ super
378
+ end
379
+ end
380
+
381
+ end
382
+ end