1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os.path, tarfile, tempfile
18 try:
19 from cStringIO import StringIO
20 assert StringIO
21 except ImportError:
22 from StringIO import StringIO
23 from twisted.internet import reactor
24 from twisted.spread import pb
25 from twisted.python import log
26 from buildbot.process.buildstep import RemoteCommand, BuildStep
27 from buildbot.process.buildstep import SUCCESS, FAILURE, SKIPPED
28 from buildbot.interfaces import BuildSlaveTooOldError
29 from buildbot.util import json
30 from buildbot import config
34 """
35 Helper class that acts as a file-object with write access
36 """
37
38 - def __init__(self, destfile, maxsize, mode):
39
40 destfile = os.path.abspath(destfile)
41 dirname = os.path.dirname(destfile)
42 if not os.path.exists(dirname):
43 os.makedirs(dirname)
44
45 self.destfile = destfile
46 self.mode = mode
47 fd, self.tmpname = tempfile.mkstemp(dir=dirname)
48 self.fp = os.fdopen(fd, 'wb')
49 self.remaining = maxsize
50
52 """
53 Called from remote slave to write L{data} to L{fp} within boundaries
54 of L{maxsize}
55
56 @type data: C{string}
57 @param data: String of data to write
58 """
59 if self.remaining is not None:
60 if len(data) > self.remaining:
61 data = data[:self.remaining]
62 self.fp.write(data)
63 self.remaining = self.remaining - len(data)
64 else:
65 self.fp.write(data)
66
68 os.utime(self.destfile,accessed_modified)
69
71 """
72 Called by remote slave to state that no more data will be transfered
73 """
74 self.fp.close()
75 self.fp = None
76
77 if os.path.exists(self.destfile):
78 os.unlink(self.destfile)
79 os.rename(self.tmpname, self.destfile)
80 self.tmpname = None
81 if self.mode is not None:
82 os.chmod(self.destfile, self.mode)
83
85
86
87 fp = getattr(self, "fp", None)
88 if fp:
89 fp.close()
90 os.unlink(self.destfile)
91 if self.tmpname and os.path.exists(self.tmpname):
92 os.unlink(self.tmpname)
93
96 """Fallback extractall method for TarFile, in case it doesn't have its own."""
97
98 import copy
99
100 directories = []
101
102 if members is None:
103 members = self
104
105 for tarinfo in members:
106 if tarinfo.isdir():
107
108 directories.append(tarinfo)
109 tarinfo = copy.copy(tarinfo)
110 tarinfo.mode = 0700
111 self.extract(tarinfo, path)
112
113
114 directories.sort(lambda a, b: cmp(a.name, b.name))
115 directories.reverse()
116
117
118 for tarinfo in directories:
119 dirpath = os.path.join(path, tarinfo.name)
120 try:
121 self.chown(tarinfo, dirpath)
122 self.utime(tarinfo, dirpath)
123 self.chmod(tarinfo, dirpath)
124 except tarfile.ExtractError, e:
125 if self.errorlevel > 1:
126 raise
127 else:
128 self._dbg(1, "tarfile: %s" % e)
129
131 """
132 A DirectoryWriter is implemented as a FileWriter, with an added post-processing
133 step to unpack the archive, once the transfer has completed.
134 """
135
136 - def __init__(self, destroot, maxsize, compress, mode):
137 self.destroot = destroot
138 self.compress = compress
139
140 self.fd, self.tarname = tempfile.mkstemp()
141 os.close(self.fd)
142
143 _FileWriter.__init__(self, self.tarname, maxsize, mode)
144
146 """
147 Called by remote slave to state that no more data will be transfered
148 """
149
150 self.remote_close()
151
152
153 if self.compress == 'bz2':
154 mode='r|bz2'
155 elif self.compress == 'gz':
156 mode='r|gz'
157 else:
158 mode = 'r'
159
160
161 if not hasattr(tarfile.TarFile, 'extractall'):
162 tarfile.TarFile.extractall = _extractall
163
164
165 archive = tarfile.open(name=self.tarname, mode=mode)
166 archive.extractall(path=self.destroot)
167 archive.close()
168 os.remove(self.tarname)
169
172 - def __init__(self, step, remote_command, args):
176
178 """
179 Base class for FileUpload and FileDownload to factor out common
180 functionality.
181 """
182 DEFAULT_WORKDIR = "build"
183
184 renderables = [ 'workdir' ]
185
186 haltOnFailure = True
187 flunkOnFailure = True
188
192
199
205
216
219
220 name = 'upload'
221
222 renderables = [ 'slavesrc', 'masterdest', 'url' ]
223
224 - def __init__(self, slavesrc, masterdest,
225 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
226 keepstamp=False, url=None,
227 **buildstep_kwargs):
228 BuildStep.__init__(self, **buildstep_kwargs)
229 self.addFactoryArguments(slavesrc=slavesrc,
230 masterdest=masterdest,
231 workdir=workdir,
232 maxsize=maxsize,
233 blocksize=blocksize,
234 mode=mode,
235 keepstamp=keepstamp,
236 url=url,
237 )
238
239 self.slavesrc = slavesrc
240 self.masterdest = masterdest
241 self.workdir = workdir
242 self.maxsize = maxsize
243 self.blocksize = blocksize
244 if not isinstance(mode, (int, type(None))):
245 config.error(
246 'mode must be an integer or None')
247 self.mode = mode
248 self.keepstamp = keepstamp
249 self.url = url
250
252 version = self.slaveVersion("uploadFile")
253
254 if not version:
255 m = "slave is too old, does not know about uploadFile"
256 raise BuildSlaveTooOldError(m)
257
258 source = self.slavesrc
259 masterdest = self.masterdest
260
261
262
263
264 masterdest = os.path.expanduser(masterdest)
265 log.msg("FileUpload started, from slave %r to master %r"
266 % (source, masterdest))
267
268 self.step_status.setText(['uploading', os.path.basename(source)])
269 if self.url is not None:
270 self.addURL(os.path.basename(masterdest), self.url)
271
272
273 fileWriter = _FileWriter(masterdest, self.maxsize, self.mode)
274
275 if self.keepstamp and self.slaveVersionIsOlderThan("uploadFile","2.13"):
276 m = ("This buildslave (%s) does not support preserving timestamps. "
277 "Please upgrade the buildslave." % self.build.slavename )
278 raise BuildSlaveTooOldError(m)
279
280
281 args = {
282 'slavesrc': source,
283 'workdir': self._getWorkdir(),
284 'writer': fileWriter,
285 'maxsize': self.maxsize,
286 'blocksize': self.blocksize,
287 'keepstamp': self.keepstamp,
288 }
289
290 self.cmd = StatusRemoteCommand(self, 'uploadFile', args)
291 d = self.runCommand(self.cmd)
292 @d.addErrback
293 def cancel(res):
294 fileWriter.cancel()
295 return res
296 d.addCallback(self.finished).addErrback(self.failed)
297
300
301 name = 'upload'
302
303 renderables = [ 'slavesrc', 'masterdest', 'url' ]
304
305 - def __init__(self, slavesrc, masterdest,
306 workdir=None, maxsize=None, blocksize=16*1024,
307 compress=None, url=None, **buildstep_kwargs):
308 BuildStep.__init__(self, **buildstep_kwargs)
309 self.addFactoryArguments(slavesrc=slavesrc,
310 masterdest=masterdest,
311 workdir=workdir,
312 maxsize=maxsize,
313 blocksize=blocksize,
314 compress=compress,
315 url=url,
316 )
317
318 self.slavesrc = slavesrc
319 self.masterdest = masterdest
320 self.workdir = workdir
321 self.maxsize = maxsize
322 self.blocksize = blocksize
323 if compress not in (None, 'gz', 'bz2'):
324 config.error(
325 "'compress' must be one of None, 'gz', or 'bz2'")
326 self.compress = compress
327 self.url = url
328
330 version = self.slaveVersion("uploadDirectory")
331
332 if not version:
333 m = "slave is too old, does not know about uploadDirectory"
334 raise BuildSlaveTooOldError(m)
335
336 source = self.slavesrc
337 masterdest = self.masterdest
338
339
340
341
342 masterdest = os.path.expanduser(masterdest)
343 log.msg("DirectoryUpload started, from slave %r to master %r"
344 % (source, masterdest))
345
346 self.step_status.setText(['uploading', os.path.basename(source)])
347 if self.url is not None:
348 self.addURL(os.path.basename(masterdest), self.url)
349
350
351 dirWriter = _DirectoryWriter(masterdest, self.maxsize, self.compress, 0600)
352
353
354 args = {
355 'slavesrc': source,
356 'workdir': self._getWorkdir(),
357 'writer': dirWriter,
358 'maxsize': self.maxsize,
359 'blocksize': self.blocksize,
360 'compress': self.compress
361 }
362
363 self.cmd = StatusRemoteCommand(self, 'uploadDirectory', args)
364 d = self.runCommand(self.cmd)
365 @d.addErrback
366 def cancel(res):
367 dirWriter.cancel()
368 return res
369 d.addCallback(self.finished).addErrback(self.failed)
370
381
386 """
387 Helper class that acts as a file-object with read access
388 """
389
392
394 """
395 Called from remote slave to read at most L{maxlength} bytes of data
396
397 @type maxlength: C{integer}
398 @param maxlength: Maximum number of data bytes that can be returned
399
400 @return: Data read from L{fp}
401 @rtype: C{string} of bytes read from file
402 """
403 if self.fp is None:
404 return ''
405
406 data = self.fp.read(maxlength)
407 return data
408
410 """
411 Called by remote slave to state that no more data will be transfered
412 """
413 if self.fp is not None:
414 self.fp.close()
415 self.fp = None
416
419
420 name = 'download'
421
422 renderables = [ 'mastersrc', 'slavedest' ]
423
424 - def __init__(self, mastersrc, slavedest,
425 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
426 **buildstep_kwargs):
445
447 version = self.slaveVersion("downloadFile")
448 if not version:
449 m = "slave is too old, does not know about downloadFile"
450 raise BuildSlaveTooOldError(m)
451
452
453
454 source = os.path.expanduser(self.mastersrc)
455 slavedest = self.slavedest
456 log.msg("FileDownload started, from master %r to slave %r" %
457 (source, slavedest))
458
459 self.step_status.setText(['downloading', "to",
460 os.path.basename(slavedest)])
461
462
463 try:
464 fp = open(source, 'rb')
465 except IOError:
466
467 self.addCompleteLog('stderr',
468 'File %r not available at master' % source)
469
470
471 reactor.callLater(0, BuildStep.finished, self, FAILURE)
472 return
473 fileReader = _FileReader(fp)
474
475
476 args = {
477 'slavedest': slavedest,
478 'maxsize': self.maxsize,
479 'reader': fileReader,
480 'blocksize': self.blocksize,
481 'workdir': self._getWorkdir(),
482 'mode': self.mode,
483 }
484
485 self.cmd = StatusRemoteCommand(self, 'downloadFile', args)
486 d = self.runCommand(self.cmd)
487 d.addCallback(self.finished).addErrback(self.failed)
488
490
491 name = 'string_download'
492
493 renderables = [ 'slavedest', 's' ]
494
495 - def __init__(self, s, slavedest,
496 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
497 **buildstep_kwargs):
516
518 version = self.slaveVersion("downloadFile")
519 if not version:
520 m = "slave is too old, does not know about downloadFile"
521 raise BuildSlaveTooOldError(m)
522
523
524
525 slavedest = self.slavedest
526 log.msg("StringDownload started, from master to slave %r" % slavedest)
527
528 self.step_status.setText(['downloading', "to",
529 os.path.basename(slavedest)])
530
531
532 fp = StringIO(self.s)
533 fileReader = _FileReader(fp)
534
535
536 args = {
537 'slavedest': slavedest,
538 'maxsize': self.maxsize,
539 'reader': fileReader,
540 'blocksize': self.blocksize,
541 'workdir': self._getWorkdir(),
542 'mode': self.mode,
543 }
544
545 self.cmd = StatusRemoteCommand(self, 'downloadFile', args)
546 d = self.runCommand(self.cmd)
547 d.addCallback(self.finished).addErrback(self.failed)
548
550
551 name = "json_download"
552
553 - def __init__(self, o, slavedest, **buildstep_kwargs):
559
561
562 name = "json_properties_download"
563
564 - def __init__(self, slavedest, **buildstep_kwargs):
569
582