1
2
3 import os.path, tarfile, tempfile
4 from twisted.internet import reactor
5 from twisted.spread import pb
6 from twisted.python import log
7 from buildbot.process.buildstep import RemoteCommand, BuildStep
8 from buildbot.process.buildstep import SUCCESS, FAILURE, SKIPPED
9 from buildbot.interfaces import BuildSlaveTooOldError
10
11
13 """
14 Helper class that acts as a file-object with write access
15 """
16
17 - def __init__(self, destfile, maxsize, mode):
18
19 destfile = os.path.abspath(destfile)
20 dirname = os.path.dirname(destfile)
21 if not os.path.exists(dirname):
22 os.makedirs(dirname)
23
24 self.destfile = destfile
25 self.fp = open(destfile, "wb")
26 if mode is not None:
27 os.chmod(destfile, mode)
28 self.remaining = maxsize
29
31 """
32 Called from remote slave to write L{data} to L{fp} within boundaries
33 of L{maxsize}
34
35 @type data: C{string}
36 @param data: String of data to write
37 """
38 if self.remaining is not None:
39 if len(data) > self.remaining:
40 data = data[:self.remaining]
41 self.fp.write(data)
42 self.remaining = self.remaining - len(data)
43 else:
44 self.fp.write(data)
45
47 """
48 Called by remote slave to state that no more data will be transfered
49 """
50 self.fp.close()
51 self.fp = None
52
54
55
56 fp = getattr(self, "fp", None)
57 if fp:
58 fp.close()
59 os.unlink(self.destfile)
60
61
63 """Fallback extractall method for TarFile, in case it doesn't have its own."""
64
65 import copy
66
67 directories = []
68
69 if members is None:
70 members = self
71
72 for tarinfo in members:
73 if tarinfo.isdir():
74
75 directories.append(tarinfo)
76 tarinfo = copy.copy(tarinfo)
77 tarinfo.mode = 0700
78 self.extract(tarinfo, path)
79
80
81 directories.sort(lambda a, b: cmp(a.name, b.name))
82 directories.reverse()
83
84
85 for tarinfo in directories:
86 dirpath = os.path.join(path, tarinfo.name)
87 try:
88 self.chown(tarinfo, dirpath)
89 self.utime(tarinfo, dirpath)
90 self.chmod(tarinfo, dirpath)
91 except tarfile.ExtractError, e:
92 if self.errorlevel > 1:
93 raise
94 else:
95 self._dbg(1, "tarfile: %s" % e)
96
98 """
99 A DirectoryWriter is implemented as a FileWriter, with an added post-processing
100 step to unpack the archive, once the transfer has completed.
101 """
102
103 - def __init__(self, destroot, maxsize, compress, mode):
104 self.destroot = destroot
105
106 self.fd, self.tarname = tempfile.mkstemp()
107 self.compress = compress
108 _FileWriter.__init__(self, self.tarname, maxsize, mode)
109
111 """
112 Called by remote slave to state that no more data will be transfered
113 """
114 if self.fp:
115 self.fp.close()
116 self.fp = None
117 fileobj = os.fdopen(self.fd, 'rb')
118 if self.compress == 'bz2':
119 mode='r|bz2'
120 elif self.compress == 'gz':
121 mode='r|gz'
122 else:
123 mode = 'r'
124 if not hasattr(tarfile.TarFile, 'extractall'):
125 tarfile.TarFile.extractall = _extractall
126 archive = tarfile.open(name=self.tarname, mode=mode, fileobj=fileobj)
127 archive.extractall(path=self.destroot)
128 archive.close()
129 fileobj.close()
130 os.remove(self.tarname)
131
132
134 - def __init__(self, remote_command, args):
139
141
142 if 'rc' in update:
143 self.rc = update['rc']
144 if 'stderr' in update:
145 self.stderr = self.stderr + update['stderr'] + '\n'
146
148 """
149 Base class for FileUpload and FileDownload to factor out common
150 functionality.
151 """
152 DEFAULT_WORKDIR = "build"
153
157
165
178
179
181 """
182 Build step to transfer a file from the slave to the master.
183
184 arguments:
185
186 - ['slavesrc'] filename of source file at slave, relative to workdir
187 - ['masterdest'] filename of destination file at master
188 - ['workdir'] string with slave working directory relative to builder
189 base dir, default 'build'
190 - ['maxsize'] maximum size of the file, default None (=unlimited)
191 - ['blocksize'] maximum size of each block being transfered
192 - ['mode'] file access mode for the resulting master-side file.
193 The default (=None) is to leave it up to the umask of
194 the buildmaster process.
195
196 """
197
198 name = 'upload'
199
200 - def __init__(self, slavesrc, masterdest,
201 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
202 **buildstep_kwargs):
203 BuildStep.__init__(self, **buildstep_kwargs)
204 self.addFactoryArguments(slavesrc=slavesrc,
205 masterdest=masterdest,
206 workdir=workdir,
207 maxsize=maxsize,
208 blocksize=blocksize,
209 mode=mode,
210 )
211
212 self.slavesrc = slavesrc
213 self.masterdest = masterdest
214 self.workdir = workdir
215 self.maxsize = maxsize
216 self.blocksize = blocksize
217 assert isinstance(mode, (int, type(None)))
218 self.mode = mode
219
221 version = self.slaveVersion("uploadFile")
222 properties = self.build.getProperties()
223
224 if not version:
225 m = "slave is too old, does not know about uploadFile"
226 raise BuildSlaveTooOldError(m)
227
228 source = properties.render(self.slavesrc)
229 masterdest = properties.render(self.masterdest)
230
231
232
233
234 masterdest = os.path.expanduser(masterdest)
235 log.msg("FileUpload started, from slave %r to master %r"
236 % (source, masterdest))
237
238 self.step_status.setText(['uploading', os.path.basename(source)])
239
240
241 fileWriter = _FileWriter(masterdest, self.maxsize, self.mode)
242
243
244 args = {
245 'slavesrc': source,
246 'workdir': self._getWorkdir(),
247 'writer': fileWriter,
248 'maxsize': self.maxsize,
249 'blocksize': self.blocksize,
250 }
251
252 self.cmd = StatusRemoteCommand('uploadFile', args)
253 d = self.runCommand(self.cmd)
254 d.addCallback(self.finished).addErrback(self.failed)
255
256
258 """
259 Build step to transfer a directory from the slave to the master.
260
261 arguments:
262
263 - ['slavesrc'] name of source directory at slave, relative to workdir
264 - ['masterdest'] name of destination directory at master
265 - ['workdir'] string with slave working directory relative to builder
266 base dir, default 'build'
267 - ['maxsize'] maximum size of the compressed tarfile containing the
268 whole directory
269 - ['blocksize'] maximum size of each block being transfered
270 - ['compress'] compression type to use: one of [None, 'gz', 'bz2']
271
272 """
273
274 name = 'upload'
275
276 - def __init__(self, slavesrc, masterdest,
277 workdir="build", maxsize=None, blocksize=16*1024,
278 compress=None, **buildstep_kwargs):
279 BuildStep.__init__(self, **buildstep_kwargs)
280 self.addFactoryArguments(slavesrc=slavesrc,
281 masterdest=masterdest,
282 workdir=workdir,
283 maxsize=maxsize,
284 blocksize=blocksize,
285 compress=compress,
286 )
287
288 self.slavesrc = slavesrc
289 self.masterdest = masterdest
290 self.workdir = workdir
291 self.maxsize = maxsize
292 self.blocksize = blocksize
293 assert compress in (None, 'gz', 'bz2')
294 self.compress = compress
295
297 version = self.slaveVersion("uploadDirectory")
298 properties = self.build.getProperties()
299
300 if not version:
301 m = "slave is too old, does not know about uploadDirectory"
302 raise BuildSlaveTooOldError(m)
303
304 source = properties.render(self.slavesrc)
305 masterdest = properties.render(self.masterdest)
306
307
308
309
310 masterdest = os.path.expanduser(masterdest)
311 log.msg("DirectoryUpload started, from slave %r to master %r"
312 % (source, masterdest))
313
314 self.step_status.setText(['uploading', os.path.basename(source)])
315
316
317 dirWriter = _DirectoryWriter(masterdest, self.maxsize, self.compress, 0600)
318
319
320 args = {
321 'slavesrc': source,
322 'workdir': self.workdir,
323 'writer': dirWriter,
324 'maxsize': self.maxsize,
325 'blocksize': self.blocksize,
326 'compress': self.compress
327 }
328
329 self.cmd = StatusRemoteCommand('uploadDirectory', args)
330 d = self.runCommand(self.cmd)
331 d.addCallback(self.finished).addErrback(self.failed)
332
345
346
347
348
350 """
351 Helper class that acts as a file-object with read access
352 """
353
356
358 """
359 Called from remote slave to read at most L{maxlength} bytes of data
360
361 @type maxlength: C{integer}
362 @param maxlength: Maximum number of data bytes that can be returned
363
364 @return: Data read from L{fp}
365 @rtype: C{string} of bytes read from file
366 """
367 if self.fp is None:
368 return ''
369
370 data = self.fp.read(maxlength)
371 return data
372
374 """
375 Called by remote slave to state that no more data will be transfered
376 """
377 if self.fp is not None:
378 self.fp.close()
379 self.fp = None
380
381
383 """
384 Download the first 'maxsize' bytes of a file, from the buildmaster to the
385 buildslave. Set the mode of the file
386
387 Arguments::
388
389 ['mastersrc'] filename of source file at master
390 ['slavedest'] filename of destination file at slave
391 ['workdir'] string with slave working directory relative to builder
392 base dir, default 'build'
393 ['maxsize'] maximum size of the file, default None (=unlimited)
394 ['blocksize'] maximum size of each block being transfered
395 ['mode'] use this to set the access permissions of the resulting
396 buildslave-side file. This is traditionally an octal
397 integer, like 0644 to be world-readable (but not
398 world-writable), or 0600 to only be readable by
399 the buildslave account, or 0755 to be world-executable.
400 The default (=None) is to leave it up to the umask of
401 the buildslave process.
402
403 """
404 name = 'download'
405
406 - def __init__(self, mastersrc, slavedest,
407 workdir=None, maxsize=None, blocksize=16*1024, mode=None,
408 **buildstep_kwargs):
409 BuildStep.__init__(self, **buildstep_kwargs)
410 self.addFactoryArguments(mastersrc=mastersrc,
411 slavedest=slavedest,
412 workdir=workdir,
413 maxsize=maxsize,
414 blocksize=blocksize,
415 mode=mode,
416 )
417
418 self.mastersrc = mastersrc
419 self.slavedest = slavedest
420 self.workdir = workdir
421 self.maxsize = maxsize
422 self.blocksize = blocksize
423 assert isinstance(mode, (int, type(None)))
424 self.mode = mode
425
427 properties = self.build.getProperties()
428
429 version = self.slaveVersion("downloadFile")
430 if not version:
431 m = "slave is too old, does not know about downloadFile"
432 raise BuildSlaveTooOldError(m)
433
434
435
436 source = os.path.expanduser(properties.render(self.mastersrc))
437 slavedest = properties.render(self.slavedest)
438 log.msg("FileDownload started, from master %r to slave %r" %
439 (source, slavedest))
440
441 self.step_status.setText(['downloading', "to",
442 os.path.basename(slavedest)])
443
444
445 try:
446 fp = open(source, 'rb')
447 except IOError:
448
449 self.addCompleteLog('stderr',
450 'File %r not available at master' % source)
451
452
453 reactor.callLater(0, BuildStep.finished, self, FAILURE)
454 return
455 fileReader = _FileReader(fp)
456
457
458 args = {
459 'slavedest': slavedest,
460 'maxsize': self.maxsize,
461 'reader': fileReader,
462 'blocksize': self.blocksize,
463 'workdir': self._getWorkdir(),
464 'mode': self.mode,
465 }
466
467 self.cmd = StatusRemoteCommand('downloadFile', args)
468 d = self.runCommand(self.cmd)
469 d.addCallback(self.finished).addErrback(self.failed)
470