コード例 #1
0
ファイル: project.py プロジェクト: proger/project
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)
コード例 #2
0
ファイル: project.py プロジェクト: proger/project
    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