Package buildslave :: Package scripts :: Module runner
[frames] | no frames]

Source Code for Module buildslave.scripts.runner

  1  # N.B.: don't import anything that might pull in a reactor yet. Some of our 
  2  # subcommands want to load modules that need the gtk reactor. 
  3  import os, sys, stat, re, time 
  4  import traceback 
  5  from twisted.python import usage, util, runtime 
  6   
7 -def isBuildslaveDir(dir):
8 buildbot_tac = os.path.join(dir, "buildbot.tac") 9 if not os.path.isfile(buildbot_tac): 10 print "no buildbot.tac" 11 return False 12 13 contents = open(buildbot_tac, "r").read() 14 return "Application('buildslave')" in contents
15 16 # the create/start/stop commands should all be run as the same user, 17 # preferably a separate 'buildbot' account. 18 19 # Note that the terms 'options' and 'config' are used intechangeably here - in 20 # fact, they are intercanged several times. Caveat legator. 21
22 -class Maker:
23 - def __init__(self, config):
24 self.config = config 25 self.basedir = config['basedir'] 26 self.force = config.get('force', False) 27 self.quiet = config['quiet']
28
29 - def mkdir(self):
30 if os.path.exists(self.basedir): 31 if not self.quiet: 32 print "updating existing installation" 33 return 34 if not self.quiet: 35 print "mkdir", self.basedir 36 os.mkdir(self.basedir)
37
38 - def mkinfo(self):
39 path = os.path.join(self.basedir, "info") 40 if not os.path.exists(path): 41 if not self.quiet: 42 print "mkdir", path 43 os.mkdir(path) 44 created = False 45 admin = os.path.join(path, "admin") 46 if not os.path.exists(admin): 47 if not self.quiet: 48 print "Creating info/admin, you need to edit it appropriately" 49 f = open(admin, "wt") 50 f.write("Your Name Here <admin@youraddress.invalid>\n") 51 f.close() 52 created = True 53 host = os.path.join(path, "host") 54 if not os.path.exists(host): 55 if not self.quiet: 56 print "Creating info/host, you need to edit it appropriately" 57 f = open(host, "wt") 58 f.write("Please put a description of this build host here\n") 59 f.close() 60 created = True 61 access_uri = os.path.join(path, "access_uri") 62 if not os.path.exists(access_uri): 63 if not self.quiet: 64 print "Not creating info/access_uri - add it if you wish" 65 if created and not self.quiet: 66 print "Please edit the files in %s appropriately." % path
67
68 - def chdir(self):
69 if not self.quiet: 70 print "chdir", self.basedir 71 os.chdir(self.basedir)
72
73 - def makeTAC(self, contents, secret=False):
74 tacfile = "buildbot.tac" 75 if os.path.exists(tacfile): 76 oldcontents = open(tacfile, "rt").read() 77 if oldcontents == contents: 78 if not self.quiet: 79 print "buildbot.tac already exists and is correct" 80 return 81 if not self.quiet: 82 print "not touching existing buildbot.tac" 83 print "creating buildbot.tac.new instead" 84 tacfile = "buildbot.tac.new" 85 f = open(tacfile, "wt") 86 f.write(contents) 87 f.close() 88 if secret: 89 os.chmod(tacfile, 0600)
90 91 slaveTACTemplate = [""" 92 import os 93 94 from twisted.application import service 95 from buildslave.bot import BuildSlave 96 97 basedir = r'%(basedir)s' 98 rotateLength = %(log-size)s 99 maxRotatedFiles = %(log-count)s 100 101 # if this is a relocatable tac file, get the directory containing the TAC 102 if basedir == '.': 103 import os.path 104 basedir = os.path.abspath(os.path.dirname(__file__)) 105 106 # note: this line is matched against to check that this is a buildslave 107 # directory; do not edit it. 108 application = service.Application('buildslave') 109 """, 110 """ 111 try: 112 from twisted.python.logfile import LogFile 113 from twisted.python.log import ILogObserver, FileLogObserver 114 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, 115 maxRotatedFiles=maxRotatedFiles) 116 application.setComponent(ILogObserver, FileLogObserver(logfile).emit) 117 except ImportError: 118 # probably not yet twisted 8.2.0 and beyond, can't set log yet 119 pass 120 """, 121 """ 122 buildmaster_host = '%(host)s' 123 port = %(port)d 124 slavename = '%(name)s' 125 passwd = '%(passwd)s' 126 keepalive = %(keepalive)d 127 usepty = %(usepty)d 128 umask = %(umask)s 129 maxdelay = %(maxdelay)d 130 131 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir, 132 keepalive, usepty, umask=umask, maxdelay=maxdelay) 133 s.setServiceParent(application) 134 135 """] 136
137 -def createSlave(config):
138 m = Maker(config) 139 m.mkdir() 140 m.chdir() 141 if config['relocatable']: 142 config['basedir'] = '.' 143 try: 144 master = config['master'] 145 port = None 146 host, port = re.search(r'^([^:]+)(?:[:](\d+))?', master).groups() 147 if port == None: 148 port = '9989' 149 config['host'] = host 150 config['port'] = int(port) 151 except: 152 print "unparseable master location '%s'" % master 153 print " expecting something more like localhost:8007 or localhost" 154 raise 155 156 if config['no-logrotate']: 157 slaveTAC = "".join([slaveTACTemplate[0]] + slaveTACTemplate[2:]) 158 else: 159 slaveTAC = "".join(slaveTACTemplate) 160 contents = slaveTAC % config 161 162 m.makeTAC(contents, secret=True) 163 m.mkinfo() 164 165 if not m.quiet: 166 print "buildslave configured in %s" % m.basedir
167 168 169
170 -def stop(config, signame="TERM", wait=False, returnFalseOnNotRunning=False):
171 import signal 172 basedir = config['basedir'] 173 quiet = config['quiet'] 174 175 if not isBuildslaveDir(config['basedir']): 176 print "not a buildslave directory" 177 sys.exit(1) 178 179 os.chdir(basedir) 180 try: 181 f = open("twistd.pid", "rt") 182 except: 183 if returnFalseOnNotRunning: 184 return False 185 if not quiet: print "buildslave not running." 186 sys.exit(0) 187 pid = int(f.read().strip()) 188 signum = getattr(signal, "SIG"+signame) 189 timer = 0 190 try: 191 os.kill(pid, signum) 192 except OSError, e: 193 if e.errno != 3: 194 raise 195 196 if not wait: 197 if not quiet: 198 print "sent SIG%s to process" % signame 199 return 200 time.sleep(0.1) 201 while timer < 10: 202 # poll once per second until twistd.pid goes away, up to 10 seconds 203 try: 204 os.kill(pid, 0) 205 except OSError: 206 if not quiet: 207 print "buildslave process %d is dead" % pid 208 return 209 timer += 1 210 time.sleep(1) 211 if not quiet: 212 print "never saw process go away"
213
214 -def restart(config):
215 basedir = config['basedir'] 216 quiet = config['quiet'] 217 218 if not isBuildslaveDir(config['basedir']): 219 print "not a buildslave directory" 220 sys.exit(1) 221 222 from buildslave.scripts.startup import start 223 if not stop(config, wait=True, returnFalseOnNotRunning=True): 224 if not quiet: 225 print "no old buildslave process found to stop" 226 if not quiet: 227 print "now restarting buildslave process.." 228 start(config)
229 230
231 -class MakerBase(usage.Options):
232 optFlags = [ 233 ['help', 'h', "Display this message"], 234 ["quiet", "q", "Do not emit the commands being run"], 235 ] 236 237 longdesc = """ 238 Operates upon the specified <basedir> (or the current directory, if not 239 specified). 240 """ 241 242 opt_h = usage.Options.opt_help 243
244 - def parseArgs(self, *args):
245 if len(args) > 0: 246 self['basedir'] = args[0] 247 else: 248 # Use the current directory if no basedir was specified. 249 self['basedir'] = os.getcwd() 250 if len(args) > 1: 251 raise usage.UsageError("I wasn't expecting so many arguments")
252
253 - def postOptions(self):
254 self['basedir'] = os.path.abspath(self['basedir'])
255
256 -class StartOptions(MakerBase):
257 optFlags = [ 258 ['quiet', 'q', "Don't display startup log messages"], 259 ]
260 - def getSynopsis(self):
261 return "Usage: buildslave start [<basedir>]"
262
263 -class StopOptions(MakerBase):
264 - def getSynopsis(self):
265 return "Usage: buildslave stop [<basedir>]"
266
267 -class RestartOptions(MakerBase):
268 optFlags = [ 269 ['quiet', 'q', "Don't display startup log messages"], 270 ]
271 - def getSynopsis(self):
272 return "Usage: buildslave restart [<basedir>]"
273
274 -class UpgradeSlaveOptions(MakerBase):
275 optFlags = [ 276 ] 277 optParameters = [ 278 ] 279
280 - def getSynopsis(self):
281 return "Usage: buildslave upgrade-slave [<basedir>]"
282 283 longdesc = """ 284 This command takes an existing buildslave working directory and 285 upgrades it to the current version. 286 """
287
288 -def upgradeSlave(config):
289 basedir = os.path.expanduser(config['basedir']) 290 buildbot_tac = open(os.path.join(basedir, "buildbot.tac")).read() 291 new_buildbot_tac = buildbot_tac.replace( 292 "from buildbot.slave.bot import BuildSlave", 293 "from buildslave.bot import BuildSlave") 294 if new_buildbot_tac != buildbot_tac: 295 open(os.path.join(basedir, "buildbot.tac"), "w").write(new_buildbot_tac) 296 print "buildbot.tac updated" 297 else: 298 print "No changes made" 299 300 return 0
301 302
303 -class SlaveOptions(MakerBase):
304 optFlags = [ 305 ["force", "f", "Re-use an existing directory"], 306 ["relocatable", "r", 307 "Create a relocatable buildbot.tac"], 308 ["no-logrotate", "n", 309 "Do not permit buildmaster rotate logs by itself"] 310 ] 311 optParameters = [ 312 ["keepalive", "k", 600, 313 "Interval at which keepalives should be sent (in seconds)"], 314 ["usepty", None, 0, 315 "(1 or 0) child processes should be run in a pty (default 0)"], 316 ["umask", None, "None", 317 "controls permissions of generated files. Use --umask=022 to be world-readable"], 318 ["maxdelay", None, 300, 319 "Maximum time between connection attempts"], 320 ["log-size", "s", "10000000", 321 "size at which to rotate twisted log files"], 322 ["log-count", "l", "10", 323 "limit the number of kept old twisted log files (None for unlimited)"], 324 ] 325 326 longdesc = """ 327 This command creates a buildslave working directory and buildbot.tac 328 file. The bot will use the <name> and <passwd> arguments to authenticate 329 itself when connecting to the master. All commands are run in a 330 build-specific subdirectory of <basedir>. <master> is a string of the 331 form 'hostname[:port]', and specifies where the buildmaster can be reached. 332 port defaults to 9989 333 334 The appropriate values for <name>, <passwd>, and <master> should be 335 provided to you by the buildmaster administrator. You must choose <basedir> 336 yourself. 337 """ 338
339 - def getSynopsis(self):
340 return "Usage: buildslave create-slave [options] <basedir> <master> <name> <passwd>"
341
342 - def parseArgs(self, *args):
343 if len(args) < 4: 344 raise usage.UsageError("command needs more arguments") 345 basedir, master, name, passwd = args 346 if master[:5] == "http:": 347 raise usage.UsageError("<master> is not a URL - do not use URL") 348 self['basedir'] = basedir 349 self['master'] = master 350 self['name'] = name 351 self['passwd'] = passwd
352
353 - def postOptions(self):
354 MakerBase.postOptions(self) 355 self['usepty'] = int(self['usepty']) 356 self['keepalive'] = int(self['keepalive']) 357 self['maxdelay'] = int(self['maxdelay']) 358 if not re.match('^\d+$', self['log-size']): 359 raise usage.UsageError("log-size parameter needs to be an int") 360 if not re.match('^\d+$', self['log-count']) and \ 361 self['log-count'] != 'None': 362 raise usage.UsageError("log-count parameter needs to be an int "+ 363 " or None")
364
365 -class Options(usage.Options):
366 synopsis = "Usage: buildslave <command> [command options]" 367 368 subCommands = [ 369 # the following are all admin commands 370 ['create-slave', None, SlaveOptions, 371 "Create and populate a directory for a new buildslave"], 372 ['upgrade-slave', None, UpgradeSlaveOptions, 373 "Upgrade an existing buildslave directory for the current version"], 374 ['start', None, StartOptions, "Start a buildslave"], 375 ['stop', None, StopOptions, "Stop a buildslave"], 376 ['restart', None, RestartOptions, 377 "Restart a buildslave"], 378 ] 379
380 - def opt_version(self):
381 import buildslave 382 print "Buildslave version: %s" % buildslave.version 383 usage.Options.opt_version(self)
384
385 - def opt_verbose(self):
386 from twisted.python import log 387 log.startLogging(sys.stderr)
388
389 - def postOptions(self):
390 if not hasattr(self, 'subOptions'): 391 raise usage.UsageError("must specify a command")
392 393
394 -def run():
395 config = Options() 396 try: 397 config.parseOptions() 398 except usage.error, e: 399 print "%s: %s" % (sys.argv[0], e) 400 print 401 c = getattr(config, 'subOptions', config) 402 print str(c) 403 sys.exit(1) 404 405 command = config.subCommand 406 so = config.subOptions 407 408 if command == "create-slave": 409 createSlave(so) 410 elif command == "upgrade-slave": 411 upgradeSlave(so) 412 elif command == "start": 413 if not isBuildslaveDir(so['basedir']): 414 print "not a buildslave directory" 415 sys.exit(1) 416 417 from buildslave.scripts.startup import start 418 start(so) 419 elif command == "stop": 420 stop(so, wait=True) 421 elif command == "restart": 422 restart(so) 423 sys.exit(0)
424