Reparto 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,46 @@
1
+ =Reparto
2
+ Do things on multiple ssh connections
3
+
4
+ ==Requirements
5
+ you can install from rubygems or well from your distribution repositories
6
+
7
+ * net/ssh
8
+ * net/sftp
9
+ * ini
10
+ * r18n-desktop
11
+
12
+
13
+
14
+ ==You can
15
+ * Call commands
16
+ * Copy files
17
+ * Copy directories
18
+ * Update directories
19
+ * Easy file .ini with your actions
20
+
21
+
22
+ create a file myssh.ini:
23
+ ;this a comment to host 192.168.1.33
24
+ [192.168.1.33]
25
+ type = ssh
26
+ username = root
27
+ password = root
28
+ port = 22
29
+ ;update fail2ban from this host to remote host 192.168.1.33
30
+ cmd_1 = service fail2ban stop
31
+ updatedir_local_2 = /etc/fail2ban
32
+ updatedir_remote_2 = /etc/fail2ban
33
+ cmd_3 = service fail2ban start
34
+ ;backups directory
35
+ cpdir_local_4 = /etc/fail2ban
36
+ cpdir_remote_4 = /var/backups/fail2ban/20120415
37
+ ;update/copy one file
38
+ cp_local_5 = /etc/rc.local
39
+ cp_remote_5 = /etc/rc.local
40
+
41
+ ;another host actions
42
+ [....]
43
+ ....
44
+
45
+ then from a terminal call script, and look the output:
46
+ $ ruby reparto.rb myssh.ini
data/bin/reparto ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ require 'reparto'
5
+
6
+ #Muestra ayuda de uso
7
+ def usage
8
+ print $t.reparto.usage($0, REPARTO_VERSION)
9
+ print "\n"
10
+ exit -1
11
+ end
12
+
13
+ usage() if ARGV.size != 1 or not File.exist? ARGV[0]
14
+ r = Reparto.new(ARGV[0])
15
+ $threads.each do |th|
16
+ th.join
17
+ end
@@ -0,0 +1,41 @@
1
+ ini:
2
+ type: type
3
+ username: username
4
+ password: password
5
+ port: port
6
+ reparto:
7
+ conecting: Conecting to %1 port %2
8
+ closing: Closing conection %1
9
+ type_not_supported: Type %1 not supported
10
+ require_username: Required username
11
+ require_password: Required password
12
+ port_numeric: Port only numeric
13
+ usage: "
14
+ Reparto v%2 do things on multiple clients\n
15
+ %1: <file .ini>\n
16
+ example, myclients.ini:\n\n
17
+ [192.168.1.33]\n
18
+ type = ssh\n
19
+ username = user\n
20
+ password = pass\n
21
+ ;execute command\n
22
+ cmd_1 = hostname\n
23
+ ;execute another command\n
24
+ cmd_2 = date\n
25
+ ;copy new dir\n
26
+ cpdir_local_1 = /root/data\n
27
+ cpdir_remote_1 = /root/data\n
28
+ ;cp file\n
29
+ cp_local_1 = /etc/rc.local\n
30
+ cp_remote_1 = /root/backups/rc.local\n
31
+ ;update dir remote\n
32
+ updatedir_1 = /root/data\n
33
+
34
+ ;another host\n
35
+ [.....]\n
36
+ ...\n
37
+
38
+ ;another host\n
39
+ [....]\n
40
+ ..
41
+ "
@@ -0,0 +1,41 @@
1
+ ini:
2
+ type: tipo
3
+ username: usuario
4
+ password: clave
5
+ port: puerto
6
+ reparto:
7
+ conecting: Conectando a %1 puerto %2
8
+ closing: Cerrando conexion de %1
9
+ type_not_supported: Tipo %1 no soportado
10
+ require_username: Nombre de usuario requerido
11
+ require_password: Clave requerida
12
+ port_numeric: Puerto solo numerico
13
+ usage: "
14
+ Reparto v%2 do things on multiple clients\n
15
+ %1: <file .ini>\n
16
+ ejemplo, myclients.ini:\n\n
17
+ [192.168.1.33]\n
18
+ tipo = ssh\n
19
+ usuario = user\n
20
+ clave = pass\n
21
+ ;ejecuta comando \n
22
+ cmd_1 = hostname\n
23
+ ;ejecuta otro comando\n
24
+ cmd_2 = date\n
25
+ ;copia un directorio no existente en el equipo remoto\n
26
+ cpdir_local_1 = /root/data\n
27
+ cpdir_remote_1 = /root/data\n
28
+ ;copia un archivo\n
29
+ cp_local_1 = /etc/rc.local\n
30
+ cp_remote_1 = /root/backups/rc.local\n
31
+ ;actualiza directory remoto con local\n
32
+ updatedir_1 = /root/data\n
33
+
34
+ ;otro equipo\n
35
+ [.....]\n
36
+ ...\n
37
+
38
+ ;otro equipo\n
39
+ [....]\n
40
+ ..
41
+ "
data/lib/reparto.rb ADDED
@@ -0,0 +1,428 @@
1
+ #Reparto 0.0
2
+ #::Author:: bit4bit <bit4bit@riseup.net>
3
+ #::Date:: 15 abril 2012
4
+ #::License:: GPLv3 o Superior
5
+
6
+
7
+
8
+ require 'find'
9
+
10
+ begin
11
+ require 'inifile'
12
+ require 'r18n-desktop'
13
+ require 'logger'
14
+ require 'net/ssh'
15
+ require 'net/sftp'
16
+
17
+ rescue LoadError
18
+ require 'rubygems'
19
+ require 'net/ssh'
20
+ require 'net/sftp'
21
+ require 'inifile'
22
+ require 'r18n-desktop'
23
+ require 'logger'
24
+ end
25
+
26
+
27
+ REPARTO_VERSION = 0.0
28
+
29
+ $t = R18n.from_env File.join Gem.datadir('Reparto'), 'i18n/'
30
+ $logs = Logger.new(STDOUT)
31
+ $threads = []
32
+
33
+ class String
34
+ def is_number?
35
+ true if Float(self) rescue false
36
+ end
37
+ end
38
+
39
+ class SSHClient
40
+ def initialize(ip, port, username, password)
41
+ @ip = ip
42
+ @port = port
43
+ @username = username
44
+ @password = password
45
+ @actions = []
46
+ end
47
+
48
+ #ejecuta acciones
49
+ def do
50
+ $logs.debug($t.reparto.conecting(@ip, @port))
51
+
52
+ Net::SSH.start(@ip, @username, :password => @password, :port => @port) do |ssh|
53
+ @actions.each do |action|
54
+ name = action[:action]
55
+
56
+
57
+ case name
58
+ when :cmd
59
+ cmd = action[:cmd]
60
+ $logs.debug("%s command execute: %s" % [@ip,cmd])
61
+ r = ssh.exec! cmd
62
+ $logs.debug("%s command return: %s" % [@ip,r])
63
+ when :cp
64
+ local = action[:local]
65
+ remote = action[:remote]
66
+
67
+ unless File.exist? local
68
+ $logs.error("%s Not found %s to copy on local machine" % [@ip,local])
69
+ next
70
+ end
71
+
72
+ begin
73
+ ssh.sftp.connect do |sftp|
74
+
75
+ begin
76
+ remote = File.join(remote, File.basename(local)) if ssh.sftp.file.directory? remote
77
+ rescue
78
+ end
79
+
80
+ sftp.upload!(local, remote)
81
+ $logs.debug("%s cp: %s %s" % [@ip, local, remote])
82
+
83
+ end
84
+ rescue Net::SFTP::StatusException => e
85
+ $logs.error("%s error[%s] cp: %s %s" % [@ip, e.description, local, remote])
86
+ end
87
+
88
+ #Copia directorio local con remoto solo si no existe
89
+ when :cpdir
90
+ local = action[:local]
91
+ remote = action[:remote]
92
+
93
+ unless File.directory? local
94
+ $logs.error("%s Only dir allow for cpdir not %s" % [@ip, local])
95
+ end
96
+
97
+ begin
98
+ ssh.sftp.upload!(local, remote)
99
+ $logs.debug("%s cpdir: %s %s" % [@ip, local, remote])
100
+
101
+ rescue Net::SFTP::StatusException => e
102
+ case e.code
103
+ when 4
104
+ $logs.error("%s error[Can't copy directory already exist, use updatedir] cpdir: %s %s" % [@ip, local, remote])
105
+ else
106
+ $logs.error("%s error[%s] cpdir: %s %s" % [@ip, e.description, local, remote])
107
+ end
108
+ rescue ArgumentError
109
+ $logs.error("%s error[Need directory to upload] cpdir: %s %s" % [@ip, local, remote])
110
+ end
111
+
112
+ #Actualiza directory local con remoto
113
+ when :updatedir
114
+
115
+ ssh.sftp.connect do |sftp|
116
+ local_dir = action[:local]
117
+ remote_dir = action[:remote]
118
+
119
+ $logs.debug("Checking for files which need updating %s" % @ip)
120
+ Find.find(local_dir) do |file|
121
+ local_file = file
122
+ remote_file = File.join(remote_dir, local_file.sub(local_dir, ''))
123
+
124
+ #actualiza directorio no existene en el remoto
125
+ if File.directory? file
126
+ begin
127
+ ssh.sftp.file.directory? remote_file
128
+ rescue Net::SFTP::StatusException => e
129
+ if e.code == 2
130
+ sftp.upload!(file, remote_file)
131
+ $logs.debug("%s Updating, new dir %s" % [@ip, remote_file])
132
+ else
133
+ $logs.error("%s error[%s] updating new dir %s" % [@ip, e.description, remote_file])
134
+ end
135
+ end
136
+ next
137
+ end
138
+
139
+ #actualiza comparando mtime de stat
140
+ #o fechas de modificacion
141
+ begin
142
+ lstat = File.stat(local_file)
143
+ rstat = sftp.stat!(remote_file)
144
+
145
+ if lstat.mtime > Time.at(rstat.mtime)
146
+ $logs.debug("%s Updating %s" % [@ip, remote_file])
147
+ sftp.upload!(local_file, remote_file)
148
+ sftp.setstat!(remote_file, :permissions => lstat.mode)
149
+ end
150
+ rescue Net::SFTP::StatusException => e
151
+ if e.code == 2
152
+ sftp.upload!(local_file, remote_file)
153
+ $logs.debug("%s Updating new file %s" % [@ip, remote_file])
154
+ else
155
+ $logs.error("%s error[%s] updating new file %s" % [@ip, e.description, local_file])
156
+ end
157
+ rescue Errno::ENOENT
158
+ $logs.error("%s error[No such directory] updating: %s %s" % [@ip, local_file, remote_file])
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ def <<(action)
168
+ @actions << action
169
+ end
170
+
171
+ #Ejecuta comando en cliente
172
+ #::cmd:: comando en cliente
173
+ def cmd(cmd)
174
+ @actions << {:action => :cmd, :cmd => cmd}
175
+ end
176
+
177
+ #Copia archivo local a remote
178
+ #::local:: archivo local
179
+ #::remote:: archivo remote
180
+ def cp(local, remote)
181
+ @actions << {:action => :cp, :local =>local, :remote =>remote}
182
+ end
183
+
184
+ #Copia directoria local a remote
185
+ #::local:: directorio local
186
+ #::remote:: directorio remote
187
+ def cp_dir(local, remote)
188
+ @actions << {:action => :cpdir, :local => local, :remote => remote}
189
+ end
190
+
191
+ #Actualiza directorio remote en caso de cambio
192
+ #::dir:: directorio local/remote
193
+ def update_dir(local, remote)
194
+ @actions << {:action => :updatedir, :local => local, :remote => remote}
195
+ end
196
+ end
197
+
198
+
199
+ #Clase Reparto
200
+ #Lee archivo .ini parsea, y crea hilos de ejecucion segun
201
+ #instrucciones indicadas
202
+ class Reparto
203
+
204
+ #::filename:: archivo .ini
205
+ def initialize(filename)
206
+ @fn = filename
207
+ @ini = IniFile.new(@fn)
208
+
209
+ @ssh_clients = {}
210
+
211
+ @types_supported = ['ssh']
212
+ parse
213
+ end
214
+
215
+ #Parse el archivo indicado y crea hilos de ejecucion
216
+ def parse
217
+
218
+ #Por cada seccion o equipo, es un hilo
219
+ @ini.each_section do |section|
220
+
221
+ #@todo validate ip
222
+ ip = section
223
+
224
+ #obtiene tipo
225
+ if @ini[section].has_key? $t.ini['type']
226
+ type = @ini[section][$t.ini['type']]
227
+ unless @types_supported.member? type
228
+ raise RuntimeError, $t.reparto.type_not_supported(type)
229
+ end
230
+ else
231
+ type = 'ssh'
232
+ end
233
+
234
+ #valida campos requeridos
235
+ raise RuntimeError, $t.reparto.require_username unless @ini[section].has_key? $t.ini.username
236
+ raise RuntimeError, $t.reparto.require_password unless @ini[section].has_key? $t.ini.password
237
+
238
+ username = @ini[section][$t.ini.username]
239
+ password = @ini[section][$t.ini.password]
240
+
241
+ #se obtiene puerto o por defecto 22
242
+ if @ini[section].has_key? $t.ini.port
243
+ port = @ini[section][$t.ini.port]
244
+ unless port.is_number?
245
+ raise RuntimeError, $t.reparto.port_numeric
246
+ end
247
+ port = port.to_i
248
+ else
249
+ case type
250
+ when 'ssh'
251
+ port = 22
252
+ end
253
+ end
254
+
255
+ #comandos a ejecutar
256
+ #en orden , segun paramentro terminado en [0-9]+
257
+ cmds = []
258
+ cmd_index = nil
259
+
260
+ #se lee parametros y se asignan segun nombre
261
+ @ini[section].keys.each do |param|
262
+ cpdirs = nil
263
+ cpfiles = nil
264
+ updatedirs = nil
265
+
266
+ cmd = param.match(/cmd_([0-9]+)/)
267
+ if param =~ /cmd/
268
+ cmd_index = cmd[1].to_i
269
+ cmds[cmd_index] = ["cmd", @ini[section][param]]
270
+ end
271
+
272
+ #cp directorios
273
+ cpdir = param.match(/cpdir_local_([0-9]+)/)
274
+ if cpdir
275
+ cpdir_num = cpdir[1].to_i
276
+ cpdir_index = "cpdir_%d" % cpdir_num
277
+ cmd_index = cpdir_num
278
+ cpdirs = cmds.assoc(cpdir_index)
279
+
280
+ if cpdirs.nil?
281
+ cpdirs = [cpdir_index, {}]
282
+ cpdirs[1][:local] = @ini[section][param]
283
+ cmds[cmd_index] = cpdirs
284
+ else
285
+ cmds[cmds.index{|x| x[0] == cpdir_index unless x.nil?}][1][:local] = @ini[section][param]
286
+ end
287
+ end
288
+
289
+ cpdir = param.match(/cpdir_remote_([0-9]+)/)
290
+ if cpdir
291
+ cpdir_num = cpdir[1].to_i
292
+ cpdir_index = "cpdir_%d" % cpdir_num
293
+ cmd_index = cpdir_num
294
+ cpdirs = cmds.assoc(cpdir_index)
295
+ if cpdirs.nil?
296
+ cpdirs = [cpdir_index, {}]
297
+ cpdirs[1][:remote] = @ini[section][param]
298
+ cmds[cmd_index] = cpdirs
299
+ else
300
+ cmds[cmds.index{|x| x[0] == cpdir_index unless x.nil?}][1][:remote] = @ini[section][param]
301
+ end
302
+ end
303
+
304
+
305
+ #cp archivos
306
+ cpfile = param.match(/cp_local_([0-9]+)/)
307
+ if cpfile
308
+ cp_num = cpfile[1].to_i
309
+ cp_index = "cp_%d" % cp_num
310
+ cmd_index = cp_num
311
+ cpfiles = cmds.assoc(cp_index)
312
+ if cpfiles.nil?
313
+ cpfiles = [cp_index, {}]
314
+ cpfiles[1][:local] = @ini[section][param]
315
+ cmds[cmd_index] = cpfiles
316
+ else
317
+ cmds[cmds.index{|x| x[0] == cp_index unless x.nil?}][1][:remote] = @ini[section][param]
318
+ end
319
+ end
320
+
321
+ cpfile = param.match(/cp_remote_([0-9]+)/)
322
+ if cpfile
323
+ cp_num = cpfile[1].to_i
324
+ cp_index = "cp_%d" % cp_num
325
+ cmd_index = cp_num
326
+ cpfiles = cmds.assoc(cp_index)
327
+ if cpfiles.nil?
328
+ cpfiles = [cp_index, {}]
329
+ cpfiles[1][:remote] = @ini[section][param]
330
+ cmds[cmd_index] = cpfiles
331
+ else
332
+ cmds[cmds.index{|x| x[0] == cp_index unless x.nil?}][1][:remote] = @ini[section][param]
333
+ end
334
+ end
335
+
336
+
337
+ #actualiza archivos/directorios en remoto
338
+ updatedir = param.match(/updatedir_([a-z]+_)?([0-9]+)/)
339
+
340
+ if param =~ /^updatedir_([0-9]+)$/
341
+ i_num = updatedir[2].to_i
342
+ cmd_index = i_num
343
+ up_index = "updatedir_%d" % i_num
344
+ updatedirs = cmds.assoc(up_index)
345
+ if updatedirs.nil?
346
+ updatedirs = [up_index, {}]
347
+ updatedirs[1][:remote] = @ini[section][param]
348
+ updatedirs[1][:local] = @ini[section][param]
349
+ cmds[cmd_index] = updatedirs
350
+ else
351
+ cmds[cmds.index{|x| x[0] == up_index unless x.nil?}][1][:local] = @ini[section][param]
352
+ cmds[cmds.index{|x| x[0] == up_index unless x.nil?}][1][:remote] = @ini[section][param]
353
+ end
354
+ end
355
+
356
+ if param =~ /^updatedir_local/
357
+ i_num = updatedir[2].to_i
358
+ cmd_index = i_num
359
+ up_index = "updatedir_%d" % i_num
360
+ updatedirs = cmds.assoc(up_index)
361
+ if updatedirs.nil?
362
+ updatedirs = [up_index, {}]
363
+ updatedirs[1][:local] = @ini[section][param]
364
+ cmds[cmd_index] = updatedirs
365
+ else
366
+ cmds[cmds.index{|x| x[0] == up_index unless x.nil?}][1][:local] = @ini[section][param]
367
+ end
368
+ end
369
+
370
+ if param =~ /^updatedir_remote/
371
+ i_num = updatedir[2].to_i
372
+ cmd_index = i_num
373
+ up_index = "updatedir_%d" % i_num
374
+ updatedirs = cmds.assoc(up_index)
375
+ if updatedirs.nil?
376
+ updatedirs = [up_index, {}]
377
+ updatedirs[1][:remote] = @ini[section][param]
378
+ cmds[cmd_index] = updatedirs
379
+ else
380
+ cmds[cmds.index{|x| x[0] == up_index unless x.nil?}][1][:remote] = @ini[section][param]
381
+ end
382
+ end
383
+
384
+
385
+ end
386
+
387
+ case type
388
+ when 'ssh'
389
+ cssh = SSHClient.new(ip, port, username, password)
390
+ cmds.each do |cmd|
391
+ next if cmd.nil?
392
+ type = cmd[0]
393
+ args = cmd[1]
394
+
395
+ if type =~ /cmd/
396
+ cssh.cmd(args)
397
+ elsif type =~ /cpdir/
398
+ cssh.cp_dir(args[:local], args[:remote])
399
+ elsif type =~ /cp/
400
+ cssh.cp(args[:local], args[:remote])
401
+ elsif type =~ /updatedir/
402
+ cssh.update_dir(args[:local], args[:remote])
403
+ else
404
+ $logs.error("Unknown action %s\n" % type)
405
+ end
406
+ end
407
+
408
+ #Hilo por cliente
409
+ $threads << Thread.new(cssh) do |tssh|
410
+ begin
411
+ cssh.do
412
+ rescue Errno::ENETUNREACH
413
+ $logs.error("Can't connect to %s" % ip)
414
+ rescue Errno::EHOSTUNREACH
415
+ $logs.error("Can't route to host %s" % ip)
416
+ end
417
+ end
418
+
419
+ end
420
+
421
+ end
422
+
423
+ end
424
+
425
+ end
426
+
427
+
428
+
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Reparto
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Bit4bit
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-08 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: inifile
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 31
29
+ segments:
30
+ - 0
31
+ - 0
32
+ - 0
33
+ version: 0.0.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: r18n-desktop
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 31
45
+ segments:
46
+ - 0
47
+ - 0
48
+ - 0
49
+ version: 0.0.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Exectue commands on multiple computers
53
+ email: bit4bit@riseup.net
54
+ executables:
55
+ - reparto
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ files:
61
+ - lib/reparto.rb
62
+ - data/Reparto/i18n/es.yml
63
+ - data/Reparto/i18n/en.yml
64
+ - README.rdoc
65
+ - bin/reparto
66
+ homepage: https://github.com/bit4bit/reparto
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 3
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ requirements: []
93
+
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.24
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Do thing with multiple ssh connections
99
+ test_files: []
100
+