class Project: DESCR_FMT = """\ project %(name)s description %(descr)s cred %(user)s %(group)s hosts %(hosts)s """ CONF_FMT = """\ hook_up %(scriptdir)s/%(hostname)s_up hook_down %(scriptdir)s/%(hostname)s_down ip %(ip)s """ def __init__(self, name, *args, **kwargs): if name == '.': name = os.getcwd().split('/')[-1] self.name = name self.treepath = os.path.join(PROJECTROOT, self.name) #self.descrpath = os.path.join(self.treepath, PROJECTDESCR) #self.confpath = os.path.join(self.treepath, PROJECTCONF) self.descrpath = Path(self, PROJECTDESCR) self.confpath = Path(self, PROJECTCONF) self.confpathlist = [self.confpath.rel()] self.args = {} self.features = [] self.tree = TREE self.deployattrs = { 'ip': 'ip' in kwargs and kwargs['ip'] or utils.ifconfig_ip(), } self.hosts = [] self.fqdnpool = [] # used to keep track of used fqdn to # not make them overlap in different features # set to True whether you want to control project automatically, # i.e. reconfigure features for local host self.autofix = 'autofix' in kwargs and kwargs['autofix'] or False # set to False if load()/__init__() do not need to perform # additional stuff self.nocreate = 'nocreate' in kwargs and True or False self.svn = False self.loaded = self.load() if not 'create' in kwargs: # found an existing project or just requested to pass return else: # creating a new one self.description = kwargs['descr'] self.user = kwargs['user'] self.group = kwargs['group'] self.featurelist = kwargs['features'] self.hosts.append(HOSTNAME) def __str__(self): return 'project %s (%s)' % (self.name, self.description) def describe(self): return """ name = %s ip = %s descr = %s user = %s group = %s features = %s hosts = %s """ % (self.name, self.deployattrs['ip'], self.description, self.user, self.group, self.features, self.hosts) def _featureappend(self, feature, args=None, priority=-1, nodeploy=False): #if not feature.__class__ in features.FEATURES: # die('undefined feature') #if DEBUG: # print 'featureappend: %s %s' % (feature, args) #softlist = [ft.software for ft in self.features] #if feature.software in softlist: # die('feature %s: software %s is used by <%s>\n', feature, feature.software, # feature) nfeature = feature(project=self, fargs=args, nodeploy=nodeploy) if priority < 0: self.features.append(nfeature) else: self.features.insert(priority, nfeature) return nfeature def _writedescr(self): descr = open(self.descrpath.abs(), 'w') descr.write(Project.DESCR_FMT % { 'name': self.name, 'descr': self.description, 'user': self.user, 'group': self.group, 'hosts': ' '.join(self.hosts), #'features': ' '.join([fe.split()[0] for fe in felist]), }) descr.close() print '\n> %s' % self.descrpath print open(self.descrpath.abs()).read() def _writeconf(self, felist=[], hostname=HOSTNAME): conf = open(self.confpath.abs(), 'w') conf.write(Project.CONF_FMT % { 'hostname': hostname, 'scriptdir': self.getpath('scripts').rel(), 'ip': self.deployattrs['ip'], }) # add features to config for fe in felist: conf.write('%s\n' % fe) conf.close() print '\n> %s' % self.confpath.abs() print open(self.confpath.abs()).read() def getpath(self, node, trail=''): return Path(self, self.tree[node] + trail) def load(self): localhost_hooked = False # parse project description if not self.descrpath.isfile(): return False descr = open(self.descrpath.abs()).readlines() for line in descr: ltokens = line.split() if ltokens[0] == 'project': # some paranoia if self.name != ltokens[1]: die('load: names do not match (%s and %s)\n' % (self.name, ltokens[1])) else: self.args[ltokens[0]] = ltokens[1:] self.description = self.args['description'][0] self.user = self.args['cred'][0] self.group = self.args['cred'][1] self.hosts = self.args['hosts'] # find project config for current host self.confpathlist = [] for host in self.hosts: cfname = Path(self, PROJECTCONF_HOSTFMT % host) if cfname.isfile(): self.confpathlist.append(cfname.rel()) else: # die? print 'ERROR: config %s for host %s not found' % (cfname, host) self.deployattrs = {} confpath = self.confpath # if not found, schedule for reconfiguration if HOSTNAME in self.hosts and self.confpath.rel() in self.confpathlist: localhost_hooked = True if not localhost_hooked and self.autofix: print 'NOTE: project is not hooked to %s' % HOSTNAME print 'reconfiguring project' confpath = Path(self, self.confpathlist[0]) if self.nocreate and not localhost_hooked: #return True return False # if found, parse config for local host # if not, parse first available config for generation of local one conf = open(confpath.abs()).readlines() for line in conf: ltokens = line.split() if len(ltokens) < 1: continue # skip empties if ltokens[0] == 'ip': self.deployattrs['ip'] = ltokens[1] else: # import project args to args dict nodeploy = False if ltokens[0][0] == '!': nodeploy = True ltokens[0] = ltokens[0][1:] self.args[ltokens[0]] = ltokens[1:] # add feature to project features list if used for ft in features.FEATURES: if ft.fid == ltokens[0]: #self._featureappend(ft, ltokens[1:]) # XXX: feature has args in self.project.args on load() self._featureappend(ft, nodeploy=nodeploy) # reconfigure features for localhost if scheduled if not localhost_hooked and self.autofix: self.hosts.append(HOSTNAME) # rewrite project config self._writeconf(felist=[fe.create() for fe in self.features], hostname=HOSTNAME) # rewrite project description self._writedescr() # test if the project uses subversion if os.path.isdir(os.path.join(self.treepath, '.svn')): self.svn = True return True def create(self): if self.loaded: die('create: project already loaded/exists') if os.path.exists(self.treepath): die('create: project root exists on %s\n' % HOSTNAME) # create internal list of requested features for fe in self.featurelist: for ft in features.FEATURES: if ft.fid == fe[0]: self._featureappend(ft, fe[1:]) # resolve feature dependencies for fe in self.features: fe.depresolve() # create tree os.mkdir(self.treepath, 0775) for node in self.tree: os.mkdir(os.path.join(self.treepath, self.tree[node]), 0775) # check credentials existance on localhost if not self.group in [g[0] for g in grp.getgrall()]: cmd = '/usr/sbin/groupadd %s' % self.group utils.cmd(cmd) if not self.user in [p[0] for p in pwd.getpwall()]: cmd = '/usr/sbin/useradd -d %s -g %s %s' % (self.treepath, self.group, self.user) utils.cmd(cmd) # create features # create() should return config string, so keep them in a list felist = [fe.create() for fe in self.features] # create hook scripts hook_up = os.path.join(self.getpath('scripts').abs(), '%s_up' % HOSTNAME) open(hook_up, 'w').write('#!/bin/sh\n') os.chmod(hook_up, 0774) hook_down = os.path.join(self.getpath('scripts').abs(), '%s_down' % HOSTNAME) open(hook_down, 'w').write('#!/bin/sh\n') os.chmod(hook_down, 0774) # write project description self._writedescr() # write project config self._writeconf(felist) return def touch(self): utils.cmd('chown -R %s:%s %s' % (self.user, self.group, self.treepath)) utils.cmd('chmod -R g+rw %s' % self.treepath) return def delete(self): pass def deploy(self): self.deployctl = DeployCtl(self) if self.deployctl.deployed: return False for fe in self.features: fe.deploy() return self.deployctl.dump() def unlink(self): self.deployctl = DeployCtl(self) if not self.deployctl.deployed: return False for fe in self.features: fe.unlink() return self.deployctl.dump() @classmethod def svncheckout(cls, name): if Project(name, nocreate=True).loaded: print 'project %s exists' % name return False rc = utils.cmd('svn checkout %s/%s %s' % (SUBVERSION_PATH, name, os.path.join(PROJECTROOT, name)), system=True) if rc[0] != 0: die('svn error') nproj = Project(name, nocreate=True) if not nproj.loaded: print 'project %s does not seem like a valid one' % name return False assert self.svn == True return nproj def svnimport(self): if not self.loaded: die('%s not loaded (it does not exist?)' % name) rc = utils.cmd('svn import -m \'%s initial import\' %s %s/%s' % (self.name, self.treepath, SUBVERSION_PATH, self.name), system=True) if rc[0] != 0: die('svn error') rc = utils.cmd('svn checkout --force %s/%s %s' % (SUBVERSION_PATH, self.name, self.treepath), system=True) if rc[0] != 0: die('svn error') rc = utils.cmd('cd %s; svn propset svn:ignore * logs;' \ 'svn commit -m \'ignore logs\'' % self.treepath) #assert self.svn == True def featureadd(self, featuredesc): fe = None # test if we have similar feature for fe in self.features: if fe.cmp(featuredesc): return False # add it for ft in features.FEATURES: if ft.fid == featuredesc[0]: fe = self._featureappend(ft, featuredesc[1:]) break fe.depresolve() rc = fe.create() if not rc: return False # sync tree for node in self.tree: newpath = os.path.join(self.treepath, self.tree[node]) if not os.path.exists(newpath): os.mkdir(newpath, 0775) # update host config conf = open(self.confpath.abs(), 'a') conf.write('%s\n' % rc) conf.close() print '\n> %s' % self.confpath.abs() print open(self.confpath.abs()).read() return True def featuredrop(self, featuredesc): fe = None for fe in self.features: if fe.cmp(featuredesc): break if not fe: return False # drop tok = fe.delete() if not tok: return False # update host config conf = open(self.confpath.abs()).readlines() confc = conf for line in confc: if line.startswith(tok): conf.remove(line) open(self.confpath.abs(), 'w').write(''.join(conf)) print '\n> %s' % self.confpath.abs() print open(self.confpath.abs()).read() return True def up(self): if 'hook_up' in self.args: script = Path(self, self.args['hook_up'][0]) utils.cmd('sudo -u %s -H %s' % (self.user, script.abs()), system=True) def down(self): if 'hook_down' in self.args: script = Path(self, self.args['hook_down'][0]) utils.cmd('sudo -u %s -H %s' % (self.user, script.abs()), system=True)
def down(self): if 'hook_down' in self.args: script = Path(self, self.args['hook_down'][0]) utils.cmd('sudo -u %s -H %s' % (self.user, script.abs()), system=True)
def load(self): localhost_hooked = False # parse project description if not self.descrpath.isfile(): return False descr = open(self.descrpath.abs()).readlines() for line in descr: ltokens = line.split() if ltokens[0] == 'project': # some paranoia if self.name != ltokens[1]: die('load: names do not match (%s and %s)\n' % (self.name, ltokens[1])) else: self.args[ltokens[0]] = ltokens[1:] self.description = self.args['description'][0] self.user = self.args['cred'][0] self.group = self.args['cred'][1] self.hosts = self.args['hosts'] # find project config for current host self.confpathlist = [] for host in self.hosts: cfname = Path(self, PROJECTCONF_HOSTFMT % host) if cfname.isfile(): self.confpathlist.append(cfname.rel()) else: # die? print 'ERROR: config %s for host %s not found' % (cfname, host) self.deployattrs = {} confpath = self.confpath # if not found, schedule for reconfiguration if HOSTNAME in self.hosts and self.confpath.rel() in self.confpathlist: localhost_hooked = True if not localhost_hooked and self.autofix: print 'NOTE: project is not hooked to %s' % HOSTNAME print 'reconfiguring project' confpath = Path(self, self.confpathlist[0]) if self.nocreate and not localhost_hooked: #return True return False # if found, parse config for local host # if not, parse first available config for generation of local one conf = open(confpath.abs()).readlines() for line in conf: ltokens = line.split() if len(ltokens) < 1: continue # skip empties if ltokens[0] == 'ip': self.deployattrs['ip'] = ltokens[1] else: # import project args to args dict nodeploy = False if ltokens[0][0] == '!': nodeploy = True ltokens[0] = ltokens[0][1:] self.args[ltokens[0]] = ltokens[1:] # add feature to project features list if used for ft in features.FEATURES: if ft.fid == ltokens[0]: #self._featureappend(ft, ltokens[1:]) # XXX: feature has args in self.project.args on load() self._featureappend(ft, nodeploy=nodeploy) # reconfigure features for localhost if scheduled if not localhost_hooked and self.autofix: self.hosts.append(HOSTNAME) # rewrite project config self._writeconf(felist=[fe.create() for fe in self.features], hostname=HOSTNAME) # rewrite project description self._writedescr() # test if the project uses subversion if os.path.isdir(os.path.join(self.treepath, '.svn')): self.svn = True return True