1
2
3 import os.path, tarfile, tempfile
4 try:
5 from cStringIO import StringIO
6 except ImportError:
7 from StringIO import StringIO
8 from twisted.internet import reactor
9 from twisted.spread import pb
10 from twisted.python import log
11 from buildbot.process.buildstep import RemoteCommand, BuildStep
12 from buildbot.process.buildstep import SUCCESS, FAILURE, SKIPPED
13 from buildbot.interfaces import BuildSlaveTooOldError
14 from buildbot.util import json
15
16
18 """
19 Helper class that acts as a file-object with write access
20 """
21
22 - def __init__(self, destfile, maxsize, mode):
23
24 destfile = os.path.abspath(destfile)
25 dirname = os.path.dirname(destfile)
26 if not os.path.exists(dirname):
27 os.makedirs(dirname)
28
29 self.destfile = destfile
30 fd, self.tmpname = tempfile.mkstemp(dir=dirname)
31 self.fp = os.fdopen(fd, 'wb')
32 if mode is not None:
33 os.chmod(destfile, mode)
34 self.remaining = maxsize
35
37 """
38 Called from remote slave to write L{data} to L{fp} within boundaries
39 of L{maxsize}
40
41 @type data: C{string}
42 @param data: String of data to write
43 """
44 if self.remaining is not None:
45 if len(data) > self.remaining:
46 data = data[:self.remaining]
47 self.fp.write(data)
48 self.remaining = self.remaining - len(data)
49 else:
50 self.fp.write(data)
51
53 """
54 Called by remote slave to state that no more data will be transfered
55 """
56 self.fp.close()
57 self.fp = None
58 os.rename(self.tmpname, self.destfile)
59 self.tmpname = None
60
62
63
64 fp = getattr(self, "fp", None)
65 if fp:
66 fp.close()
67 os.unlink(self.destfile)
68 if self.tmpname and os.path.exists(self.tmpname):
69 os.unlink(self.tmpname)
70
71
73 """Fallback extractall method for TarFile, in case it doesn't have its own."""
74
75 import copy
76
77 directories = []
78
79 if members is None:
80 members = self
81
82 for tarinfo in members:
83 if tarinfo.isdir():
84
85 directories.append(tarinfo)
86 tarinfo = copy.copy(tarinfo)
87 tarinfo.mode = 0700
88 self.extract(tarinfo, path)
89
90
91 directories.sort(lambda a, b: cmp(a.name, b.name))
92 directories.reverse()
93
94
95 for tarinfo in directories:
96 dirpath = os.path.join(path, tarinfo.name)
97 try:
98 self.chown(tarinfo, dirpath)
99 self.utime(tarinfo, dirpath)
100 self.chmod(tarinfo, dirpath)
101 except tarfile.ExtractError, e:
102 if self.errorlevel > 1:
103 raise
104 else:
105 self._dbg(1, "tarfile: %s" % e)
106
108 """
109 A DirectoryWriter is implemented as a FileWriter, with an added post-processing
110 step to unpack the archive, once the transfer has completed.
111 """
112
113 - def __init__(self, destroot, maxsize, compress, mode):
114 self.destroot = destroot
115 self.compress = compress
116
117 self.fd, self.tarname = tempfile.mkstemp()
118 os.close(self.fd)
119
120 _FileWriter.__init__(self, self.tarname, maxsize, mode)
121
123 """
124 Called by remote slave to state that no more data will be transfered
125 """
126
127 self.remote_close()
128
129
130 if self.compress == 'bz2':
131 mode='r|bz2'
132 elif self.compress == 'gz':
133 mode='r|gz'
134 else:
135 mode = 'r'
136
137
138 if not hasattr(tarfile.TarFile, 'extractall'):
139 tarfile.TarFile.extractall = _extractall
140
141
142 archive = tarfile.open(name=self.tarname, mode=mode)
143 archive.extractall(path=self.destroot)
144 archive.close()
145 os.remove(self.tarname)
146
147
149 - def __init__(self, remote_command, args):
154
156
157 if 'rc' in update:
158 self.rc = update['rc']
159 if 'stderr' in update:
160 self.stderr = self.stderr + update['stderr'] + '\n'
161
163 """
164 Base class for FileUpload and FileDownload to factor out common
165 functionality.
166 """
167 DEFAULT_WORKDIR = "build"
168
172
180
193
194
196 """
197 Build step to transfer a file from the slave to the master.
198
199 arguments:
200
201 - ['slavesrc'] filename of source file at slave, relative to workdir
202 - ['masterdest'] filename of destination file at master
203 - ['workdir'] string with slave working directory relative to builder
204 base dir, default 'build'
205 - ['maxsize'] maximum size of the file, default None (=unlimited)
206 - ['blocksize'] maximum size of each block being transfered
207 - ['mode'] file access mode for the resulting master-side file.
208 The default (=None) is to leave it up to the umask of
209 the buildmaster process.
210
211 """
212
213 name = 'upload'
214
215 - def __init__(self, slavesrc, masterdest,
216 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
217 **buildstep_kwargs):
218 BuildStep.__init__(self, **buildstep_kwargs)
219 self.addFactoryArguments(slavesrc=slavesrc,
220 masterdest=masterdest,
221 workdir=workdir,
222 maxsize=maxsize,
223 blocksize=blocksize,
224 mode=mode,
225 )
226
227 self.slavesrc = slavesrc
228 self.masterdest = masterdest
229 self.workdir = workdir
230 self.maxsize = maxsize
231 self.blocksize = blocksize
232 assert isinstance(mode, (int, type(None)))
233 self.mode = mode
234
236 version = self.slaveVersion("uploadFile")
237 properties = self.build.getProperties()
238
239 if not version:
240 m = "slave is too old, does not know about uploadFile"
241 raise BuildSlaveTooOldError(m)
242
243 source = properties.render(self.slavesrc)
244 masterdest = properties.render(self.masterdest)
245
246
247
248
249 masterdest = os.path.expanduser(masterdest)
250 log.msg("FileUpload started, from slave %r to master %r"
251 % (source, masterdest))
252
253 self.step_status.setText(['uploading', os.path.basename(source)])
254
255
256 fileWriter = _FileWriter(masterdest, self.maxsize, self.mode)
257
258
259 args = {
260 'slavesrc': source,
261 'workdir': self._getWorkdir(),
262 'writer': fileWriter,
263 'maxsize': self.maxsize,
264 'blocksize': self.blocksize,
265 }
266
267 self.cmd = StatusRemoteCommand('uploadFile', args)
268 d = self.runCommand(self.cmd)
269 d.addCallback(self.finished).addErrback(self.failed)
270
271
273 """
274 Build step to transfer a directory from the slave to the master.
275
276 arguments:
277
278 - ['slavesrc'] name of source directory at slave, relative to workdir
279 - ['masterdest'] name of destination directory at master
280 - ['workdir'] string with slave working directory relative to builder
281 base dir, default 'build'
282 - ['maxsize'] maximum size of the compressed tarfile containing the
283 whole directory
284 - ['blocksize'] maximum size of each block being transfered
285 - ['compress'] compression type to use: one of [None, 'gz', 'bz2']
286
287 """
288
289 name = 'upload'
290
291 - def __init__(self, slavesrc, masterdest,
292 workdir="build", maxsize=None, blocksize=16*1024,
293 compress=None, **buildstep_kwargs):
294 BuildStep.__init__(self, **buildstep_kwargs)
295 self.addFactoryArguments(slavesrc=slavesrc,
296 masterdest=masterdest,
297 workdir=workdir,
298 maxsize=maxsize,
299 blocksize=blocksize,
300 compress=compress,
301 )
302
303 self.slavesrc = slavesrc
304 self.masterdest = masterdest
305 self.workdir = workdir
306 self.maxsize = maxsize
307 self.blocksize = blocksize
308 assert compress in (None, 'gz', 'bz2')
309 self.compress = compress
310
312 version = self.slaveVersion("uploadDirectory")
313 properties = self.build.getProperties()
314
315 if not version:
316 m = "slave is too old, does not know about uploadDirectory"
317 raise BuildSlaveTooOldError(m)
318
319 source = properties.render(self.slavesrc)
320 masterdest = properties.render(self.masterdest)
321
322
323
324
325 masterdest = os.path.expanduser(masterdest)
326 log.msg("DirectoryUpload started, from slave %r to master %r"
327 % (source, masterdest))
328
329 self.step_status.setText(['uploading', os.path.basename(source)])
330
331
332 dirWriter = _DirectoryWriter(masterdest, self.maxsize, self.compress, 0600)
333
334
335 args = {
336 'slavesrc': source,
337 'workdir': self.workdir,
338 'writer': dirWriter,
339 'maxsize': self.maxsize,
340 'blocksize': self.blocksize,
341 'compress': self.compress
342 }
343
344 self.cmd = StatusRemoteCommand('uploadDirectory', args)
345 d = self.runCommand(self.cmd)
346 d.addCallback(self.finished).addErrback(self.failed)
347
360
361
362
363
365 """
366 Helper class that acts as a file-object with read access
367 """
368
371
373 """
374 Called from remote slave to read at most L{maxlength} bytes of data
375
376 @type maxlength: C{integer}
377 @param maxlength: Maximum number of data bytes that can be returned
378
379 @return: Data read from L{fp}
380 @rtype: C{string} of bytes read from file
381 """
382 if self.fp is None:
383 return ''
384
385 data = self.fp.read(maxlength)
386 return data
387
389 """
390 Called by remote slave to state that no more data will be transfered
391 """
392 if self.fp is not None:
393 self.fp.close()
394 self.fp = None
395
396
398 """
399 Download the first 'maxsize' bytes of a file, from the buildmaster to the
400 buildslave. Set the mode of the file
401
402 Arguments::
403
404 ['mastersrc'] filename of source file at master
405 ['slavedest'] filename of destination file at slave
406 ['workdir'] string with slave working directory relative to builder
407 base dir, default 'build'
408 ['maxsize'] maximum size of the file, default None (=unlimited)
409 ['blocksize'] maximum size of each block being transfered
410 ['mode'] use this to set the access permissions of the resulting
411 buildslave-side file. This is traditionally an octal
412 integer, like 0644 to be world-readable (but not
413 world-writable), or 0600 to only be readable by
414 the buildslave account, or 0755 to be world-executable.
415 The default (=None) is to leave it up to the umask of
416 the buildslave process.
417
418 """
419 name = 'download'
420
421 - def __init__(self, mastersrc, slavedest,
422 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
423 **buildstep_kwargs):
424 BuildStep.__init__(self, **buildstep_kwargs)
425 self.addFactoryArguments(mastersrc=mastersrc,
426 slavedest=slavedest,
427 workdir=workdir,
428 maxsize=maxsize,
429 blocksize=blocksize,
430 mode=mode,
431 )
432
433 self.mastersrc = mastersrc
434 self.slavedest = slavedest
435 self.workdir = workdir
436 self.maxsize = maxsize
437 self.blocksize = blocksize
438 assert isinstance(mode, (int, type(None)))
439 self.mode = mode
440
442 properties = self.build.getProperties()
443
444 version = self.slaveVersion("downloadFile")
445 if not version:
446 m = "slave is too old, does not know about downloadFile"
447 raise BuildSlaveTooOldError(m)
448
449
450
451 source = os.path.expanduser(properties.render(self.mastersrc))
452 slavedest = properties.render(self.slavedest)
453 log.msg("FileDownload started, from master %r to slave %r" %
454 (source, slavedest))
455
456 self.step_status.setText(['downloading', "to",
457 os.path.basename(slavedest)])
458
459
460 try:
461 fp = open(source, 'rb')
462 except IOError:
463
464 self.addCompleteLog('stderr',
465 'File %r not available at master' % source)
466
467
468 reactor.callLater(0, BuildStep.finished, self, FAILURE)
469 return
470 fileReader = _FileReader(fp)
471
472
473 args = {
474 'slavedest': slavedest,
475 'maxsize': self.maxsize,
476 'reader': fileReader,
477 'blocksize': self.blocksize,
478 'workdir': self._getWorkdir(),
479 'mode': self.mode,
480 }
481
482 self.cmd = StatusRemoteCommand('downloadFile', args)
483 d = self.runCommand(self.cmd)
484 d.addCallback(self.finished).addErrback(self.failed)
485
487 """
488 Download the first 'maxsize' bytes of a string, from the buildmaster to the
489 buildslave. Set the mode of the file
490
491 Arguments::
492
493 ['s'] string to transfer
494 ['slavedest'] filename of destination file at slave
495 ['workdir'] string with slave working directory relative to builder
496 base dir, default 'build'
497 ['maxsize'] maximum size of the file, default None (=unlimited)
498 ['blocksize'] maximum size of each block being transfered
499 ['mode'] use this to set the access permissions of the resulting
500 buildslave-side file. This is traditionally an octal
501 integer, like 0644 to be world-readable (but not
502 world-writable), or 0600 to only be readable by
503 the buildslave account, or 0755 to be world-executable.
504 The default (=None) is to leave it up to the umask of
505 the buildslave process.
506 """
507 name = 'string_download'
508
509 - def __init__(self, s, slavedest,
510 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
511 **buildstep_kwargs):
528
530 properties = self.build.getProperties()
531
532 version = self.slaveVersion("downloadFile")
533 if not version:
534 m = "slave is too old, does not know about downloadFile"
535 raise BuildSlaveTooOldError(m)
536
537
538
539 slavedest = properties.render(self.slavedest)
540 log.msg("StringDownload started, from master to slave %r" % slavedest)
541
542 self.step_status.setText(['downloading', "to",
543 os.path.basename(slavedest)])
544
545
546 fp = StringIO(self.s)
547 fileReader = _FileReader(fp)
548
549
550 args = {
551 'slavedest': slavedest,
552 'maxsize': self.maxsize,
553 'reader': fileReader,
554 'blocksize': self.blocksize,
555 'workdir': self._getWorkdir(),
556 'mode': self.mode,
557 }
558
559 self.cmd = StatusRemoteCommand('downloadFile', args)
560 d = self.runCommand(self.cmd)
561 d.addCallback(self.finished).addErrback(self.failed)
562
564 """
565 Encode object o as a json string and save it on the buildslave
566
567 Arguments::
568
569 ['o'] object to encode and transfer
570 """
571 name = "json_download"
572 - def __init__(self, o, slavedest, **buildstep_kwargs):
578
580 """
581 Download the current build properties as a json string and save it on the
582 buildslave
583 """
584 name = "json_properties_download"
585 - def __init__(self, slavedest, **buildstep_kwargs):
590
603