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