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

Source Code for Module buildslave.scripts.runner

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