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