Reparto 1.0.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.
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
+