From 7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Fri, 6 Oct 2006 21:06:16 +0000 Subject: Merge of the client/refactor branch (revisions r2218-r2400) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@2401 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Client/Debian.py | 147 -------- src/lib/Client/Frame.py | 213 +++++++++++ src/lib/Client/Gentoo.py | 123 ------- src/lib/Client/Redhat.py | 172 --------- src/lib/Client/Solaris.py | 263 -------------- src/lib/Client/Tools/APT.py | 73 ++++ src/lib/Client/Tools/Blast.py | 22 ++ src/lib/Client/Tools/Chkconfig.py | 62 ++++ src/lib/Client/Tools/DebInit.py | 65 ++++ src/lib/Client/Tools/Encap.py | 60 +++ src/lib/Client/Tools/POSIX.py | 243 +++++++++++++ src/lib/Client/Tools/PostInstall.py | 21 ++ src/lib/Client/Tools/RPM.py | 82 +++++ src/lib/Client/Tools/SMF.py | 123 +++++++ src/lib/Client/Tools/SYSV.py | 83 +++++ src/lib/Client/Tools/__init__.py | 252 +++++++++++++ src/lib/Client/Toolset.py | 706 ------------------------------------ src/lib/Client/__init__.py | 2 +- src/lib/Server/Component.py | 5 +- src/lib/Server/Plugins/Crontab.py | 39 -- src/sbin/bcfg2 | 88 +---- 21 files changed, 1315 insertions(+), 1529 deletions(-) delete mode 100644 src/lib/Client/Debian.py create mode 100644 src/lib/Client/Frame.py delete mode 100644 src/lib/Client/Gentoo.py delete mode 100644 src/lib/Client/Redhat.py delete mode 100644 src/lib/Client/Solaris.py create mode 100644 src/lib/Client/Tools/APT.py create mode 100644 src/lib/Client/Tools/Blast.py create mode 100644 src/lib/Client/Tools/Chkconfig.py create mode 100644 src/lib/Client/Tools/DebInit.py create mode 100644 src/lib/Client/Tools/Encap.py create mode 100644 src/lib/Client/Tools/POSIX.py create mode 100644 src/lib/Client/Tools/PostInstall.py create mode 100644 src/lib/Client/Tools/RPM.py create mode 100644 src/lib/Client/Tools/SMF.py create mode 100644 src/lib/Client/Tools/SYSV.py create mode 100644 src/lib/Client/Tools/__init__.py delete mode 100644 src/lib/Client/Toolset.py delete mode 100644 src/lib/Server/Plugins/Crontab.py (limited to 'src') diff --git a/src/lib/Client/Debian.py b/src/lib/Client/Debian.py deleted file mode 100644 index b431f982d..000000000 --- a/src/lib/Client/Debian.py +++ /dev/null @@ -1,147 +0,0 @@ -'''This is the bcfg2 support for debian''' -__revision__ = '$Revision$' - -import apt_pkg, glob, os, re, sys, Bcfg2.Client.Toolset - -class ToolsetImpl(Bcfg2.Client.Toolset.Toolset): - '''The Debian toolset implements package and service operations and inherits - the rest from Toolset.Toolset''' - __name__ = 'Debian' - __important__ = ["/etc/apt/sources.list", "/var/cache/debconf/config.dat", \ - "/var/cache/debconf/templates.dat", '/etc/passwd', '/etc/group', \ - '/etc/apt/apt.conf', '/etc/dpkg/dpkg.cfg'] - pkgtool = {'deb':('DEBIAN_FRONTEND=noninteractive apt-get --reinstall -q=2 --force-yes -y install %s', - ('%s=%s', ['name', 'version']))} - svcre = re.compile("/etc/.*/[SK]\d\d(?P\S+)") - - def __init__(self, cfg, setup): - Bcfg2.Client.Toolset.Toolset.__init__(self, cfg, setup) - self.cfg = cfg - self.logger.debug('Configuring Debian toolset') - os.environ["DEBIAN_FRONTEND"] = 'noninteractive' - # dup /dev/null on top of stdin - #null = open('/dev/null', 'w+') - #os.dup2(null.fileno(), sys.__stdin__.fileno()) - if not self.setup['dryrun']: - if self.setup['kevlar']: - self.saferun("dpkg --force-confold --configure --pending") - self.saferun("apt-get clean") - self.saferun("apt-get -q=2 -y update") - self.installed = {} - self.pkgwork = {'add':[], 'update':[], 'remove':[]} - for pkg in [cpkg for cpkg in self.cfg.findall(".//Package") if not cpkg.attrib.has_key('type')]: - pkg.set('type', 'deb') - self.Refresh() - self.logger.debug('Done configuring Debian toolset') - - def Refresh(self): - '''Refresh memory hashes of packages''' - apt_pkg.init() - cache = apt_pkg.GetCache() - self.installed = {} - for pkg in cache.Packages: - if pkg.CurrentVer: - self.installed[pkg.Name] = pkg.CurrentVer.VerStr - - # implement entry (Verify|Install) ops - def VerifyService(self, entry): - '''Verify Service status for entry''' - rawfiles = glob.glob("/etc/rc*.d/*%s" % (entry.get('name'))) - files = [filename for filename in rawfiles if self.svcre.match(filename).group('name') == entry.get('name')] - if entry.get('status') == 'off': - if files: - entry.set('current_status', 'on') - return False - else: - return True - else: - if files: - return True - else: - entry.set('current_status', 'off') - return False - - def InstallService(self, entry): - '''Install Service for entry''' - cmdrc = 1 - self.logger.info("Installing Service %s" % (entry.get('name'))) - try: - os.stat('/etc/init.d/%s' % entry.get('name')) - except OSError: - self.logger.debug("Init script for service %s does not exist" % entry.get('name')) - return False - - if entry.attrib['status'] == 'off': - if self.setup['dryrun']: - self.logger.info("Disabling service %s" % (entry.get('name'))) - else: - self.saferun("/etc/init.d/%s stop" % (entry.get('name'))) - cmdrc = self.saferun("/usr/sbin/update-rc.d -f %s remove" % entry.get('name'))[0] - else: - if self.setup['dryrun']: - self.logger.info("Enabling service %s" % (entry.attrib['name'])) - else: - cmdrc = self.saferun("/usr/sbin/update-rc.d %s defaults" % (entry.attrib['name']))[0] - if cmdrc: - return False - return True - - def VerifyPackage(self, entry, modlist): - '''Verify package for entry''' - if not entry.attrib.has_key('version'): - self.logger.info("Cannot verify unversioned package %s" % - (entry.attrib['name'])) - return False - if self.installed.has_key(entry.attrib['name']): - if self.installed[entry.attrib['name']] == entry.attrib['version']: - if not self.setup['quick']: - output = self.saferun("/usr/bin/debsums -s %s" % entry.get('name'))[1] - if [filename for filename in output if filename not in modlist]: - return False - return True - else: - entry.set('current_version', self.installed[entry.get('name')]) - return False - entry.set('current_exists', 'false') - return False - - def Inventory(self): - '''Do standard inventory plus debian extra service check''' - Bcfg2.Client.Toolset.Toolset.Inventory(self) - allsrv = [] - [allsrv.append(self.svcre.match(fname).group('name')) for fname in - glob.glob("/etc/rc[12345].d/S*") if self.svcre.match(fname).group('name') not in allsrv] - self.logger.debug("Found active services:") - self.logger.debug(allsrv) - csrv = self.cfg.findall(".//Service") - [allsrv.remove(svc.get('name')) for svc in csrv if - svc.get('status') == 'on' and svc.get('name') in allsrv] - self.extra_services = allsrv - - def HandleExtra(self): - '''Deal with extra configuration detected''' - if self.setup['dryrun']: - return - - if len(self.pkgwork['remove']) > 0: - if self.setup['remove'] in ['all', 'packages']: - self.logger.info('Removing packages:') - self.logger.info(self.pkgwork['remove']) - if not self.saferun("apt-get remove -y --force-yes %s" % " ".join(self.pkgwork['remove']))[0]: - self.pkgwork['remove'] = [] - else: - if not self.setup['bundle']: - self.logger.info("Found extra packages:") - self.logger.info(self.pkgwork['remove']) - - if len(self.extra_services) > 0: - if self.setup['remove'] in ['all', 'services']: - self.logger.info('Removing services:') - self.logger.info(self.extra_services) - [self.extra_services.remove(serv) for serv in self.extra_services if - not self.saferun("rm -f /etc/rc*.d/S??%s" % serv)[0]] - else: - if not self.setup['bundle']: - self.logger.info('Found extra active services:') - self.logger.info(self.extra_services) - diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py new file mode 100644 index 000000000..dd1e98008 --- /dev/null +++ b/src/lib/Client/Frame.py @@ -0,0 +1,213 @@ +'''Frame is the Client Framework that verifies and installs entries, and generates statistics''' +__revision__ = '$Revision$' + +import logging, time +import Bcfg2.Client.Tools + +def promptFilter(prompt, entries): + '''Filter a supplied list based on user input''' + ret = [] + for entry in [entry for entry in entries]: + try: + if raw_input(prompt % (entry.tag, entry.get('name'))) in ['y', 'Y']: + ret.append(entry) + except: + continue + return ret + +class Frame: + '''Frame is the container for all Tool objects and state information''' + def __init__(self, config, setup, times): + self.config = config + self.times = times + self.times['initialization'] = time.time() + self.setup = setup + self.tools = [] + self.states = {} + self.whitelist = [] + self.removal = [] + self.logger = logging.getLogger("Bcfg2.Client.Frame") + for tool in Bcfg2.Client.Tools.__all__[:]: + try: + tool_class = "Bcfg2.Client.Tools.%s" % tool + mod = __import__(tool_class, globals(), locals(), ['*']) + except ImportError: + print "Failed to import module %s" % (tool) + continue + + try: + self.tools.append(getattr(mod, tool)(self.logger, setup, config, self.states)) + except Bcfg2.Client.Tools.toolInstantiationError: + continue + except: + self.logger.error("Failed to instantiate tool %s" % (tool), exc_info=1) + self.logger.info("Loaded tool drivers:") + self.logger.info([tool.__name__ for tool in self.tools]) + if not self.setup['dryrun']: + for cfile in [cfl for cfl in config.findall(".//ConfigFile") \ + if cfl.get('name') in self.__important__]: + self.VerifyEntry(cfile) + if not self.states[cfile]: + self.InstallConfigFile(cfile) + # find entries not handled by any tools + problems = [entry for struct in config for entry in struct if entry not in self.handled] + if problems: + self.logger.error("The following entries are not handled by any tool:") + self.logger.error(["%s:%s:%s" % (entry.tag, entry.get('type'), \ + entry.get('name')) for entry in problems]) + self.logger.error("") + + def __getattr__(self, name): + if name in ['extra', 'handled', 'modified', '__important__']: + ret = [] + for tool in self.tools: + ret += getattr(tool, name) + return ret + elif self.__dict__.has_key(name): + return self.__dict__[name] + raise AttributeError, name + + def Inventory(self): + '''Verify all entries, find extra entries, and build up workqueues''' + # initialize all states + for struct in self.config.getchildren(): + for entry in struct.getchildren(): + self.states[entry] = False + for tool in self.tools: + try: + tool.Inventory() + except: + self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1) + + def Decide(self): + '''Set self.whitelist based on user interaction''' + prompt = "Would you like to install %s: %s? (y/N): " + rprompt = "Would you like to remove %s: %s? (y/N): " + if self.setup['dryrun']: + self.logger.info("In dryrun mode: suppressing entry installation for:") + self.logger.info(["%s:%s" % (entry.tag, entry.get('name')) for entry \ + in self.states if not self.states[entry]]) + return + elif self.setup['interactive']: + self.whitelist = promptFilter(prompt, [entry for entry in self.states \ + if not self.states[entry]]) + self.removal = promptFilter(rprompt, self.extra) + elif self.setup['bundle']: + # only install entries in specified bundle + mbs = [bund for bund in self.config.findall('./Bundle') \ + if bund.get('name') == self.setup['bundle']] + if not mbs: + self.logger.error("Could not find bundle %s" % (self.setup['bundle'])) + return + self.whitelist = [entry for entry in self.states if not self.states[entry] \ + and entry in mbs[0].getchildren()] + else: + # all systems are go + self.whitelist = [entry for entry in self.states if not self.states[entry]] + if self.setup['remove']: + if self.setup['remove'] == 'all': + self.removal = self.extra + elif self.setup['remove'] == 'services': + self.removal = [entry for entry in self.extra if entry.tag == 'Service'] + elif self.setup['remove'] == 'packages': + self.removal = [entry for entry in self.extra if entry.tag == 'Package'] + + def DispatchInstallCalls(self, entries): + '''Dispatch install calls to underlying tools''' + for tool in self.tools: + handled = [entry for entry in entries if tool.canInstall(entry)] + if not handled: + continue + try: + tool.Install(handled) + except: + self.logger.error("%s.Install() call failed:" % tool.__name__, exc_info=1) + + def Install(self): + '''Install all entries''' + self.DispatchInstallCalls(self.whitelist) + if self.modified: + # Handle Bundle interdeps + mods = self.modified + mbundles = [struct for struct in self.config if struct.tag == 'Bundle' and \ + [mod for mod in mods if mod in struct]] + self.logger.info("The Following Bundles have been modifed:") + self.logger.info([mbun.get('name') for mbun in mbundles]) + self.logger.info("") + tbm = [(t, b) for t in self.tools for b in mbundles] + for tool, bundle in tbm: + try: + tool.Inventory(bundle) + except: + self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1) + clobbered = [entry for bundle in mbundles for entry in bundle \ + if not self.states[entry]] + if not self.setup['interactive']: + self.DispatchInstallCalls(clobbered) + for tool, bundle in tbm: + try: + tool.BundleUpdated(bundle) + except: + self.logger.error("%s.BundleUpdated() call failed:" % (tool.__name__), exc_info=1) + + def Remove(self): + '''Remove extra entries''' + for tool in self.tools: + extras = [entry for entry in self.removal if tool.canInstall(entry)] + if extras: + try: + tool.Remove(extras) + except: + self.logger.error("%s.Remove() failed" % tool.__name__, exc_info=1) + + def CondDisplayState(self, phase): + '''Conditionally print tracing information''' + self.logger.info('\nPhase: %s' % phase) + self.logger.info('Correct entries:\t%d' % self.states.values().count(True)) + self.logger.info('Incorrect entries:\t%d' % self.states.values().count(False)) + self.logger.info('Total managed entries:\t%d' % len(self.states.values())) + self.logger.info('Unmanaged entries:\t%d' % len(self.extra)) + self.logger.info("") + + if ((self.states.values().count(False) == 0) and not self.extra): + self.logger.info('All entries correct.') + + def Execute(self): + '''Run all methods''' + self.Inventory() + self.times['inventory'] = time.time() + self.CondDisplayState('initial') + self.Decide() + self.Install() + self.times['install'] = time.time() + self.Remove() + self.times['finished'] = time.time() + self.CondDisplayState('final') + + def GenerateStats(self): + '''Generate XML summary of execution statistics''' + feedback = Bcfg2.Client.XML.Element("upload-statistics") + stats = Bcfg2.Client.XML.SubElement(feedback, \ + 'Statistics', total=str(len(self.states)), + client_version=__revision__, version='2.0', + revision=self.config.get('revision', '-1')) + good = len([key for key, val in self.states.iteritems() if val]) + stats.set('good', str(good)) + if len([key for key, val in self.states.iteritems() if not val]) == 0: + stats.set('state', 'clean') + else: + stats.set('state', 'dirty') + + # List bad elements of the configuration + for (data, ename) in [(self.modified, 'Modified'), (self.extra, "Extra"), \ + ([entry for entry in self.states if not \ + self.states[entry]], "Bad")]: + container = Bcfg2.Client.XML.SubElement(stats, ename) + [container.append(item) for item in data] + + timeinfo = Bcfg2.Client.XML.Element("OpStamps") + feedback.append(stats) + for (event, timestamp) in self.times.iteritems(): + timeinfo.set(event, str(timestamp)) + stats.append(timeinfo) + return feedback diff --git a/src/lib/Client/Gentoo.py b/src/lib/Client/Gentoo.py deleted file mode 100644 index 99390f19e..000000000 --- a/src/lib/Client/Gentoo.py +++ /dev/null @@ -1,123 +0,0 @@ -'''This provides bcfg2 support for Gentoo''' -__revision__ = '$Revision$' - -import glob, os, re -from Bcfg2.Client.Toolset import Toolset - -class Gentoo(Toolset): - '''This class implelements support for Gentoo binary packages and standard /etc/init.d services''' - pkgtool = {'emerge':("/usr/bin/emerge --quiet --nospinner --usepkg --getbinpkg %s", ("%s-%s", ['name', 'version']))} - - def __init__(self, cfg, setup): - Toolset.__init__(self, cfg, setup) - self.cfg = cfg - self.pkgwork = {'add':[], 'update':[], 'remove':[]} - self.installed = {} - self.extra_services = [] - self.Refresh() - self.saferun("emerge sync") - - def Refresh(self): - '''Refresh memory hashes of packages''' - self.installed = {} - - splitter = re.compile('([\w\-\+]+)-([\d].*)') - - # Build list of packages - instp = [splitter.match(fname.split('/')[-1].replace('.ebuild','')).groups() - for fname in glob.glob('/var/db/pkg/*/*/*.ebuild')] - for info in instp: - self.installed["%s-%s" % info] = info[1] - - def VerifyService(self, entry): - '''Verify Service status for entry''' - runlevels = self.saferun("/bin/rc-status --list")[1] - try: - crl = self.saferun("/sbin/rc-update show | grep %s" % entry.attrib['name'])[1][0].split()[2:] - except IndexError: - # Ocurrs when no lines are returned (service not installed) - return False - - if entry.get('status') == 'off': - return len(crl) == 0 - elif entry.get('status') == 'on': - return len(crl) > 0 - return False - - def InstallService(self, entry): - '''Install Service entry''' - - self.logger.info("Installing Service %s" % (entry.get('name'))) - try: - os.stat('/etc/init.d/%s' % entry.get('name')) - except OSError: - self.logger.debug("Init script for service %s does not exist" % entry.get('name')) - return False - - if entry.attrib['status'] == 'off': - if self.setup['dryrun']: - self.logger.info("Disabling Service %s" % (entry.get('name'))) - else: - cmdrc = self.saferun("/sbin/rc-update del %s %s" % (entry.attrib['name'], entry.attrib['runlevels']))[0] - else: - if self.setup['dryrun']: - self.logger.info("Enabling Service %s" % (entry.attrib['name'])) - else: - cmdrc = self.saferun("/sbin/rc-update add %s %s" % (entry.attrib['name'], entry.attrib['runlevels']))[0] - if cmdrc: - return False - return True - - def VerifyPackage(self, entry, modlist): - '''Verify Package status for entry''' - if not (entry.get('name') and entry.get('version')): - self.logger.error("Can't verify package, not enough data.") - return False - - installed_package = self.saferun("/usr/bin/qpkg --no-color --installed --verbose %s-%s" % - (entry.get('name'), entry.get('version')))[1] - if installed_package: - installed_package = installed_package[0].strip("\n").split('/')[-1] - if installed_package != "%s-%s" % (entry.get('name'), entry.get('version')): - self.logger.debug("Package %s-%s version incorrect" % (entry.get('name'), entry.get('version'))) - if entry.attrib.get('verify', 'true') == 'true': - if self.setup['quick']: - return True - output = self.saferun("/usr/bin/qpkg --no-color --check %s-%s" % - (entry.get('name'), entry.get('version')))[1] - differences = output[-1] - - if re.match("^0/", differences): - return True - else: - for line in output[1:-1]: - if line.split()[0] not in modlist: - self.logger.debug("Package %s content verification failed" % (entry.get('name'))) - return False - return True - return False - - def Inventory(self): - '''Do standard inventory plus debian extra service check''' - Toolset.Inventory(self) - allsrv = [ srv.split('/')[-1] for srv in glob.glob('/etc/init.d/*')] - csrv = self.cfg.findall(".//Service") - [allsrv.remove(svc.get('name')) for svc in csrv if svc.get('status') == 'on' and svc.get('name') in allsrv] - self.extra_services = allsrv - - def HandleExtra(self): - '''Deal with extra configuration detected''' - if len(self.pkgwork) > 0: - if self.setup['remove'] in ['all', 'packages']: - self.logger.info("Removing packages: %s" % (self.pkgwork['remove'])) - cmd = "/usr/bin/emerge --quiet --nospinner unmerge %s" % " ".join(self.pkgwork['remove']) - self.saferun(cmd) - else: - self.logger.info("Need to remove packages: %s" % (self.pkgwork['remove'])) - if len(self.extra_services) > 0: - if self.setup['remove'] in ['all', 'services']: - self.logger.info("Removing services: %s" % (self.extra_services)) - for service in self.extra_services: - self.saferun("/sbin/rc-update del %s" % service) - else: - self.logger.info("Need to remove services: %s" % (self.extra_services)) diff --git a/src/lib/Client/Redhat.py b/src/lib/Client/Redhat.py deleted file mode 100644 index c99ed028a..000000000 --- a/src/lib/Client/Redhat.py +++ /dev/null @@ -1,172 +0,0 @@ -# This is the bcfg2 support for redhat -# $Id$ - -'''This is redhat client support''' -__revision__ = '$Revision$' -import time -from Bcfg2.Client.Toolset import Toolset - -class ToolsetImpl(Toolset): - '''This class implelements support for rpm packages and standard chkconfig services''' - __name__ = 'Redhat' - pkgtool = {'rpm':("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"]))} - - def __init__(self, cfg, setup): - Toolset.__init__(self, cfg, setup) - self.pkgwork = {'add':[], 'update':[], 'remove':[]} - self.Refresh() - for pkg in [cpkg for cpkg in self.cfg.findall(".//Package") if not cpkg.attrib.has_key('type')]: - pkg.set('type', 'rpm') - for srv in [csrv for csrv in self.cfg.findall(".//Service") if not csrv.attrib.has_key('type')]: - srv.set('type', 'chkconfig') - # relocation hack: we will define one pkgtool per relocation location - for pkg in [cpkg for cpkg in self.cfg.findall('.//Package') if cpkg.attrib.has_key('reloc')]: - ptoolname = "rpm-reloc-%s" % (pkg.get('reloc')) - if not self.pkgtool.has_key(ptoolname): - cmd = "rpm --relocate %s --oldpackage --replacepkgs --quiet -U %%s" % (pkg.get('reloc')) - self.pkgtool[ptoolname] = (cmd, ("%s", ["url"])) - pkg.set('type', ptoolname) - - - def Refresh(self): - '''Refresh memory hashes of packages''' - self.installed = {} - for line in self.saferun("rpm -qa --qf '%{NAME} %{VERSION}-%{RELEASE}\n'")[1]: - try: - (name, version) = line.split() - self.installed[name] = version - except ValueError: - self.logger.error("RPM database already in use; waiting 30 seconds and retrying") - time.sleep(30) - return self.Refresh() - - def VerifyService(self, entry): - '''Verify Service status for entry''' - try: - srvdata = self.saferun('/sbin/chkconfig --list %s | grep -v "unknown service"' - % entry.attrib['name'])[1][0].split() - except IndexError: - # Ocurrs when no lines are returned (service not installed) - entry.set('current_status', 'off') - return False - if entry.attrib['type'] == 'xinetd': - return entry.attrib['status'] == srvdata[1] - - try: - onlevels = [level.split(':')[0] for level in srvdata[1:] if level.split(':')[1] == 'on'] - except IndexError: - onlevels = [] - - # chkconfig/init.d service - if entry.get('status') == 'on': - status = len(onlevels) > 0 - else: - status = len(onlevels) == 0 - - if not status: - if entry.get('status') == 'on': - entry.set('current_status', 'off') - else: - entry.set('current_status', 'on') - return status - - def InstallService(self, entry): - '''Install Service entry''' - self.saferun("/sbin/chkconfig --add %s"%(entry.attrib['name'])) - self.logger.info("Installing Service %s" % (entry.get('name'))) - if not entry.get('status'): - self.logger.error("Can't install service %s, not enough data" % (entry.get('name'))) - return False - if entry.attrib['status'] == 'off': - if self.setup['dryrun']: - self.logger.info("Disabling server %s" % (entry.get('name'))) - else: - cmdrc = self.saferun("/sbin/chkconfig %s %s" % (entry.attrib['name'], - entry.attrib['status']))[0] - else: - if self.setup['dryrun']: - self.logger.info("Enabling server %s" % (entry.get('name'))) - else: - cmdrc = self.saferun("/sbin/chkconfig %s %s" % - (entry.attrib['name'], entry.attrib['status']))[0] - return cmdrc == 0 - - def VerifyPackage(self, entry, modlist): - '''Verify Package status for entry''' - if not entry.get('version'): - self.logger.error("Can't install package %s, not enough data." % (entry.get('name'))) - return False - rpm_options = [] - if entry.get('verify', 'false') == 'nomtime': - self.logger.debug("Skipping mtime verification for package %s" % (entry.get('name'))) - rpm_options.append("--nomtime") - if self.installed.has_key(entry.get('name')): - if entry.get('version') == self.installed[entry.get('name')]: - if entry.get('multiarch'): - archs = entry.get('multiarch').split() - info = self.saferun('rpm -q %s --qf "%%{NAME} %%{VERSION}-%%{RELEASE} %%{ARCH}\n"' % (entry.get('name')))[1] - while info: - (_, vers, arch) = info.pop().split() - if arch in archs: - archs.remove(arch) - else: - self.logger.error("Got pkg install for Package %s: arch %s" % (entry.get('name'), arch)) - return False - if archs: - self.logger.error("Package %s not installed for arch: %s" % (entry.get('name'), archs)) - return False - if (self.setup['quick'] or (entry.get('verify', 'true') == 'false')) or entry.get('multiarch'): - if entry.get('verify', 'true') == 'false': - self.logger.debug("Skipping checksum verification for package %s" % (entry.get('name'))) - return True - else: - self.logger.debug("Package %s: wrong version installed. want %s have %s" % - (entry.get('name'), entry.get('version'), self.installed[entry.get('name')])) - entry.set('current_version', self.installed[entry.get('name')]) - return False - else: - self.logger.debug("Package %s: not installed" % (entry.get('name'))) - entry.set('current_exists', 'false') - return False - - (vstat, output) = self.saferun("rpm --verify -q %s %s-%s" % (" ".join(rpm_options), entry.get('name'), entry.get('version'))) - if vstat != 0: - if [name for name in output if name.split()[-1] not in modlist]: - self.logger.debug("Package %s content verification failed" % entry.get('name')) - return False - return True - - def HandleExtra(self): - '''Deal with extra configuration detected''' - if len(self.pkgwork['remove']) > 0: - if self.setup['remove'] in ['all', 'packages']: - self.logger.info("Removing packages: %s" % self.pkgwork['remove']) - if not self.saferun("rpm --quiet -e --allmatches %s" % " ".join(self.pkgwork['remove']))[0]: - self.pkgwork['remove'] = [] - self.Refresh() - self.Inventory() - else: - self.logger.info("Found extra packages:") - self.logger.info(self.pkgwork['remove']) - if len(self.extra_services) > 0: - if self.setup['remove'] in ['all', 'services']: - self.logger.info('Removing services:') - self.logger.info(self.extra_services) - for service in self.extra_services: - if not self.saferun("/sbin/chkconfig --level 123456 %s off" % service)[0]: - self.extra_services.remove(service) - self.logger.info("Failed to remove service %s" % (service)) - else: - self.logger.info('Found extra active services:') - self.logger.info(self.extra_services) - - def Inventory(self): - '''Do standard inventory plus debian extra service check''' - Toolset.Inventory(self) - allsrv = [line.split()[0] for line in self.saferun("/sbin/chkconfig --list|grep :on")[1]] - self.logger.debug('Found active services:') - self.logger.debug(allsrv) - csrv = self.cfg.findall(".//Service") - [allsrv.remove(svc.get('name')) for svc in csrv if - svc.get('status') == 'on' and svc.get('name') in allsrv] - self.extra_services = allsrv diff --git a/src/lib/Client/Solaris.py b/src/lib/Client/Solaris.py deleted file mode 100644 index ab60a0a04..000000000 --- a/src/lib/Client/Solaris.py +++ /dev/null @@ -1,263 +0,0 @@ -# This is the bcfg2 support for solaris -'''This provides bcfg2 support for Solaris''' -__revision__ = '$Revision$' - -import os -from glob import glob -from os import stat, unlink -from re import compile as regcompile -from tempfile import mktemp - -from Bcfg2.Client.Toolset import Toolset - -noask = ''' -mail= -instance=overwrite -partial=nocheck -runlevel=nocheck -idepend=nocheck -rdepend=nocheck -space=ask -setuid=nocheck -conflict=nocheck -action=nocheck -basedir=default -''' - -class ToolsetImpl(Toolset): - '''This class implelements support for SYSV/blastware/encap packages - and standard SMF services''' - pkgtool = {'sysv':("/usr/sbin/pkgadd %s -d %%s -n %%%%s", (("%s", ["name"]))), - 'blast':("/opt/csw/bin/pkg-get install %s", ("%s", ["name"])), - 'encap':("/local/sbin/epkg -l -f -q %s", ("%s", ["url"]))} - splitter = regcompile('.*/(?P[\w-]+)\-(?P[\w\.-]+)') - ptypes = {} - __name__ = 'Solaris' - - def __init__(self, cfg, setup): - Toolset.__init__(self, cfg, setup) - self.extra_services = [] - self.snames = {} - self.noaskname = mktemp() - try: - open(self.noaskname, 'w+').write(noask) - self.pkgtool['sysv'] = (self.pkgtool['sysv'][0] % ("-a %s" % (self.noaskname)), self.pkgtool['sysv'][1]) - except: - self.pkgtool['sysv'] = (self.pkgtool['sysv'][0] % (""), self.pkgtool['sysv'][1]) - try: - stat("/opt/csw/bin/pkg-get") - self.saferun("/opt/csw/bin/pkg-get -U > /dev/null") - except OSError: - pass - self.Refresh() - for pkg in [cpkg for cpkg in self.cfg.findall(".//Package") if not cpkg.attrib.has_key('type')]: - pkg.set('type', 'sysv') - for pkg in [cpkg for cpkg in self.cfg.findall(".//Package") if cpkg.get('type') == 'sysv']: - if pkg.attrib.has_key('url'): - pkg.set('url', '/'.join(pkg.get('url').split('/')[:-1])) - pkg.set('type', "sysv-%s" % (pkg.get('url'))) - if not self.pkgtool.has_key(pkg.get('type')): - self.pkgtool[pkg.get('type')] = (self.pkgtool['sysv'][0] % (pkg.get('url')), - self.pkgtool['sysv'][1]) - - def Refresh(self): - '''Refresh memory hashes of packages''' - self.installed = {} - self.ptypes = {} - # Build list of packages - lines = self.saferun("/usr/bin/pkginfo -x")[1] - while lines: - version = lines.pop().split()[1] - pkg = lines.pop().split()[0] - self.installed[pkg] = version - self.ptypes[pkg] = 'sysv' - # try to find encap packages - for pkg in glob("/local/encap/*"): - match = self.splitter.match(pkg) - if match: - self.installed[match.group('name')] = match.group('version') - self.ptypes[match.group('name')] = 'encap' - else: - print "Failed to split name %s" % pkg - #print self.installed.keys() - - def VerifyService(self, entry): - '''Verify Service status for entry''' - if not entry.attrib.has_key('FMRI'): - name = self.saferun("/usr/bin/svcs -H -o FMRI %s 2>/dev/null" % entry.get('name'))[1] - if name: - entry.set('FMRI', name[0]) - else: - self.logger.info('Failed to locate FMRI for service %s' % entry.get('name')) - return False - if entry.get('FMRI').startswith('lrc'): - filename = entry.get('FMRI').split('/')[-1] - # this is a legacy service - gname = "/etc/rc*.d/%s" % filename - files = glob(gname.replace('_', '.')) - if files: - self.logger.debug("Matched %s with %s" % \ - (entry.get("FMRI"), ":".join(files))) - return entry.get('status') == 'on' - else: - self.logger.debug("No service matching %s" % (entry.get("FMRI"))) - return entry.get('status') == 'off' - try: - srvdata = self.saferun("/usr/bin/svcs -H -o STA %s" % entry.attrib['name'])[1][0].split() - except IndexError: - # Ocurrs when no lines are returned (service not installed) - return False - - if entry.get('status') == 'on': - return srvdata[0] == 'ON' - else: - return srvdata[0] in ['OFF', 'UN', 'MNT', 'DIS', 'DGD'] - - def InstallService(self, entry): - '''Install Service entry''' - if not entry.attrib.has_key('status'): - self.logger.info('Insufficient information for Service %s; cannot Install' % entry.get('name')) - return False - if not entry.attrib.has_key('FMRI'): - name = self.saferun("/usr/bin/svcs -H -o FMRI %s 2>/dev/null" % entry.get('name'))[1] - if name: - entry.set('FMRI', name[0]) - else: - self.logger.info('Failed to locate FMRI for service %s' % entry.get('name')) - return False - self.logger.info("Installing Service %s" % (entry.get('name'))) - if entry.attrib['status'] == 'off': - if self.setup['dryrun']: - print "Disabling Service %s" % (entry.get('name')) - else: - if entry.get("FMRI").startswith('lrc'): - try: - loc = entry.get("FMRI")[4:].replace('_', '.') - self.logger.debug("Renaming file %s to %s" % \ - (loc, loc.replace('/S', '/DISABLED.S'))) - os.rename(loc, loc.replace('/S', '/DISABLED.S')) - return True - except OSError: - self.logger.error("Failed to rename init script %s" % (loc)) - return False - cmdrc = self.saferun("/usr/sbin/svcadm disable -r %s" % (entry.attrib['FMRI']))[0] - else: - if self.setup['dryrun']: - print "Enabling Service %s" % (entry.attrib['name']) - else: - if entry.get('FMRI').startswith('lrc'): - loc = entry.get("FMRI")[4:].replace('_', ',') - try: - stat(loc.replace('/S', '/Disabled.')) - self.logger.debug("Renaming file %s to %s" % \ - (loc.replace('/S', '/DISABLED.S'), loc)) - os.rename(loc.replace('/S', '/DISABLED.S'), loc) - cmdrc = 0 - except: - self.logger.debug("Failed to rename %s to %s" \ - % (loc.replace('/S', '/DISABLED.S'), loc)) - cmdrc = 1 - else: - cmdrc = self.saferun("/usr/sbin/svcadm enable -r %s" % (entry.attrib['FMRI']))[0] - return cmdrc == 0 - - def VerifyPackage(self, entry, modlist): - '''Verify Package status for entry''' - if not entry.get('version'): - self.logger.info("Insufficient information of Package %s; cannot Verify" % entry.get('name')) - return False - if entry.get('type') in ['sysv', 'blast'] or entry.get('type')[:4] == 'sysv': - cmdrc = self.saferun("/usr/bin/pkginfo -q -v \"%s\" %s" % (entry.get('version'), entry.get('name')))[0] - elif entry.get('type') in ['encap']: - cmdrc = self.saferun("/local/sbin/epkg -q -S -k %s-%s >/dev/null" % - (entry.get('name'), entry.get('version')))[0] - if cmdrc != 0: - self.logger.debug("Package %s version incorrect" % entry.get('name')) - else: - if self.setup['quick'] or entry.get('type') == 'encap' or \ - entry.attrib.get('verify', 'true') == 'false': - return True - (vstat, odata) = self.saferun("/usr/sbin/pkgchk -n %s" % (entry.get('name'))) - if vstat == 0: - return True - else: - output = [line for line in odata if line[:5] == 'ERROR'] - if len([name for name in output if name.split()[-1] not in modlist]): - self.logger.debug("Package %s content verification failed" % \ - (entry.get('name'))) - else: - return True - return False - - def Inventory(self): - '''Do standard inventory plus debian extra service check''' - Toolset.Inventory(self) - allsrv = [name for name, version in [ srvc.strip().split() for srvc in - self.saferun("/usr/bin/svcs -a -H -o FMRI,STATE")[1] ] - if version != 'disabled'] - csrv = self.cfg.findall(".//Service") - # need to build a service name map. services map to a fullname if they are already installed - for srv in csrv: - name = self.saferun("/usr/bin/svcs -H -o FMRI %s 2>/dev/null" % srv.get('name'))[1] - if name: - srv.set('FMRI', name[0]) - else: - self.logger.info("Failed to locate FMRI for service %s" % srv.get('name')) - #nsrv = [ r for r in [ popen("/usr/bin/svcs -H -o FMRI %s " % s).read().strip() for s in csrv ] if r ] - [allsrv.remove(svc.get('FMRI')) for svc in csrv if - svc.get('status') == 'on' and svc.get("FMRI") in allsrv] - self.extra_services = allsrv - - def HandleExtra(self): - '''Deal with extra configuration detected''' - if len(self.pkgwork) > 0: - if self.setup['remove'] in ['all', 'packages']: - self.logger.info("Removing packages: %s" % (self.pkgwork['remove'])) - sysvrmpkgs = [pkg for pkg in self.pkgwork['remove'] if self.ptypes[pkg] == 'sysv'] - enrmpkgs = [pkg for pkg in self.pkgwork['remove'] if self.ptypes[pkg] == 'encap'] - if sysvrmpkgs: - if not self.saferun("/usr/sbin/pkgrm -a %s -n %s" % \ - (self.noaskname, " ".join(sysvrmpkgs)))[0]: - [self.pkgwork['remove'].remove(pkg) for pkg in sysvrmpkgs] - if enrmpkgs: - if not self.saferun("/local/sbin/epkg -l -q -r %s" % " ".join(enrmpkgs))[0]: - [self.pkgwork['remove'].remove(pkg) for pkg in enrmpkgs] - else: - self.logger.info("Need to remove packages: %s" % (self.pkgwork['remove'])) - if len(self.extra_services) > 0: - self.logger.info("Here") - self.logger.info('removal mode is: %s' % (self.setup)) - if self.setup['remove'] in ['all', 'services']: - self.logger.info("Removing services: %s" % (self.extra_services)) - for service in [svc for svc in self.extra_services if not svc.startswith('lrc:')]: - if not self.saferun("/usr/sbin/svcadm disable %s" % service)[0]: - self.extra_services.remove(service) - for svc in [svc for svc in self.extra_services if svc.startswith('lrc:')]: - loc = svc[4:].replace('_', '.') - self.logger.info("Renaming file %s to %s" % \ - (loc, loc.replace('/S', '/DISABLED.S'))) - try: - os.rename(loc, loc.replace('/S', '/DISABLED.S')) - self.extra_services.remove(svc) - except OSError: - self.logger.error("Failed to rename %s" % loc) - - else: - self.logger.info("Need to remove services: %s" % (self.extra_services)) - - def Install(self): - '''Local install method handling noaskfiles''' - Toolset.Install(self) - try: - unlink(self.noaskname) - except: - pass - - def RestartService(self, service): - '''Restart a service''' - if service.get("FMRI").startswith('lrc'): - Toolset.RestartService(self, service) - else: - if service.get('status') == 'on': - self.logger.debug("Restarting service %s" % (service.get("FMRI"))) - self.saferun("svcadm restart %s" % (service.get("FMRI"))) diff --git a/src/lib/Client/Tools/APT.py b/src/lib/Client/Tools/APT.py new file mode 100644 index 000000000..4c049310a --- /dev/null +++ b/src/lib/Client/Tools/APT.py @@ -0,0 +1,73 @@ +'''This is the bcfg2 support for apt-get''' +__revision__ = '$Revision$' + +import apt_pkg, os, re +import Bcfg2.Client.Tools + +class APT(Bcfg2.Client.Tools.PkgTool): + '''The Debian toolset implements package and service operations and inherits + the rest from Toolset.Toolset''' + __name__ = 'APT' + __execs__ = ['/usr/bin/debsums', '/usr/bin/apt-get', '/usr/bin/dpkg'] + __important__ = ["/etc/apt/sources.list", "/var/cache/debconf/config.dat", \ + "/var/cache/debconf/templates.dat", '/etc/passwd', '/etc/group', \ + '/etc/apt/apt.conf', '/etc/dpkg/dpkg.cfg'] + __handles__ = [('Package', 'deb')] + __req__ = {'Package': ['name', 'version']} + pkgtype = 'deb' + pkgtool = ('apt-get --reinstall -q=2 --force-yes -y install %s', + ('%s=%s', ['name', 'version'])) + + svcre = re.compile("/etc/.*/[SK]\d\d(?P\S+)") + + def __init__(self, logger, cfg, setup, states): + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, cfg, setup, states) + self.cfg = cfg + os.environ["DEBIAN_FRONTEND"] = 'noninteractive' + if not self.setup['dryrun']: + if self.setup['kevlar']: + self.cmd.run("dpkg --force-confold --configure --pending") + self.cmd.run("apt-get clean") + self.cmd.run("apt-get -q=2 -y update") + self.installed = {} + self.RefreshPackages() + + def RefreshPackages(self): + '''Refresh memory hashes of packages''' + apt_pkg.init() + cache = apt_pkg.GetCache() + self.installed = {} + for pkg in cache.Packages: + if pkg.CurrentVer: + self.installed[pkg.Name] = pkg.CurrentVer.VerStr + + def VerifyPackage(self, entry, modlist): + '''Verify package for entry''' + if not entry.attrib.has_key('version'): + self.logger.info("Cannot verify unversioned package %s" % + (entry.attrib['name'])) + return False + if self.installed.has_key(entry.attrib['name']): + if self.installed[entry.attrib['name']] == entry.attrib['version']: + if not self.setup['quick']: + output = self.cmd.run("/usr/bin/debsums -s %s" % entry.get('name'))[1] + if [filename for filename in output if filename not in modlist]: + return False + return True + else: + entry.set('current_version', self.installed[entry.get('name')]) + return False + entry.set('current_exists', 'false') + return False + + def RemovePackages(self, packages): + '''Deal with extra configuration detected''' + if len(packages) > 0: + self.logger.info('Removing packages:') + self.logger.info(packages) + self.cmd.run("apt-get remove -y --force-yes %s" % \ + " ".join(packages)) + self.RefreshPackages() + self.extra = self.FindExtraPackages() + + diff --git a/src/lib/Client/Tools/Blast.py b/src/lib/Client/Tools/Blast.py new file mode 100644 index 000000000..6133ab2c1 --- /dev/null +++ b/src/lib/Client/Tools/Blast.py @@ -0,0 +1,22 @@ +# This is the bcfg2 support for blastwave packages (pkg-get) +'''This provides bcfg2 support for blastwave''' +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools.SYSV + +class Blast(Bcfg2.Client.Tools.SYSV.SYSV): + '''Support for Blastwave packages''' + pkgtype = 'blast' + pkgtool = ("/opt/csw/bin/pkg-get install %s", ("%s", ["name"])) + __name__ = 'Blast' + __execs__ = ['/opt/csw/bin/pkg-get'] + __handles__ = [('Package', 'blast')] + + # VerifyPackage comes from Bcfg2.Client.Tools.SYSV + # Install comes from Bcfg2.Client.Tools.PkgTool + # Extra comes from Bcfg2.Client.Tools.Tool + # Remove comes from Bcfg2.Client.Tools.SYSV + + def FindExtraPackages(self): + '''Pass through to null FindExtra call''' + return [] diff --git a/src/lib/Client/Tools/Chkconfig.py b/src/lib/Client/Tools/Chkconfig.py new file mode 100644 index 000000000..a1c1160c9 --- /dev/null +++ b/src/lib/Client/Tools/Chkconfig.py @@ -0,0 +1,62 @@ +# This is the bcfg2 support for chkconfig +# $Id$ + +'''This is chkconfig support''' +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools, Bcfg2.Client.XML + +class Chkconfig(Bcfg2.Client.Tools.SvcTool): + '''Chkconfig support for Bcfg2''' + __name__ = 'Chkconfig' + __execs__ = ['/sbin/chkconfig'] + __handles__ = [('Service', 'chkconfig')] + __req__ = {'Service': ['name', 'status']} + + def VerifyService(self, entry, _): + '''Verify Service status for entry''' + try: + srvdata = self.cmd.run('/sbin/chkconfig --list %s | grep -v "unknown service"' + % entry.attrib['name'])[1][0].split() + except IndexError: + # Ocurrs when no lines are returned (service not installed) + entry.set('current_status', 'off') + return False + if entry.attrib['type'] == 'xinetd': + return entry.attrib['status'] == srvdata[1] + + try: + onlevels = [level.split(':')[0] for level in srvdata[1:] if level.split(':')[1] == 'on'] + except IndexError: + onlevels = [] + + # chkconfig/init.d service + if entry.get('status') == 'on': + status = len(onlevels) > 0 + else: + status = len(onlevels) == 0 + + if not status: + if entry.get('status') == 'on': + entry.set('current_status', 'off') + else: + entry.set('current_status', 'on') + return status + + def InstallService(self, entry): + '''Install Service entry''' + self.cmd.run("/sbin/chkconfig --add %s"%(entry.attrib['name'])) + self.logger.info("Installing Service %s" % (entry.get('name'))) + return self.cmd.run("/sbin/chkconfig %s %s" % (entry.get('name'), + entry.get('status')))[0] == 0 + + def FindExtra(self): + '''Locate extra chkconfig Services''' + allsrv = [line.split()[0] for line in \ + self.cmd.run("/sbin/chkconfig --list|grep :on")[1]] + self.logger.debug('Found active services:') + self.logger.debug(allsrv) + specified = [srv.get('name') for srv in self.getSupportedEntries()] + return [Bcfg2.Client.XML.Element('Service', type='chkconfig', name=name) \ + for name in allsrv if name not in specified] + diff --git a/src/lib/Client/Tools/DebInit.py b/src/lib/Client/Tools/DebInit.py new file mode 100644 index 000000000..5cfabb4f0 --- /dev/null +++ b/src/lib/Client/Tools/DebInit.py @@ -0,0 +1,65 @@ +'''Debian Init Support for Bcfg2''' +__revision__ = '$Revision$' + +import glob, os, re +import Bcfg2.Client.Tools + +class DebInit(Bcfg2.Client.Tools.SvcTool): + '''Debian Service Support for Bcfg2''' + __name__ = 'DebInit' + __execs__ = ['/usr/sbin/update-rc.d'] + __handles__ = [('Service', 'deb')] + __req__ = {'Service': ['name', 'status']} + svcre = re.compile("/etc/.*/[SK]\d\d(?P\S+)") + + # implement entry (Verify|Install) ops + def VerifyService(self, entry, _): + '''Verify Service status for entry''' + rawfiles = glob.glob("/etc/rc*.d/*%s" % (entry.get('name'))) + files = [filename for filename in rawfiles if \ + self.svcre.match(filename).group('name') == entry.get('name')] + if entry.get('status') == 'off': + if files: + entry.set('current_status', 'on') + return False + else: + return True + else: + if files: + return True + else: + entry.set('current_status', 'off') + return False + + def InstallService(self, entry): + '''Install Service for entry''' + self.logger.info("Installing Service %s" % (entry.get('name'))) + try: + os.stat('/etc/init.d/%s' % entry.get('name')) + except OSError: + self.logger.debug("Init script for service %s does not exist" % entry.get('name')) + return False + + if entry.get('status') == 'off': + self.cmd.run("/etc/init.d/%s stop" % (entry.get('name'))) + cmdrc = self.cmd.run("/usr/sbin/update-rc.d -f %s remove" % entry.get('name'))[0] + else: + cmdrc = self.cmd.run("/usr/sbin/update-rc.d %s defaults" % \ + (entry.get('name')))[0] + return cmdrc == 0 + + def FindExtra(self): + '''Find Extra Debian Service Entries''' + specified = [entry.get('name') for entry in self.getSupportedEntries()] + extra = [self.svcre.match(fname).group('name') for fname in + glob.glob("/etc/rc[12345].d/S*") \ + if self.svcre.match(fname).group('name') not in specified] + return [Bcfg2.Client.XML.Element('Service', name=name, type='deb') for name \ + in extra] + + def Remove(self, entries): + '''Remove extra service entries''' + self.logger.info('Removing services:') + self.logger.info([entry.get('name') for entry in entries]) + for entry in entries: + self.cmd.run("rm -f /etc/rc*.d/S??%s" % (entry.get('name'))) diff --git a/src/lib/Client/Tools/Encap.py b/src/lib/Client/Tools/Encap.py new file mode 100644 index 000000000..af12ddcd0 --- /dev/null +++ b/src/lib/Client/Tools/Encap.py @@ -0,0 +1,60 @@ +# dclark: This is just stuff from Solaris.py munged together into what looked +# like the right places, before making anything actually work. + +'''Bcfg2 Support for Encap Packages''' + +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools, glob, re + +class Encap(Bcfg2.Client.Tools.PkgTool): + '''Support for Encap packages''' + __name__ = 'Encap' + __execs__ = ['/usr/local/bin/epkg'] + __handles__ = [('Package', 'encap')] + __req__ = {'Package': ['version', 'url']} + pkgtype = 'encap' + pkgtool = ("/usr/local/bin/epkg -l -f -q %s", ("%s", ["url"])) + splitter = re.compile('.*/(?P[\w-]+)\-(?P[\w\.-]+)') + +# If you define self.pkgtool and self.pkgname it will [use] the Pkgtool.Install +# method will do the installation stuff for you + + def __init__(self, logger, setup, config, states): + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config, states) + self.installed = {} + + def RefreshPackages(self): + '''Try to find encap packages''' + self.installed = {} + for pkg in glob.glob("/usr/local/encap/*"): + match = self.splitter.match(pkg) + if match: + self.installed[match.group('name')] = match.group('version') + else: + print "Failed to split name %s" % pkg + #print self.installed.keys() + + def VerifyPackage(self, entry, _): + '''Verify Package status for entry''' + if not entry.get('version'): + self.logger.info("Insufficient information of Package %s; cannot Verify" % entry.get('name')) + return False + cmdrc = self.cmd.run("/usr/local/bin/epkg -q -S -k %s-%s >/dev/null" % + (entry.get('name'), entry.get('version')))[0] + if cmdrc != 0: + self.logger.debug("Package %s version incorrect" % entry.get('name')) + else: + return True + return False + + # Can use the FindExtraPackages method from Bcfg2.Client.Tools.PkgTool + + def RemovePackages(self, packages): + '''Deal with extra configuration detected''' + names = " ".join([pkg.get('name') for pkg in packages]) + self.logger.info("Removing packages: %s" % (names)) + self.cmd.run("/usr/local/bin/epkg -l -q -r %s" % (names)) + self.RefreshPackages() + self.extra = self.FindExtraPackages() + diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Client/Tools/POSIX.py new file mode 100644 index 000000000..c5d82a403 --- /dev/null +++ b/src/lib/Client/Tools/POSIX.py @@ -0,0 +1,243 @@ +'''All POSIX Type client support for Bcfg2''' +__revision__ = '$Revision$' + +from stat import S_ISVTX, S_ISGID, S_ISUID, S_IXUSR, S_IWUSR, S_IRUSR, S_IXGRP +from stat import S_IWGRP, S_IRGRP, S_IXOTH, S_IWOTH, S_IROTH, ST_MODE, S_ISDIR +from stat import S_IFREG, ST_UID, ST_GID, S_ISREG, S_IFDIR, S_ISLNK + +import binascii, difflib, grp, os, pwd, xml.sax.saxutils +import Bcfg2.Client.Tools + +def calcPerms(initial, perms): + '''This compares ondisk permissions with specified ones''' + pdisp = [{1:S_ISVTX, 2:S_ISGID, 4:S_ISUID}, {1:S_IXUSR, 2:S_IWUSR, 4:S_IRUSR}, + {1:S_IXGRP, 2:S_IWGRP, 4:S_IRGRP}, {1:S_IXOTH, 2:S_IWOTH, 4:S_IROTH}] + tempperms = initial + if len(perms) == 3: + perms = '0%s' % (perms) + pdigits = [int(perms[digit]) for digit in range(4)] + for index in range(4): + for (num, perm) in pdisp[index].iteritems(): + if pdigits[index] & num: + tempperms |= perm + return tempperms + +class POSIX(Bcfg2.Client.Tools.Tool): + '''POSIX File support code''' + __name__ = 'POSIX' + __handles__ = [('ConfigFile', None), ('Directory', None), ('Permissions', None), \ + ('SymLink', None)] + __req__ = {'ConfigFile': ['name', 'owner', 'group', 'perms'], + 'Directory': ['name', 'owner', 'group', 'perms'], + 'Permissions': ['name', 'owner', 'group', 'perms'], + 'SymLink': ['name', 'to']} + + def VerifySymLink(self, entry, _): + '''Verify SymLink Entry''' + try: + sloc = os.readlink(entry.get('name')) + if sloc == entry.get('to'): + return True + self.logger.debug("Symlink %s points to %s, should be %s" % \ + (entry.get('name'), sloc, entry.get('to'))) + entry.set('current_to', sloc) + return False + except OSError: + entry.set('current_exists', 'false') + return False + + def InstallSymLink(self, entry): + '''Install SymLink Entry''' + self.logger.info("Installing Symlink %s" % (entry.get('name'))) + try: + fmode = os.lstat(entry.get('name'))[ST_MODE] + if S_ISREG(fmode) or S_ISLNK(fmode): + self.logger.debug("Non-directory entry already exists at %s" % \ + (entry.get('name'))) + os.unlink(entry.get('name')) + elif S_ISDIR(fmode): + self.logger.debug("Directory entry already exists at %s" % (entry.get('name'))) + self.cmd.run("mv %s/ %s.bak" % (entry.get('name'), entry.get('name'))) + else: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Symlink %s cleanup failed" % (entry.get('name'))) + try: + os.symlink(entry.get('to'), entry.get('name')) + return True + except OSError: + return False + + def VerifyDirectory(self, entry, _): + '''Verify Directory Entry''' + while len(entry.get('perms', '')) < 4: + entry.set('perms', '0' + entry.get('perms', '')) + try: + ondisk = os.stat(entry.get('name')) + except OSError: + entry.set('current_exists', 'false') + self.logger.debug("%s %s does not exist" % + (entry.tag, entry.get('name'))) + return False + try: + owner = pwd.getpwuid(ondisk[ST_UID])[0] + group = grp.getgrgid(ondisk[ST_GID])[0] + except (OSError, KeyError): + self.logger.error('User resolution failing') + owner = 'root' + group = 'root' + perms = oct(os.stat(entry.get('name'))[ST_MODE])[-4:] + if ((owner == entry.get('owner')) and + (group == entry.get('group')) and + (perms == entry.get('perms'))): + return True + else: + if owner != entry.get('owner'): + entry.set('current_owner', owner) + self.logger.debug("%s %s ownership wrong" % (entry.tag, entry.get('name'))) + if group != entry.get('group'): + entry.set('current_group', group) + self.logger.debug("%s %s group wrong" % (entry.tag, entry.get('name'))) + if perms != entry.get('perms'): + entry.set('current_perms', perms) + self.logger.debug("%s %s permissions wrong: are %s should be %s" % + (entry.tag, entry.get('name'), perms, entry.get('perms'))) + return False + + def InstallDirectory(self, entry): + '''Install Directory Entry''' + self.logger.info("Installing Directory %s" % (entry.get('name'))) + try: + fmode = os.lstat(entry.get('name')) + if not S_ISDIR(fmode[ST_MODE]): + self.logger.debug("Found a non-directory entry at %s" % (entry.get('name'))) + try: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Failed to unlink %s" % (entry.get('name'))) + return False + else: + self.logger.debug("Found a pre-existing directory at %s" % (entry.get('name'))) + exists = True + except OSError: + # stat failed + exists = False + + if not exists: + parent = "/".join(entry.get('name').split('/')[:-1]) + if parent: + try: + os.lstat(parent) + except: + self.logger.debug('Creating parent path for directory %s' % (entry.get('name'))) + for idx in xrange(len(parent.split('/')[:-1])): + current = '/'+'/'.join(parent.split('/')[1:2+idx]) + try: + sloc = os.lstat(current) + try: + if not S_ISDIR(sloc[ST_MODE]): + os.unlink(current) + os.mkdir(current) + except OSError: + return False + except OSError: + try: + os.mkdir(current) + except OSError: + return False + + try: + os.mkdir(entry.get('name')) + except OSError: + self.logger.error('Failed to create directory %s' % (entry.get('name'))) + return False + try: + os.chown(entry.get('name'), + pwd.getpwnam(entry.get('owner'))[2], grp.getgrnam(entry.get('group'))[2]) + os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) + return True + except (OSError, KeyError): + self.logger.error('Permission fixup failed for %s' % (entry.get('name'))) + return False + + def VerifyConfigFile(self, entry, _): + '''Install ConfigFile Entry''' + # configfile verify is permissions check + content check + permissionStatus = self.VerifyDirectory(entry, _) + if entry.get('encoding', 'ascii') == 'base64': + tempdata = binascii.a2b_base64(entry.text) + elif entry.get('empty', 'false') == 'true': + tempdata = '' + else: + if entry.text == None: + self.logger.error("Cannot verify incomplete ConfigFile %s" % (entry.get('name'))) + return False + tempdata = entry.text + + try: + content = open(entry.get('name')).read() + except IOError: + # file does not exist + return False + contentStatus = content == tempdata + if not contentStatus: + diff = '\n'.join([x for x in difflib.unified_diff(content.split('\n'), tempdata.split('\n'))]) + try: + entry.set("current_diff", xml.sax.saxutils.quoteattr(diff)) + except: + pass + return contentStatus and permissionStatus + + def InstallConfigFile(self, entry): + '''Install ConfigFile Entry''' + self.logger.info("Installing ConfigFile %s" % (entry.get('name'))) + + parent = "/".join(entry.get('name').split('/')[:-1]) + if parent: + try: + os.lstat(parent) + except: + self.logger.debug('Creating parent path for config file %s' % (entry.get('name'))) + for idx in xrange(len(parent.split('/')[:-1])): + current = '/'+'/'.join(parent.split('/')[1:2+idx]) + try: + sloc = os.lstat(current) + try: + if not S_ISDIR(sloc[ST_MODE]): + os.unlink(current) + os.mkdir(current) + except OSError: + return False + except OSError: + try: + os.mkdir(current) + except OSError: + return False + + # If we get here, then the parent directory should exist + try: + newfile = open("%s.new"%(entry.get('name')), 'w') + if entry.get('encoding', 'ascii') == 'base64': + filedata = binascii.a2b_base64(entry.text) + elif entry.get('empty', 'false') == 'true': + filedata = '' + else: + filedata = entry.text + newfile.write(filedata) + newfile.close() + try: + os.chown(newfile.name, pwd.getpwnam(entry.get('owner'))[2], + grp.getgrnam(entry.get('group'))[2]) + except KeyError: + os.chown(newfile.name, 0, 0) + os.chmod(newfile.name, calcPerms(S_IFREG, entry.get('perms'))) + if entry.get("paranoid", False) and self.setup.get("paranoid", False): + self.cmd.run("cp %s /var/cache/bcfg2/%s" % (entry.get('name'))) + os.rename(newfile.name, entry.get('name')) + return True + except (OSError, IOError), err: + if err.errno == 13: + self.logger.info("Failed to open %s for writing" % (entry.get('name'))) + else: + print err + return False diff --git a/src/lib/Client/Tools/PostInstall.py b/src/lib/Client/Tools/PostInstall.py new file mode 100644 index 000000000..34627cacf --- /dev/null +++ b/src/lib/Client/Tools/PostInstall.py @@ -0,0 +1,21 @@ +'''PostInstall Support''' +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools + +class PostInstall(Bcfg2.Client.Tools.Tool): + '''Implement PostInstalls''' + __name__ = 'PostInstall' + __handles__ = [('PostInstall', None)] + __req__ = {'PostInstall': ['name']} + + def VerifyPostInstall(self, dummy, _): + '''PostInstalls always verify true''' + return True + + def BundleUpdated(self, bundle): + '''Run postinstalls when bundles have been updated''' + for entry in bundle: + if entry.tag == 'PostInstall': + self.cmd.run(entry.get('name')) + diff --git a/src/lib/Client/Tools/RPM.py b/src/lib/Client/Tools/RPM.py new file mode 100644 index 000000000..661c0a167 --- /dev/null +++ b/src/lib/Client/Tools/RPM.py @@ -0,0 +1,82 @@ +'''Bcfg2 Support for RPMS''' + +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools, time + +class RPM(Bcfg2.Client.Tools.PkgTool): + '''Support for RPM packages''' + __name__ = 'RPM' + __execs__ = ['/bin/rpm'] + __handles__ = [('Package', 'rpm')] + __req__ = {'Package': ['name', 'version', 'url']} + pkgtype = 'rpm' + pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"])) + + def RefreshPackages(self, level=0): + '''Cache package states''' + self.installed = {} + if level > 5: + return + for line in self.cmd.run("rpm -qa --qf '%{NAME} %{VERSION}-%{RELEASE}\n'")[1]: + try: + (name, version) = line.split() + self.installed[name] = version + except ValueError: + self.logger.error("Failed to access RPM db; retrying after 30s") + time.sleep(30) + return self.RefreshPackages(level + 1) + + def VerifyPackage(self, entry, modlist): + '''Verify Package status for entry''' + if not entry.get('version'): + self.logger.error("Can't install package %s, not enough data." % (entry.get('name'))) + return False + rpm_options = [] + if entry.get('verify', 'false') == 'nomtime': + self.logger.debug("Skipping mtime verification for package %s" % (entry.get('name'))) + rpm_options.append("--nomtime") + if self.installed.has_key(entry.get('name')): + if entry.get('version') == self.installed[entry.get('name')]: + if entry.get('multiarch'): + archs = entry.get('multiarch').split() + info = self.cmd.run('rpm -q %s --qf "%%{NAME} %%{VERSION}-%%{RELEASE} %%{ARCH}\n"' % (entry.get('name')))[1] + while info: + arch = info.pop().split()[2] + if arch in archs: + archs.remove(arch) + else: + self.logger.error("Got pkg install for Package %s: arch %s" % (entry.get('name'), arch)) + return False + if archs: + self.logger.error("Package %s not installed for arch: %s" % (entry.get('name'), archs)) + return False + if (self.setup['quick'] or (entry.get('verify', 'true') == 'false')) or entry.get('multiarch'): + if entry.get('verify', 'true') == 'false': + self.logger.debug("Skipping checksum verification for package %s" % (entry.get('name'))) + return True + else: + self.logger.debug("Package %s: wrong version installed. want %s have %s" % + (entry.get('name'), entry.get('version'), self.installed[entry.get('name')])) + entry.set('current_version', self.installed[entry.get('name')]) + return False + else: + self.logger.debug("Package %s: not installed" % (entry.get('name'))) + entry.set('current_exists', 'false') + return False + + (vstat, output) = self.cmd.run("rpm --verify -q %s %s-%s" % (" ".join(rpm_options), entry.get('name'), entry.get('version'))) + if vstat != 0: + if [name for name in output if name.split()[-1] not in modlist]: + self.logger.debug("Package %s content verification failed" % entry.get('name')) + return False + return True + + def RemovePackages(self, entries): + '''Remove specified entries''' + pkgnames = " ".join([entry[2] for entry in entries]) + if len(pkgnames) > 0: + self.logger.info("Removing packages: %s" % pkgnames) + self.cmd.run("rpm --quiet -e --allmatches %s" % " ".join(pkgnames)) + self.RefreshPackages() + self.extra = self.FindExtraPackages() diff --git a/src/lib/Client/Tools/SMF.py b/src/lib/Client/Tools/SMF.py new file mode 100644 index 000000000..321c3cc17 --- /dev/null +++ b/src/lib/Client/Tools/SMF.py @@ -0,0 +1,123 @@ +'''SMF support for Bcfg2''' +__revision__ = '$Revision$' + +import glob, os +import Bcfg2.Client.Tools + +class SMF(Bcfg2.Client.Tools.Tool): + '''Support for Solaris SMF Services''' + __handles__ = [('Service', 'smf')] + __execs__ = ['/usr/sbin/svcadm', '/usr/bin/svcs'] + __name__ = 'SMF' + __req__ = {'Service':['name', 'status']} + __ireq__ = {'Service': ['name', 'status', 'FMRI']} + + def GetFMRI(self, entry): + '''Perform FMRI resolution for service''' + if not entry.attrib.has_key('FMRI'): + name = self.cmd.run("/usr/bin/svcs -H -o FMRI %s 2>/dev/null" % \ + entry.get('name'))[1] + if name: + entry.set('FMRI', name[0]) + return True + else: + self.logger.info('Failed to locate FMRI for service %s' % entry.get('name')) + return False + + def VerifyService(self, entry, _): + '''Verify SMF Service Entry''' + if not self.GetFMRI(entry): + return False + if entry.get('FMRI').startswith('lrc'): + filename = entry.get('FMRI').split('/')[-1] + # this is a legacy service + gname = "/etc/rc*.d/%s" % filename + files = glob.glob(gname.replace('_', '.')) + if files: + self.logger.debug("Matched %s with %s" % \ + (entry.get("FMRI"), ":".join(files))) + return entry.get('status') == 'on' + else: + self.logger.debug("No service matching %s" % (entry.get("FMRI"))) + return entry.get('status') == 'off' + try: + srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % entry.attrib['name'])[1][0].split() + except IndexError: + # Ocurrs when no lines are returned (service not installed) + return False + + if entry.get('status') == 'on': + return srvdata[0] == 'ON' + else: + return srvdata[0] in ['OFF', 'UN', 'MNT', 'DIS', 'DGD'] + + def InstallService(self, entry): + '''Install SMF Service Entry''' + self.logger.info("Installing Service %s" % (entry.get('name'))) + if entry.get('status') == 'off': + if entry.get("FMRI").startswith('lrc'): + try: + loc = entry.get("FMRI")[4:].replace('_', '.') + self.logger.debug("Renaming file %s to %s" % \ + (loc, loc.replace('/S', '/DISABLED.S'))) + os.rename(loc, loc.replace('/S', '/DISABLED.S')) + return True + except OSError: + self.logger.error("Failed to rename init script %s" % (loc)) + return False + else: + cmdrc = self.cmd.run("/usr/sbin/svcadm disable %s" % \ + (entry.get('FMRI')))[0] + else: + if entry.get('FMRI').startswith('lrc'): + loc = entry.get("FMRI")[4:].replace('_', ',') + try: + os.stat(loc.replace('/S', '/Disabled.')) + self.logger.debug("Renaming file %s to %s" % \ + (loc.replace('/S', '/DISABLED.S'), loc)) + os.rename(loc.replace('/S', '/DISABLED.S'), loc) + cmdrc = 0 + except OSError: + self.logger.debug("Failed to rename %s to %s" \ + % (loc.replace('/S', '/DISABLED.S'), loc)) + cmdrc = 1 + else: + cmdrc = self.cmd.run("/usr/sbin/svcadm enable -r %s" % \ + (entry.get('FMRI')))[0] + return cmdrc == 0 + + def Remove(self, svcs): + '''Remove Extra SMF entries''' + pass + + def FindExtra(self): + '''Find Extra SMF Services''' + allsrv = [name for name, version in \ + [ srvc.strip().split() for srvc in + self.cmd.run("/usr/bin/svcs -a -H -o FMRI,STATE")[1] ] + if version != 'disabled'] + + for svc in self.getSupportedEntries(): + name = self.cmd.run("/usr/bin/svcs -H -o FMRI %s 2>/dev/null" % \ + svc.get('name'))[1] + if name: + svc.set('FMRI', name[0]) + if name in allsrv: + allsrv.remove(name) + else: + self.logger.info("Failed to locate FMRI for service %s" % svc.get('name')) + + return [Bcfg2.Client.XML.Element("Service", type='smf', name=name) for name in allsrv] + + def BundleUpdated(self, bundle): + '''Restart smf services''' + for entry in [entry for entry in bundle if self.handlesEntry(entry)]: + if not self.canInstall(entry): + self.logger.error("Insufficient information to restart service %s" % (entry.get('name'))) + else: + if entry.get('status') == 'on': + self.logger.info("Restarting smf service %s" % (entry.get("FMRI"))) + self.cmd.run("svcadm restart %s" % (entry.get("FMRI"))) + else: + self.cmd.run("svcadm disable %s" % (entry.get("FMRI"))) + diff --git a/src/lib/Client/Tools/SYSV.py b/src/lib/Client/Tools/SYSV.py new file mode 100644 index 000000000..ad9bb00c5 --- /dev/null +++ b/src/lib/Client/Tools/SYSV.py @@ -0,0 +1,83 @@ +# This is the bcfg2 support for solaris sysv packages +'''This provides bcfg2 support for Solaris SYSV packages''' +__revision__ = '$Revision$' + +import tempfile, Bcfg2.Client.Tools, Bcfg2.Client.XML + + +noask = ''' +mail= +instance=overwrite +partial=nocheck +runlevel=nocheck +idepend=nocheck +rdepend=nocheck +space=ask +setuid=nocheck +conflict=nocheck +action=nocheck +basedir=default +''' + +class SYSV(Bcfg2.Client.Tools.PkgTool): + '''Solaris SYSV package support''' + __execs__ = ["/usr/sbin/pkgadd"] + __handles__ = [('Package', 'sysv')] + __req__ = {'Package': ['name', 'version']} + __name__ = 'SYSV' + pkgtype = 'sysv' + pkgtool = ("/usr/sbin/pkgadd %s -d %%s -n %%%%s", (("%s", ["name"]))) + + def __init__(self, logger, setup, config, states): + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config, states) + self.noaskname = tempfile.mktemp() + try: + open(self.noaskname, 'w+').write(noask) + self.pkgtool = (self.pkgtool[0] % ("-a %s" % (self.noaskname)), \ + self.pkgtool[1]) + except: + self.pkgtool = (self.pkgtool[0] % (""), self.pkgtool[1]) + + def RefreshPackages(self): + '''Refresh memory hashes of packages''' + self.installed = {} + # Build list of packages + lines = self.cmd.run("/usr/bin/pkginfo -x")[1] + while lines: + version = lines.pop().split()[1] + pkg = lines.pop().split()[0] + self.installed[pkg] = version + + def VerifyPackage(self, entry, modlist): + '''Verify Package status for entry''' + if not entry.get('version'): + self.logger.info("Insufficient information of Package %s; cannot Verify" % entry.get('name')) + return False + cmdrc = self.cmd.run("/usr/bin/pkginfo -q -v \"%s\" %s" % \ + (entry.get('version'), entry.get('name')))[0] + + if cmdrc != 0: + self.logger.debug("Package %s version incorrect" % entry.get('name')) + else: + if self.setup['quick'] or entry.attrib.get('verify', 'true') == 'false': + return True + (vstat, odata) = self.cmd.run("/usr/sbin/pkgchk -n %s" % (entry.get('name'))) + if vstat == 0: + return True + else: + output = [line for line in odata if line[:5] == 'ERROR'] + if len([name for name in output if name.split()[-1] not in modlist]): + self.logger.debug("Package %s content verification failed" % \ + (entry.get('name'))) + else: + return True + return False + + def RemovePackages(self, packages): + '''Remove specified Sysv packages''' + names = [pkg.get('name') for pkg in packages] + self.logger.info("Removing packages: %s" % (names)) + self.cmd.run("/usr/sbin/pkgrm -a %s -n %s" % \ + (self.noaskname, names)) + self.RefreshPackages() + self.extra = self.FindExtraPackages() diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py new file mode 100644 index 000000000..da9f47eeb --- /dev/null +++ b/src/lib/Client/Tools/__init__.py @@ -0,0 +1,252 @@ +'''This contains all Bcfg2 Tool modules''' +__revision__ = '$Revision$' + +__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Launchd", "PostInstall", + "POSIX", "RPM", "SMF", "SYSV"] + +import os, popen2, stat, sys, Bcfg2.Client.XML + +class toolInstantiationError(Exception): + '''This error is called if the toolset cannot be instantiated''' + pass + +class readonlypipe(popen2.Popen4): + '''This pipe sets up stdin --> /dev/null''' + def __init__(self, cmd, bufsize=-1): + popen2._cleanup() + c2pread, c2pwrite = os.pipe() + null = open('/dev/null', 'w+') + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(null.fileno(), sys.__stdin__.fileno()) + #os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + os.dup2(c2pwrite, 2) + self._run_child(cmd) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + popen2._active.append(self) + +class executor: + '''this class runs stuff for us''' + def __init__(self, logger): + self.logger = logger + + def run(self, command): + '''Run a command in a pipe dealing with stdout buffer overloads''' + self.logger.debug('> %s' % command) + + runpipe = readonlypipe(command, bufsize=16384) + output = '' + cmdstat = -1 + while cmdstat == -1: + runpipe.fromchild.flush() + moreOutput = runpipe.fromchild.read() + if len(moreOutput) > 0: + self.logger.debug('< %s' % moreOutput) + output += moreOutput + cmdstat = runpipe.poll() + + return (cmdstat, [line for line in output.split('\n') if line]) + +class Tool: + '''All tools subclass this. It defines all interfaces that need to be defined''' + __name__ = 'Tool' + __execs__ = [] + __handles__ = [] + __req__ = {} + __important__ = [] + + def __init__(self, logger, setup, config, states): + self.setup = setup + self.logger = logger + if not hasattr(self, '__ireq__'): + self.__ireq__ = self.__req__ + self.config = config + self.cmd = executor(logger) + self.states = states + self.modified = [] + self.extra = [] + self.handled = [entry for struct in self.config for entry in struct \ + if self.handlesEntry(entry)] + for filename in self.__execs__: + try: + mode = stat.S_IMODE(os.stat(filename)[stat.ST_MODE]) + if mode & stat.S_IEXEC != stat.S_IEXEC: + self.logger.debug("%s: %s not executable" % \ + (self.__name__, filename)) + raise toolInstantiationError + except OSError: + raise toolInstantiationError + except: + self.logger.debug("%s failed" % filename, exc_info=1) + raise toolInstantiationError + + def BundleUpdated(self, _): + '''This callback is used when bundle updates occur''' + pass + + def Inventory(self, structures=[]): + '''Dispatch verify calls to underlying methods''' + if not structures: + structures = self.config.getchildren() + for (struct, entry) in [(struct, entry) for struct in structures \ + for entry in struct.getchildren() \ + if self.canVerify(entry)]: + try: + func = getattr(self, "Verify%s" % (entry.tag)) + self.states[entry] = func(entry, self.buildModlist(entry, struct)) + except: + self.logger.error( + "Unexpected failure of verification method for entry type %s" \ + % (entry.tag), exc_info=1) + self.extra = self.FindExtra() + + def Install(self, entries): + '''Install all entries in sublist''' + for entry in entries: + try: + func = getattr(self, "Install%s" % (entry.tag)) + self.states[entry] = func(entry) + self.modified.append(entry) + except: + self.logger.error("Unexpected failure of install method for entry type %s" \ + % (entry.tag), exc_info=1) + + def Remove(self, entries): + '''Remove specified extra entries''' + pass + + def getSupportedEntries(self): + '''return a list of supported entries''' + return [entry for struct in self.config.getchildren() for entry in struct.getchildren() \ + if self.handlesEntry(entry)] + + def handlesEntry(self, entry): + '''return if entry is handled by this Tool''' + return (entry.tag, entry.get('type')) in self.__handles__ + + def buildModlist(self, entry, struct): + '''Build a list of potentially modified POSIX paths for this entry''' + if entry.tag != 'Package' or struct.tag != 'Bundle': + return [] + return [sentry.get('name') for sentry in struct if sentry.tag in \ + ['ConfigFile', 'SymLink', 'Directory']] + + def canVerify(self, entry): + '''test if entry has enough information to be verified''' + if not self.handlesEntry(entry): + return False + + if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + return True + + + def FindExtra(self): + '''Return a list of extra entries''' + return [] + + def canInstall(self, entry): + '''test if entry has enough information to be installed''' + if not self.handlesEntry(entry): + return False + if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + return False + return True + +class PkgTool(Tool): + '''PkgTool provides a one-pass install with fallback for use with packaging systems''' + pkgtool = ('echo %s', ('%s', ['name'])) + pkgtype = 'echo' + __name__ = 'PkgTool' + + def __init__(self, logger, setup, config, states): + Tool.__init__(self, logger, setup, config, states) + self.installed = {} + self.Remove = self.RemovePackages + self.FindExtra = self.FindExtraPackages + + def VerifyPackage(self, dummy, _): + '''Dummy verification method''' + return False + + def Install(self, packages): + '''Run a one-pass install, followed by single pkg installs in case of failure''' + self.logger.info("Trying single pass package install for pkgtype %s" % \ + self.pkgtype) + + data = [tuple([pkg.get(field) for field in self.pkgtool[1][1]]) for pkg in packages] + pkgargs = " ".join([self.pkgtool[1][0] % datum for datum in data]) + + self.logger.debug("Installing packages: :%s:" % pkgargs) + self.logger.debug("Running command ::%s::" % (self.pkgtool[0] % pkgargs)) + + cmdrc = self.cmd.run(self.pkgtool[0] % pkgargs)[0] + if cmdrc == 0: + self.logger.info("Single Pass Succeded") + # set all package states to true and flush workqueues + pkgnames = [pkg.get('name') for pkg in packages] + for entry in [entry for entry in self.states.keys() + if entry.tag == 'Package' and entry.get('type') == self.pkgtype + and entry.get('name') in pkgnames]: + self.logger.debug('Setting state to true for pkg %s' % (entry.get('name'))) + self.states[entry] = True + self.RefreshPackages() + else: + self.logger.error("Single Pass Failed") + # do single pass installs + self.RefreshPackages() + for pkg in packages: + # handle state tracking updates + if self.VerifyPackage(pkg, []): + self.logger.info("Forcing state to true for pkg %s" % (pkg.get('name'))) + self.states[pkg] = True + else: + self.logger.info("Installing pkg %s version %s" % + (pkg.get('name'), pkg.get('version'))) + cmdrc = self.cmd.run(self.pkgtool[0] % + (self.pkgtool[1][0] % + tuple([pkg.get(field) for field in self.pkgtool[1][1]]))) + if cmdrc[0] == 0: + self.states[pkg] = True + else: + self.logger.error("Failed to install package %s" % (pkg.get('name'))) + for entry in [ent for ent in packages if self.states[ent]]: + self.modified.append(entry) + + def RefreshPackages(self): + '''Dummy state refresh method''' + pass + + def RemovePackages(self, packages): + '''Dummy implementation of package removal method''' + pass + + def FindExtraPackages(self): + '''Find extra packages''' + packages = [entry.get('name') for entry in self.getSupportedEntries()] + extras = [key for key in self.installed if key not in packages] + return [Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) \ + for name in extras] + +class SvcTool(Tool): + '''This class defines basic Service behavior''' + __name__ = 'SvcTool' + + def BundleUpdated(self, bundle): + '''The Bundle has been updated''' + for entry in bundle: + if self.handlesEntry(entry): + if entry.get('status') == 'on': + self.logger.debug('Restarting service %s' % entry.get('name')) + self.cmd.run('/etc/init.d/%s %s' % \ + (entry.get('name'), entry.get('reload', 'reload'))) + else: + self.logger.debug('Stopping service %s' % entry.get('name')) + self.cmd.run('/etc/init.d/%s stop' % (entry.get('name'))) diff --git a/src/lib/Client/Toolset.py b/src/lib/Client/Toolset.py deleted file mode 100644 index 06879e631..000000000 --- a/src/lib/Client/Toolset.py +++ /dev/null @@ -1,706 +0,0 @@ -'''This is the basic toolset class for the Bcfg2 client''' -__revision__ = '$Revision$' - -from stat import S_ISVTX, S_ISGID, S_ISUID, S_IXUSR, S_IWUSR, S_IRUSR, S_IXGRP -from stat import S_IWGRP, S_IRGRP, S_IXOTH, S_IWOTH, S_IROTH, ST_MODE, S_ISDIR -from stat import S_IFREG, ST_UID, ST_GID, S_ISREG, S_IFDIR, S_ISLNK - -import binascii, copy, difflib, grp, logging, os, popen2, pwd, stat, sys, xml.sax.saxutils - -import Bcfg2.Client.XML - -def calcPerms(initial, perms): - '''This compares ondisk permissions with specified ones''' - pdisp = [{1:S_ISVTX, 2:S_ISGID, 4:S_ISUID}, {1:S_IXUSR, 2:S_IWUSR, 4:S_IRUSR}, - {1:S_IXGRP, 2:S_IWGRP, 4:S_IRGRP}, {1:S_IXOTH, 2:S_IWOTH, 4:S_IROTH}] - tempperms = initial - if len(perms) == 3: - perms = '0%s' % (perms) - pdigits = [int(perms[digit]) for digit in range(4)] - for index in range(4): - for (num, perm) in pdisp[index].iteritems(): - if pdigits[index] & num: - tempperms |= perm - return tempperms - -class readonlypipe(popen2.Popen4): - '''This pipe sets up stdin --> /dev/null''' - def __init__(self, cmd, bufsize=-1): - popen2._cleanup() - c2pread, c2pwrite = os.pipe() - null = open('/dev/null', 'w+') - self.pid = os.fork() - if self.pid == 0: - # Child - os.dup2(null.fileno(), sys.__stdin__.fileno()) - #os.dup2(p2cread, 0) - os.dup2(c2pwrite, 1) - os.dup2(c2pwrite, 2) - self._run_child(cmd) - os.close(c2pwrite) - self.fromchild = os.fdopen(c2pread, 'r', bufsize) - popen2._active.append(self) - -class Toolset(object): - '''The toolset class contains underlying command support and all states''' - __important__ = [] - __name__ = 'Toolset' - pkgtool = {'echo': ('%s', ['name'])} - - def __init__(self, cfg, setup): - '''Install initial configs, and setup state structures''' - object.__init__(self) - self.setup = setup - self.cfg = cfg - self.states = {} - self.structures = {} - self.modified = [] - self.installed = {} - self.logger = logging.getLogger('Toolset') - self.pkgwork = {'add':[], 'update':[], 'remove':[]} - self.extra_services = [] - if self.__important__: - for cfile in [cfl for cfl in cfg.findall(".//ConfigFile") if cfl.get('name') in self.__important__]: - self.VerifyEntry(cfile) - if not self.states[cfile]: - self.InstallConfigFile(cfile) - self.statistics = Bcfg2.Client.XML.Element("Statistics") - - def saferun(self, command): - '''Run a command in a pipe dealing with stdout buffer overloads''' - self.logger.debug('> %s' % command) - - runpipe = readonlypipe(command, bufsize=16384) - output = '' - cmdstat = -1 - while cmdstat == -1: - runpipe.fromchild.flush() - moreOutput = runpipe.fromchild.read() - if len(moreOutput) > 0: - self.logger.debug('< %s' % moreOutput) - output += moreOutput - cmdstat = runpipe.poll() - - return (cmdstat, [line for line in output.split('\n') if line]) - - def CondDisplayState(self, phase): - '''Conditionally print tracing information''' - self.logger.info('\nPhase: %s' % phase) - self.logger.info('Correct entries:\t%d' % self.states.values().count(True)) - self.logger.info('Incorrect entries:\t%d' % self.states.values().count(False)) - self.logger.info('Total managed entries:\t%d' % len(self.states.values())) - if not self.setup['bundle']: - self.logger.info('Unmanaged entries:\t%d' % len(self.pkgwork['remove'])) - - if ((self.states.values().count(False) == 0) and not self.pkgwork['remove']): - self.logger.info('All entries correct.') - - # These next functions form the external API - - def Refresh(self): - '''Update based on current pkg system state''' - return - - def Inventory(self): - '''Inventory system status''' - self.logger.info("Inventorying system...") - self.Inventory_Entries() - all = copy.deepcopy(self.installed) - desired = {} - for entry in self.cfg.findall(".//Package"): - desired[entry.attrib['name']] = entry - self.pkgwork = {'update':[], 'add':[], 'remove':[]} - for pkg, entry in desired.iteritems(): - if self.states.get(entry, True): - # package entry verifies - del all[pkg] - else: - if all.has_key(pkg): - # wrong version - del all[pkg] - self.pkgwork['update'].append(entry) - else: - # new pkg - self.pkgwork['add'].append(entry) - - # pkgwork contains all one-way verification data now - # all data remaining in all is extra packages - self.pkgwork['remove'] = all.keys() - - def Inventory_Entries(self): - '''Build up workqueue for installation''' - # build initial set of states - unexamined = [(child, []) for child in self.cfg.getchildren()] - while unexamined: - (entry, modlist) = unexamined.pop() - if entry.tag not in ['Bundle', 'Independant']: - self.VerifyEntry(entry, modlist) - else: - modlist = [cfile.get('name') for cfile in entry.getchildren() if cfile.tag == 'ConfigFile'] - unexamined += [(child, modlist) for child in entry.getchildren()] - self.structures[entry] = False - - for structure in self.cfg.getchildren(): - self.CheckStructure(structure) - - def CheckStructure(self, structure): - '''Check structures with bundle verification semantics''' - if structure in self.modified: - self.modified.remove(structure) - if structure.tag == 'Bundle': - # check for clobbered data - modlist = [cfile.get('name') for cfile in structure.getchildren() if cfile.tag == 'ConfigFile'] - for entry in structure.getchildren(): - self.VerifyEntry(entry, modlist) - try: - state = [self.states[entry] for entry in structure.getchildren()] - if False not in state: - self.structures[structure] = True - except KeyError, msg: - print "State verify evidently failed for %s" % (msg) - self.structures[structure] = False - - def GenerateStats(self, clientVersion): - '''Generate XML summary of execution statistics''' - stats = self.statistics - - # Calculate number of total bundles and structures - total = len(self.states) - stats.set('total', str(total)) - # Calculate number of good bundles and structures - good = len([key for key, val in self.states.iteritems() if val]) - stats.set('good', str(good)) - stats.set('version', '2.0') - stats.set('client_version', clientVersion) - stats.set('revision', self.cfg.get('revision', '-1')) - - if len([key for key, val in self.states.iteritems() if not val]) == 0: - stats.set('state', 'clean') - dirty = 0 - else: - stats.set('state', 'dirty') - dirty = 1 - #stats.set('time', asctime(localtime())) - - # List bad elements of the configuration - flows = [(dirty, "Bad"), (self.modified, "Modified")] - for (condition, tagName) in flows: - if condition: - container = Bcfg2.Client.XML.SubElement(stats, tagName) - for ent in [key for key, val in self.states.iteritems() if not val]: - newent = Bcfg2.Client.XML.SubElement(container, ent.tag, - name=ent.get('name', 'None')) - for field in [item for item in 'current_exists', 'current_diff' if item in ent.attrib]: - newent.set(field, ent.get(field)) - del ent.attrib[field] - failures = [key for key in ent.attrib if key[:8] == 'current_'] - for fail in failures: - for field in [fail, fail[8:]]: - try: - newent.set(field, ent.get(field)) - except: - self.logger.error("Failed to set field %s for entry %s, value %s" % - (field, ent.get('name'), ent.get(field))) - if 'severity' in ent.attrib: - newent.set('severity', ent.get('severity')) - if self.extra_services + self.pkgwork['remove']: - extra = Bcfg2.Client.XML.SubElement(stats, "Extra") - [Bcfg2.Client.XML.SubElement(extra, "Service", name=svc, current_status='on') - for svc in self.extra_services] - for pkg in self.pkgwork['remove']: - if pkg in self.installed: - Bcfg2.Client.XML.SubElement(extra, "Package", name=pkg, - current_version=self.installed[pkg]) - else: - Bcfg2.Client.XML.SubElement(extra, "Package", name=pkg) - self.logger.error("Failed to find installed version of packages %s" % (pkg)) - return stats - - # the next two are dispatch functions - - def VerifyEntry(self, entry, modlist = []): - '''Dispatch call to Verify and save state in self.states''' - if not hasattr(self, "Verify%s" % (entry.tag)): - self.logger("Got unsupported entry type %s" % (entry.tag)) - self.states[entry] = False - try: - method = getattr(self, "Verify%s" % (entry.tag)) - # verify state and stash value in state - if entry.tag == 'Package': - self.states[entry] = method(entry, modlist) - else: - self.states[entry] = method(entry) - except: - self.logger.error("Failure in VerifyEntry", exc_info=1) - self.logger.error("Entry: %s" % (Bcfg2.Client.XML.tostring(entry))) - - def InstallEntry(self, entry): - '''Dispatch call to self.Install''' - try: - method = getattr(self, "Install%s"%(entry.tag)) - self.states[entry] = method(entry) - except: - self.logger.error("Failure in InstallEntry", exc_info=1) - - # All remaining operations implement the mechanics of POSIX cfg elements - - def VerifySymLink(self, entry): - '''Verify SymLink Entry''' - try: - sloc = os.readlink(entry.get('name')) - if sloc == entry.get('to'): - return True - self.logger.debug("Symlink %s points to %s, should be %s" % (entry.get('name'), - sloc, entry.get('to'))) - entry.set('current_to', sloc) - return False - except OSError: - entry.set('current_exists', 'false') - return False - - def InstallSymLink(self, entry): - '''Install SymLink Entry''' - self.logger.info("Installing Symlink %s" % (entry.get('name'))) - try: - fmode = os.lstat(entry.get('name'))[ST_MODE] - if S_ISREG(fmode) or S_ISLNK(fmode): - self.logger.debug("Non-directory entry already exists at %s" % (entry.get('name'))) - os.unlink(entry.get('name')) - elif S_ISDIR(fmode): - self.logger.debug("Directory entry already exists at %s" % (entry.get('name'))) - self.saferun("mv %s/ %s.bak" % (entry.get('name'), entry.get('name'))) - else: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Symlink %s cleanup failed" % (entry.get('name'))) - try: - os.symlink(entry.get('to'), entry.get('name')) - return True - except OSError: - return False - - def VerifyDirectory(self, entry): - '''Verify Directory Entry''' - while len(entry.get('perms', '')) < 4: - entry.set('perms', '0' + entry.get('perms', '')) - try: - ondisk = os.stat(entry.get('name')) - except OSError: - entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % - (entry.tag, entry.get('name'))) - return False - try: - owner = pwd.getpwuid(ondisk[ST_UID])[0] - group = grp.getgrgid(ondisk[ST_GID])[0] - except (OSError, KeyError): - self.logger.error('User resolution failing') - owner = 'root' - group = 'root' - perms = oct(os.stat(entry.get('name'))[ST_MODE])[-4:] - if ((owner == entry.get('owner')) and - (group == entry.get('group')) and - (perms == entry.get('perms'))): - return True - else: - if owner != entry.get('owner'): - entry.set('current_owner', owner) - self.logger.debug("%s %s ownership wrong" % (entry.tag, entry.get('name'))) - if group != entry.get('group'): - entry.set('current_group', group) - self.logger.debug("%s %s group wrong" % (entry.tag, entry.get('name'))) - if perms != entry.get('perms'): - entry.set('current_perms', perms) - self.logger.debug("%s %s permissions wrong: are %s should be %s" % - (entry.tag, entry.get('name'), perms, entry.get('perms'))) - return False - - def InstallDirectory(self, entry): - '''Install Directory Entry''' - self.logger.info("Installing Directory %s" % (entry.get('name'))) - try: - fmode = os.lstat(entry.get('name')) - if not S_ISDIR(fmode[ST_MODE]): - self.logger.debug("Found a non-directory entry at %s" % (entry.get('name'))) - try: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Failed to unlink %s" % (entry.get('name'))) - return False - else: - self.logger.debug("Found a pre-existing directory at %s" % (entry.get('name'))) - exists = True - except OSError: - # stat failed - exists = False - - if not exists: - parent = "/".join(entry.get('name').split('/')[:-1]) - if parent: - try: - os.lstat(parent) - except: - self.logger.debug('Creating parent path for directory %s' % (entry.get('name'))) - for idx in xrange(len(parent.split('/')[:-1])): - current = '/'+'/'.join(parent.split('/')[1:2+idx]) - try: - sloc = os.lstat(current) - try: - if not S_ISDIR(sloc[ST_MODE]): - os.unlink(current) - os.mkdir(current) - except OSError: - return False - except OSError: - try: - os.mkdir(current) - except OSError: - return False - - try: - os.mkdir(entry.get('name')) - except OSError: - self.logger.error('Failed to create directory %s' % (entry.get('name'))) - return False - try: - os.chown(entry.get('name'), - pwd.getpwnam(entry.get('owner'))[2], grp.getgrnam(entry.get('group'))[2]) - os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) - return True - except (OSError, KeyError): - self.logger.error('Permission fixup failed for %s' % (entry.get('name'))) - return False - - def VerifyConfigFile(self, entry): - '''Install ConfigFile Entry''' - # configfile verify is permissions check + content check - permissionStatus = self.VerifyDirectory(entry) - if entry.get('encoding', 'ascii') == 'base64': - tempdata = binascii.a2b_base64(entry.text) - elif entry.get('empty', 'false') == 'true': - tempdata = '' - else: - if entry.text == None: - self.logger.error("Cannot verify incomplete ConfigFile %s" % (entry.get('name'))) - return False - tempdata = entry.text - - try: - content = open(entry.get('name')).read() - except IOError: - # file does not exist - return False - contentStatus = content == tempdata - if not contentStatus: - diff = '\n'.join([x for x in difflib.unified_diff(content.split('\n'), tempdata.split('\n'))]) - try: - entry.set("current_diff", xml.sax.saxutils.quoteattr(diff)) - except: - pass - return contentStatus and permissionStatus - - def InstallConfigFile(self, entry): - '''Install ConfigFile Entry''' - if entry.text == None and entry.get('empty', 'false') != 'true': - self.logger.error( - "Incomplete information for ConfigFile %s. Cannot install" % (entry.get('name'))) - return False - self.logger.info("Installing ConfigFile %s" % (entry.get('name'))) - - if self.setup['dryrun']: - return False - parent = "/".join(entry.get('name').split('/')[:-1]) - if parent: - try: - os.lstat(parent) - except: - self.logger.debug('Creating parent path for config file %s' % (entry.get('name'))) - for idx in xrange(len(parent.split('/')[:-1])): - current = '/'+'/'.join(parent.split('/')[1:2+idx]) - try: - sloc = os.lstat(current) - try: - if not S_ISDIR(sloc[ST_MODE]): - os.unlink(current) - os.mkdir(current) - except OSError: - return False - except OSError: - try: - os.mkdir(current) - except OSError: - return False - - # If we get here, then the parent directory should exist - try: - newfile = open("%s.new"%(entry.get('name')), 'w') - if entry.get('encoding', 'ascii') == 'base64': - filedata = binascii.a2b_base64(entry.text) - elif entry.get('empty', 'false') == 'true': - filedata = '' - else: - filedata = entry.text - newfile.write(filedata) - newfile.close() - try: - os.chown(newfile.name, pwd.getpwnam(entry.get('owner'))[2], - grp.getgrnam(entry.get('group'))[2]) - except KeyError: - os.chown(newfile.name, 0, 0) - os.chmod(newfile.name, calcPerms(S_IFREG, entry.get('perms'))) - if entry.get("paranoid", False) and self.setup.get("paranoid", False): - self.saferun("cp %s /var/cache/bcfg2/%s" % (entry.get('name'), entry.get('name'))) - os.rename(newfile.name, entry.get('name')) - return True - except (OSError, IOError), err: - if err.errno == 13: - self.logger.info("Failed to open %s for writing" % (entry.get('name'))) - else: - print err - return False - - def VerifyPackage(self, _, dummy): - '''Dummy package verification method. Cannot succeed''' - return False - - def VerifyPermissions(self, entry): - '''Verify method for abstract permission''' - try: - sinfo = os.stat(entry.get('name')) - except OSError: - self.logger.debug("Entry %s doesn't exist" % entry.get('name')) - entry.set('current_exists', 'false') - return False - # pad out perms if needed - while len(entry.get('perms', '')) < 4: - entry.set('perms', '0' + entry.get('perms', '')) - perms = oct(sinfo[ST_MODE])[-4:] - if perms == entry.get('perms'): - return True - self.logger.debug("Entry %s permissions incorrect" % entry.get('name')) - entry.set('current_perms', perms) - return False - - def InstallPermissions(self, entry): - '''Install method for abstract permission''' - try: - sinfo = os.stat(entry.get('name')) - except OSError: - self.logger.debug("Entry %s doesn't exist" % entry.get('name')) - return False - for ftype in ['DIR', 'REG', 'CHR', 'BLK']: - if getattr(stat, "S_IS%s" % ftype)(sinfo[ST_MODE]): - os.chmod(entry.get('name'), calcPerms(getattr(stat, "S_IF%s" % ftype), entry.get('perms'))) - return True - self.logger.info("Entry %s has unknown file type" % entry.get('name')) - return False - - def VerifyPostInstall(self, _): - '''Postinstall verification method''' - return True - - def HandleBundleDeps(self): - '''Handle bundles depending on what has been modified''' - for entry in [child for child in self.structures if child.tag == 'Bundle']: - bchildren = entry.getchildren() - if [b_ent for b_ent in bchildren if b_ent in self.modified]: - # This bundle has been modified - self.logger.info("%s %s needs update" % (entry.tag, entry.get('name', '???'))) - modfiles = [cfile.get('name') for cfile in bchildren if cfile.tag == 'ConfigFile'] - for child in bchildren: - if child.tag == 'Package': - self.VerifyPackage(child, modfiles) - else: - self.VerifyEntry(child) - if not self.states.has_key(child): - self.logger.error("Could not get state for entry %s: %s" % (child.tag, child.get('name'))) - continue - if not self.states[child]: - self.logger.debug("Reinstalling clobbered entry %s %s" % (child.tag, - child.get('name'))) - self.InstallEntry(child) - self.VerifyEntry(child) - self.logger.debug("Re-checked entry %s %s: %s" % - (child.tag, child.get('name'), self.states[child])) - for postinst in [entry for entry in bchildren if entry.tag == 'PostInstall']: - self.saferun(postinst.get('name')) - for svc in [svc for svc in bchildren if svc.tag == 'Service' and - svc.get('status', 'off') == 'on']: - if self.setup['build']: - # stop services in miniroot - self.saferun('/etc/init.d/%s stop' % svc.get('name')) - else: - self.RestartService(svc) - - for entry in self.structures: - leftovers = [strent for strent in entry.getchildren() if not self.states.get(strent, False)] - if len(leftovers) > 0: - self.logger.info("%s %s incomplete" % (entry.tag, entry.get('name', ""))) - self.logger.info("Incorrect entries:") - self.logger.info(["%s: %s" % (left.tag, left.get('name')) for left in leftovers]) - else: - self.structures[entry] = True - - def HandleExtra(self): - '''deal with extra configuration during installation''' - return False - - def displayWork(self): - '''Display all entries that will be upgraded''' - if self.pkgwork['update']: - self.logger.info("Packages to update:") - self.logger.info([pkg.get('name') for pkg in self.pkgwork['update']]) - if self.pkgwork['add']: - self.logger.info("Packages to add:") - self.logger.info([pkg.get('name') for pkg in self.pkgwork['add']]) - if self.pkgwork['remove']: - self.logger.info("Packages to remove:") - self.logger.info(self.pkgwork['remove']) - if [entry for entry in self.states if not (self.states[entry] or entry.tag == 'Package')]: - self.logger.info("Entries are incorrect:") - self.logger.info(["%s: %s" % (entry.tag, entry.get('name')) - for entry in self.states if not (self.states[entry] - or entry.tag == 'Package')]) - if self.extra_services: - self.logger.info("Services to remove:") - self.logger.info(self.extra_services) - - def PromptUser(self): - '''Prompts user for each entry in interactive mode''' - #get list of entries that need to be updated - #ask user for each entry - work = self.pkgwork['add'] + self.pkgwork['update'] - work += [ent for ent in self.states if ent.tag != 'Package' and not self.states[ent]] - self.iinst = []; - for entry in work: - try: - if raw_input("Would you like to install %s: %s? (y/N): " % (entry.tag, entry.get('name'))) in ['y', 'Y']: - self.iinst.append((entry.tag, entry.get('name'))) - except: - continue - self.logger.info("You chose to install:") - self.logger.info(['%s:%s' % item for item in self.iinst]) - - def Install(self): - '''Correct detected misconfigurations''' - if self.setup['dryrun']: - self.logger.info("Dry-run mode: no changes will be made") - else: - self.logger.info("Updating the system") - self.logger.info("") - self.HandleExtra() - - if self.setup['dryrun'] or self.setup['debug']: - self.displayWork() - if self.setup['dryrun']: - return - - # use quick package ops from here on - self.setup['quick'] = True - - # build up work queue - work = self.pkgwork['add'] + self.pkgwork['update'] - # add non-package entries - work += [ent for ent in self.states if ent.tag != 'Package' and not self.states[ent]] - - if self.setup['interactive']: - work = [entry for entry in work if (entry.tag, entry.get('name')) in self.iinst] - - # Counters - ## Packages left to install - left = len(work) + len(self.pkgwork['remove']) - ## Packages installed in previous iteration - old = left + 1 - ## loop iterations performed - count = 1 - - # Installation loop - while ((0 < left < old) and (count < 20)): - # Print pass info - self.logger.info("Starting pass %s" % (count)) - self.logger.info("%s Entries left" % (len(work))) - if self.setup['bundle']: - self.logger.info("%s new, %s update" % (len(self.pkgwork['add']), len(self.pkgwork['update']))) - else: - self.logger.info("%s new, %s update, %s remove" % - (len(self.pkgwork['add']), len(self.pkgwork['update']), - len(self.pkgwork['remove']))) - - # Update counters - count = count + 1 - old = left - - self.logger.info("Installing non-package entries") - [self.InstallEntry(ent) for ent in work if ent.tag != 'Package'] - - packages = [pkg for pkg in work if pkg.tag == 'Package'] - ptypes = [] - for pkg in packages: - if pkg.get('type') not in ptypes: - ptypes.append(pkg.get('type')) - if packages: - for pkgtype in ptypes: - # try single large install - self.logger.info("Trying single pass package install for pkgtype %s" % pkgtype) - if not self.pkgtool.has_key(pkgtype): - self.logger.info("No support for pkgtype %s" % (pkgtype)) - continue - pkgtool = self.pkgtool[pkgtype] - pkglist = [pkg for pkg in packages if pkg.get('type') == pkgtype] - for field in pkgtool[1][1]: - pkglist = [pkg for pkg in pkglist if pkg.attrib.has_key(field)] - if not pkglist: - self.logger.debug("No complete/installable packages of type %s" % pkgtype) - continue - pkgargs = " ".join([pkgtool[1][0] % tuple([pkg.get(field) for field in pkgtool[1][1]]) - for pkg in pkglist]) - - self.logger.debug("Installing packages: :%s:" % pkgargs) - self.logger.debug("Running command ::%s::" % (pkgtool[0] % pkgargs)) - cmdrc = self.saferun(pkgtool[0] % pkgargs)[0] - - if cmdrc == 0: - self.logger.info("Single Pass Succeded") - # set all package states to true and flush workqueues - pkgnames = [pkg.get('name') for pkg in pkglist] - for entry in [entry for entry in self.states.keys() - if entry.tag == 'Package' and entry.get('type') == pkgtype - and entry.get('name') in pkgnames]: - self.logger.debug('Setting state to true for pkg %s' % (entry.get('name'))) - self.states[entry] = True - [self.pkgwork[listname].remove(entry) for listname in ['add', 'update'] - if self.pkgwork[listname].count(entry)] - self.Refresh() - else: - self.logger.error("Single Pass Failed") - # do single pass installs - self.Refresh() - for pkg in pkglist: - # handle state tracking updates - if self.VerifyPackage(pkg, []): - self.logger.info("Forcing state to true for pkg %s" % (pkg.get('name'))) - self.states[pkg] = True - else: - self.logger.info("Installing pkg %s version %s" % - (pkg.get('name'), pkg.get('version'))) - cmdrc = self.saferun(pkgtool[0] % - (pkgtool[1][0] % - tuple([pkg.get(field) for field in pkgtool[1][1]]))) - if cmdrc[0] == 0: - self.states[pkg] = True - else: - self.logger.error("Failed to install package %s" % (pkg.get('name'))) - for entry in [ent for ent in work if self.states[ent]]: - work.remove(entry) - self.modified.append(entry) - left = len(work) + len(self.pkgwork['remove']) - if self.setup['interactive']: - self.setup['dryrun'] = True - self.HandleBundleDeps() - - def RestartService(self, entry): - '''Restart a service entry''' - if entry.get('status') == 'on': - self.logger.debug('Restarting service %s' % entry.get('name')) - self.saferun('/etc/init.d/%s %s' % (entry.get('name'), entry.get('reload', 'reload'))) - diff --git a/src/lib/Client/__init__.py b/src/lib/Client/__init__.py index f7ba1a537..cc8a25964 100644 --- a/src/lib/Client/__init__.py +++ b/src/lib/Client/__init__.py @@ -1,4 +1,4 @@ '''This contains all Bcfg2 Client modules''' __revision__ = '$Revision$' -__all__ = ["Proxy", "Toolset", "Debian", "Solaris", "Redhat", "Gentoo", "XML"] +__all__ = ["Frame", "Proxy", "Tools", "XML"] diff --git a/src/lib/Server/Component.py b/src/lib/Server/Component.py index f57dd7ccc..f3bd47d34 100644 --- a/src/lib/Server/Component.py +++ b/src/lib/Server/Component.py @@ -137,7 +137,10 @@ class Component(SSLServer, except: self.logger.error("Failed to load ssl key %s" % (keyfile), exc_info=1) raise ComponentInitError - SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) + try: + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) + except TypeError: + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, False, None) self.logRequests = 0 self.port = self.socket.getsockname()[1] self.url = "https://%s:%s" % (socket.gethostname(), self.port) diff --git a/src/lib/Server/Plugins/Crontab.py b/src/lib/Server/Plugins/Crontab.py deleted file mode 100644 index fcdf58f42..000000000 --- a/src/lib/Server/Plugins/Crontab.py +++ /dev/null @@ -1,39 +0,0 @@ -'''This module manages the crontab file for bcfg2''' -__revision__ = '$Revision: 1887 $' - -import binascii, os, socket, Bcfg2.Server.Plugin, random - -class Crontab(Bcfg2.Server.Plugin.Plugin): - '''This Generates a random set of times for the cron.daily entries to run. - The goal is to ensure that our Configuration Server/network does get crushed - all in a 5-10 minute period. -''' - __name__ = 'Crontab' - __version__ = '$Id: Crontab 1887 2006-06-18 02:35:54Z desai $' - __author__ = 'bcfg-dev@mcs.anl.gov' - - def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - try: - self.repository = Bcfg2.Server.Plugin.DirectoryBacked(self.data, self.core.fam) - except OSError, ioerr: - self.logger.error("Failed to load Crontab repository from %s" % (self.data)) - self.logger.error(ioerr) - raise Bcfg2.Server.Plugin.PluginInitError - try: - prefix = open("%s/prefix" % (self.data)).read().strip() - except IOError: - prefix = '' - self.Entries = {'ConfigFile': - {prefix + '/etc/crontab':self.build_crontab}} - - - def build_crontab(self, entry, metadata): - '''This function builds builds a crontab file with a random time for cron.daily''' - random.seed(metadata.hostname) - hour = random.randrange(0,6) - minute = random.randrange(0,59) - entry.text = self.repository.entries['crontab.template'].data% (minute, hour) - permdata = {'owner':'root', 'group':'root', 'perms':'0644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index e803043c9..67cfd8dbc 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -4,7 +4,7 @@ __revision__ = '$Revision$' import logging, os, signal, tempfile, time, xmlrpclib -import Bcfg2.Options, Bcfg2.Client.XML +import Bcfg2.Options, Bcfg2.Client.XML, Bcfg2.Client.Frame try: import Bcfg2.Client.Proxy, Bcfg2.Logging @@ -83,36 +83,6 @@ class Client: print "cannot use -n and -r together" raise SystemExit, 1 - def load_toolset(self, toolsetName): - '''Import client toolset modules''' - - toolset_packages = { - 'debian': "Bcfg2.Client.Debian", - 'rh': "Bcfg2.Client.Redhat", - 'solaris': "Bcfg2.Client.Solaris" - } - - if toolset_packages.has_key(toolsetName): - toolset_class = toolset_packages[toolsetName] - else: - toolset_class = toolsetName - - try: - mod = __import__(toolset_class, globals(), locals(), ['*']) - except ImportError: - self.fatal_error("Failed to load server-specified toolset: %s" % (toolsetName)) - except: - self.logger.error("Failed to import toolset %s" % (toolsetName), exc_info=1) - self.fatal_error("Cannot continue") - - try: - self.toolset = mod.ToolsetImpl(self.config, self.setup) - - self.logger.debug("Selected %s toolset..." % (toolsetName)) - except: - self.logger.error("Failed to instantiate toolset: %s" % (toolsetName), exc_info=1) - raise SystemExit, 1 - def run_probe(self, probe): '''Execute probe''' name = probe.get('name') @@ -138,6 +108,10 @@ class Client: self.logger.error("Fatal error: %s" % (message)) raise SystemExit, 1 + def get_config(self): + '''Either download the config from the server or read it from file''' + + def run(self): ''' Perform client execution phase ''' times = {} @@ -228,54 +202,13 @@ class Client: if self.config.tag == 'error': self.fatal_error("server error: %s" % (self.config.text)) - # Get toolset from server - try: - toolsetName = self.config.get('toolset', 'Toolset') - except: - self.fatal_error("server did not specify a toolset") - - if self.setup['bundle']: - replacement_xml = Bcfg2.Client.XML.Element("Configuration", version='2.0') - for child in self.config.getchildren(): - if ((child.tag == 'Bundle') and - (child.attrib['name'] == self.setup['bundle'])): - replacement_xml.append(child) - self.config = replacement_xml - - # Create toolset handle - self.load_toolset(toolsetName) - - times['initialization'] = time.time() - - # verify state - self.toolset.Inventory() - - times['inventory'] = time.time() - - # summarize current state - self.toolset.CondDisplayState('initial') - - # run bcfg in interactive mode - if self.setup['interactive']: - self.toolset.PromptUser() - - # install incorrect aspects of configuration - self.toolset.Install() - - self.toolset.CondDisplayState('final') + self.tools = Bcfg2.Client.Frame.Frame(self.config, self.setup, times) - times['install'] = time.time() - times['finished'] = time.time() + self.tools.Execute() - if not self.setup['file'] and not self.setup['bundle']: + if not self.setup['file']: # upload statistics - feedback = Bcfg2.Client.XML.Element("upload-statistics") - timeinfo = Bcfg2.Client.XML.Element("OpStamps") - for (event, timestamp) in times.iteritems(): - timeinfo.set(event, str(timestamp)) - stats = self.toolset.GenerateStats(__revision__) - stats.append(timeinfo) - feedback.append(stats) + feedback = self.tools.GenerateStats() try: proxy.RecvStats(Bcfg2.Client.XML.tostring(feedback)) @@ -285,4 +218,5 @@ class Client: if __name__ == '__main__': signal.signal(signal.SIGINT, cb_sigint_handler) - Client().run() + client = Client() + client.run() -- cgit v1.2.3-1-g7c22