From ebe7542db7217c2fac3d7111e80f94caedfb69e2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 16 Jan 2013 13:28:06 -0500 Subject: added module-level OptionParser to avoid passing it as an argument or global all over --- src/lib/Bcfg2/Client/Client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index 45e0b64e6..ea898c42b 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -22,12 +22,12 @@ from subprocess import Popen, PIPE class Client(object): """ The main Bcfg2 client class """ - def __init__(self, setup): + def __init__(self): self.toolset = None self.tools = None self.config = None self._proxy = None - self.setup = setup + self.setup = Bcfg2.Options.get_option_parser() if self.setup['debug']: level = logging.DEBUG -- cgit v1.2.3-1-g7c22 From aece6f8901711fa9e662b63f4f6b12cb90b84503 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 30 Oct 2012 09:05:23 -0400 Subject: removed deprecated PostInstall support --- src/lib/Bcfg2/Client/Tools/Action.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index b1a897c81..5aada9c0b 100644 --- a/src/lib/Bcfg2/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -11,9 +11,8 @@ from Bcfg2.Compat import input # pylint: disable=W0622 class Action(Bcfg2.Client.Tools.Tool): """Implement Actions""" name = 'Action' - __handles__ = [('PostInstall', None), ('Action', None)] - __req__ = {'PostInstall': ['name'], - 'Action': ['name', 'timing', 'when', 'command', 'status']} + __handles__ = [('Action', None)] + __req__ = {'Action': ['name', 'timing', 'when', 'command', 'status']} def _action_allowed(self, action): """ Return true if the given action is allowed to be run by @@ -66,28 +65,14 @@ class Action(Bcfg2.Client.Tools.Tool): """Actions always verify true.""" return True - def VerifyPostInstall(self, dummy, _): - """Actions always verify true.""" - return True - def InstallAction(self, entry): """Run actions as pre-checks for bundle installation.""" if entry.get('timing') != 'post': return self.RunAction(entry) return True - def InstallPostInstall(self, entry): - """ Install a deprecated PostInstall entry """ - self.logger.warning("Installing deprecated PostInstall entry %s" % - entry.get("name")) - return self.InstallAction(entry) - def BundleUpdated(self, bundle, states): """Run postinstalls when bundles have been updated.""" - for postinst in bundle.findall("PostInstall"): - if not self._action_allowed(postinst): - continue - self.cmd.run(postinst.get('name')) for action in bundle.findall("Action"): if action.get('timing') in ['post', 'both']: if not self._action_allowed(action): -- cgit v1.2.3-1-g7c22 From c0b2afa6e86557d5d206a64bd886034f432eee8d Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 31 Oct 2012 12:46:21 -0400 Subject: removed deprecated tools: RPMng, YUM24, YUMng --- src/lib/Bcfg2/Client/Tools/RPM.py | 2 - src/lib/Bcfg2/Client/Tools/RPMng.py | 9 - src/lib/Bcfg2/Client/Tools/YUM.py | 2 +- src/lib/Bcfg2/Client/Tools/YUM24.py | 404 ------------------------------------ src/lib/Bcfg2/Client/Tools/YUMng.py | 9 - 5 files changed, 1 insertion(+), 425 deletions(-) delete mode 100644 src/lib/Bcfg2/Client/Tools/RPMng.py delete mode 100644 src/lib/Bcfg2/Client/Tools/YUM24.py delete mode 100644 src/lib/Bcfg2/Client/Tools/YUMng.py (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py index 3d93149ff..0964fdb80 100644 --- a/src/lib/Bcfg2/Client/Tools/RPM.py +++ b/src/lib/Bcfg2/Client/Tools/RPM.py @@ -26,8 +26,6 @@ class RPM(Bcfg2.Client.Tools.PkgTool): __new_gpg_ireq__ = {'Package': ['name'], 'Instance': ['version', 'release']} - conflicts = ['RPMng'] - pkgtype = 'rpm' pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"])) diff --git a/src/lib/Bcfg2/Client/Tools/RPMng.py b/src/lib/Bcfg2/Client/Tools/RPMng.py deleted file mode 100644 index 0f0e4c700..000000000 --- a/src/lib/Bcfg2/Client/Tools/RPMng.py +++ /dev/null @@ -1,9 +0,0 @@ -""" RPM driver called 'RPMng' for backwards compat """ - -from Bcfg2.Client.Tools.RPM import RPM - - -class RPMng(RPM): - """ RPM driver called 'RPMng' for backwards compat """ - deprecated = True - name = "RPM" diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py index c8414b4b2..a0dfe6dd9 100644 --- a/src/lib/Bcfg2/Client/Tools/YUM.py +++ b/src/lib/Bcfg2/Client/Tools/YUM.py @@ -126,7 +126,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool): __req__ = {'Package': ['name'], 'Path': ['type']} - conflicts = ['YUM24', 'RPM', 'RPMng', 'YUMng'] + conflicts = ['RPM'] def __init__(self, logger, setup, config): self.yumbase = self._loadYumBase(setup=setup, logger=logger) diff --git a/src/lib/Bcfg2/Client/Tools/YUM24.py b/src/lib/Bcfg2/Client/Tools/YUM24.py deleted file mode 100644 index cd25ecf37..000000000 --- a/src/lib/Bcfg2/Client/Tools/YUM24.py +++ /dev/null @@ -1,404 +0,0 @@ -"""This provides bcfg2 support for yum.""" - -import copy -import os.path -import sys -import yum -import Bcfg2.Client.XML -from Bcfg2.Client.Tools.RPM import RPM - - -def build_yname(pkgname, inst): - """Build yum appropriate package name.""" - ypname = pkgname - if inst.get('version') != 'any': - ypname += '-' - if inst.get('epoch', False): - ypname += "%s:" % inst.get('epoch') - if inst.get('version', False) and inst.get('version') != 'any': - ypname += "%s" % (inst.get('version')) - if inst.get('release', False) and inst.get('release') != 'any': - ypname += "-%s" % (inst.get('release')) - if inst.get('arch', False) and inst.get('arch') != 'any': - ypname += ".%s" % (inst.get('arch')) - return ypname - - -class YUM24(RPM): - """Support for Yum packages.""" - pkgtype = 'yum' - deprecated = True - __execs__ = ['/usr/bin/yum', '/var/lib/rpm'] - __handles__ = [('Package', 'yum'), - ('Package', 'rpm'), - ('Path', 'ignore')] - - __req__ = {'Package': ['name', 'version']} - __ireq__ = {'Package': ['name']} - #__ireq__ = {'Package': ['name', 'version']} - - __new_req__ = {'Package': ['name'], - 'Instance': ['version', 'release', 'arch']} - __new_ireq__ = {'Package': ['name'], \ - 'Instance': []} - #__new_ireq__ = {'Package': ['name', 'uri'], \ - # 'Instance': ['simplefile', 'version', 'release', 'arch']} - - __gpg_req__ = {'Package': ['name', 'version']} - __gpg_ireq__ = {'Package': ['name', 'version']} - - __new_gpg_req__ = {'Package': ['name'], - 'Instance': ['version', 'release']} - __new_gpg_ireq__ = {'Package': ['name'], - 'Instance': ['version', 'release']} - - def __init__(self, logger, setup, config): - RPM.__init__(self, logger, setup, config) - self.__important__ = self.__important__ + \ - [entry.get('name') for struct in config \ - for entry in struct \ - if entry.tag in ['Path', 'ConfigFile'] and \ - (entry.get('name').startswith('/etc/yum.d') \ - or entry.get('name').startswith('/etc/yum.repos.d')) \ - or entry.get('name') == '/etc/yum.conf'] - self.autodep = setup.get("yum24_autodep") - self.yum_avail = dict() - self.yum_installed = dict() - self.yb = yum.YumBase() - self.yb.doConfigSetup() - self.yb.doTsSetup() - self.yb.doRpmDBSetup() - yup = self.yb.doPackageLists(pkgnarrow='updates') - if hasattr(self.yb.rpmdb, 'pkglist'): - yinst = self.yb.rpmdb.pkglist - else: - yinst = self.yb.rpmdb.getPkgList() - for dest, source in [(self.yum_avail, yup.updates), - (self.yum_installed, yinst)]: - for pkg in source: - if dest is self.yum_avail: - pname = pkg.name - data = {pkg.arch: (pkg.epoch, pkg.version, pkg.release)} - else: - pname = pkg[0] - if pkg[1] is None: - a = 'noarch' - else: - a = pkg[1] - if pkg[2] is None: - e = '0' - else: - e = pkg[2] - data = {a: (e, pkg[3], pkg[4])} - if pname in dest: - dest[pname].update(data) - else: - dest[pname] = dict(data) - - def VerifyPackage(self, entry, modlist): - pinned_version = None - if entry.get('version', False) == 'auto': - # old style entry; synthesize Instances from current installed - if entry.get('name') not in self.yum_installed and \ - entry.get('name') not in self.yum_avail: - # new entry; fall back to default - entry.set('version', 'any') - else: - data = copy.copy(self.yum_installed[entry.get('name')]) - if entry.get('name') in self.yum_avail: - # installed but out of date - data.update(self.yum_avail[entry.get('name')]) - for (arch, (epoch, vers, rel)) in list(data.items()): - x = Bcfg2.Client.XML.SubElement(entry, "Instance", - name=entry.get('name'), - version=vers, arch=arch, - release=rel, epoch=epoch) - if 'verify_flags' in entry.attrib: - x.set('verify_flags', entry.get('verify_flags')) - if 'verify' in entry.attrib: - x.set('verify', entry.get('verify')) - - if entry.get('type', False) == 'yum': - # Check for virtual provides or packages. If we don't have - # this package use Yum to resolve it to a real package name - knownPkgs = list(self.yum_installed.keys()) + list(self.yum_avail.keys()) - if entry.get('name') not in knownPkgs: - # If the package name matches something installed - # or available the that's the correct package. - try: - pkgDict = dict([(i.name, i) for i in \ - self.yb.returnPackagesByDep(entry.get('name'))]) - except yum.Errors.YumBaseError: - e = sys.exc_info()[1] - self.logger.error('Yum Error Depsolving for %s: %s' % \ - (entry.get('name'), str(e))) - pkgDict = {} - - if len(pkgDict) > 1: - # What do we do with multiple packages? - s = "YUM24: returnPackagesByDep(%s) returned many packages" - self.logger.info(s % entry.get('name')) - s = "YUM24: matching packages: %s" - self.logger.info(s % str(list(pkgDict.keys()))) - pkgs = set(pkgDict.keys()) & set(self.yum_installed.keys()) - if len(pkgs) > 0: - # Virtual packages matches an installed real package - pkg = pkgDict[pkgs.pop()] - s = "YUM24: chosing: %s" % pkg.name - self.logger.info(s) - else: - # What's the right package? This will fail verify - # and Yum should Do The Right Thing on package install - pkg = None - elif len(pkgDict) == 1: - pkg = list(pkgDict.values())[0] - else: # len(pkgDict) == 0 - s = "YUM24: returnPackagesByDep(%s) returned no results" - self.logger.info(s % entry.get('name')) - pkg = None - - if pkg is not None: - s = "YUM24: remapping virtual package %s to %s" - self.logger.info(s % (entry.get('name'), pkg.name)) - entry.set('name', pkg.name) - - return RPM.VerifyPackage(self, entry, modlist) - - def Install(self, packages, states): - """ - Try and fix everything that YUM24.VerifyPackages() found wrong for - each Package Entry. This can result in individual RPMs being - installed (for the first time), deleted, downgraded - or upgraded. - - NOTE: YUM can not reinstall a package that it thinks is already - installed. - - packages is a list of Package Elements that has - states[] == False - - The following effects occur: - - states{} is conditionally updated for each package. - - self.installed{} is rebuilt, possibly multiple times. - - self.instance_status{} is conditionally updated for each instance - of a package. - - Each package will be added to self.modified[] if its states{} - entry is set to True. - - """ - self.logger.info('Running YUM24.Install()') - - install_pkgs = [] - gpg_keys = [] - upgrade_pkgs = [] - - # Remove extra instances. - # Can not reverify because we don't have a package entry. - if len(self.extra_instances) > 0: - if (self.setup.get('remove') == 'all' or \ - self.setup.get('remove') == 'packages'): - self.Remove(self.extra_instances) - else: - self.logger.info("The following extra package instances will be removed by the '-r' option:") - for pkg in self.extra_instances: - for inst in pkg: - self.logger.info(" %s %s" % \ - ((pkg.get('name'), self.str_evra(inst)))) - - # Figure out which instances of the packages actually need something - # doing to them and place in the appropriate work 'queue'. - for pkg in packages: - insts = [pinst for pinst in pkg \ - if pinst.tag in ['Instance', 'Package']] - if insts: - for inst in insts: - if self.FixInstance(inst, self.instance_status[inst]): - if self.instance_status[inst].get('installed', False) \ - == False: - if pkg.get('name') == 'gpg-pubkey': - gpg_keys.append(inst) - else: - install_pkgs.append(inst) - elif self.instance_status[inst].get('version_fail', \ - False) == True: - upgrade_pkgs.append(inst) - else: - install_pkgs.append(pkg) - - # Install GPG keys. - # Alternatively specify the required keys using 'gpgkey' in the - # repository definition in yum.conf. YUM will install the keys - # automatically. - if len(gpg_keys) > 0: - for inst in gpg_keys: - self.logger.info("Installing GPG keys.") - if inst.get('simplefile') is None: - self.logger.error("GPG key has no simplefile attribute") - continue - key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - cmdrc, output = self.cmd.run("rpm --import %s" % key_arg) - if cmdrc != 0: - self.logger.debug("Unable to install %s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - else: - self.logger.debug("Installed %s-%s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - inst.get('version'), inst.get('release'))) - self.RefreshPackages() - self.gpg_keyids = self.getinstalledgpg() - pkg = self.instance_status[gpg_keys[0]].get('pkg') - states[pkg] = self.VerifyPackage(pkg, []) - - # Install packages. - if len(install_pkgs) > 0: - self.logger.info("Attempting to install packages") - - if self.autodep: - pkgtool = "/usr/bin/yum -d0 -y install %s" - else: - pkgtool = "/usr/bin/yum -d0 install %s" - - install_args = [] - for inst in install_pkgs: - pkg_arg = self.instance_status[inst].get('pkg').get('name') - install_args.append(build_yname(pkg_arg, inst)) - - cmdrc, output = self.cmd.run(pkgtool % " ".join(install_args)) - if cmdrc == 0: - # The yum command succeeded. All packages installed. - self.logger.info("Single Pass for Install Succeeded") - self.RefreshPackages() - else: - # The yum command failed. No packages installed. - # Try installing instances individually. - self.logger.error("Single Pass Install of Packages Failed") - installed_instances = [] - for inst in install_pkgs: - pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst) - - cmdrc, output = self.cmd.run(pkgtool % pkg_arg) - if cmdrc == 0: - installed_instances.append(inst) - else: - self.logger.debug("%s %s would not install." % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - self.RefreshPackages() - - # Fix upgradeable packages. - if len(upgrade_pkgs) > 0: - self.logger.info("Attempting to upgrade packages") - - if self.autodep: - pkgtool = "/usr/bin/yum -d0 -y update %s" - else: - pkgtool = "/usr/bin/yum -d0 update %s" - - upgrade_args = [] - for inst in upgrade_pkgs: - pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst) - upgrade_args.append(pkg_arg) - - cmdrc, output = self.cmd.run(pkgtool % " ".join(upgrade_args)) - if cmdrc == 0: - # The yum command succeeded. All packages installed. - self.logger.info("Single Pass for Install Succeeded") - self.RefreshPackages() - else: - # The yum command failed. No packages installed. - # Try installing instances individually. - self.logger.error("Single Pass Install of Packages Failed") - installed_instances = [] - for inst in upgrade_pkgs: - pkg_arg = build_yname(self.instance_status[inst].get('pkg').get('name'), inst) - cmdrc, output = self.cmd.run(pkgtool % pkg_arg) - if cmdrc == 0: - installed_instances.append(inst) - else: - self.logger.debug("%s %s would not install." % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - - self.RefreshPackages() - - if not self.setup['kevlar']: - for pkg_entry in [p for p in packages if self.canVerify(p)]: - self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name'))) - states[pkg_entry] = self.VerifyPackage(pkg_entry, \ - self.modlists.get(pkg_entry, [])) - - for entry in [ent for ent in packages if states[ent]]: - self.modified.append(entry) - - def Remove(self, packages): - """ - Remove specified entries. - - packages is a list of Package Entries with Instances generated - by FindExtra(). - """ - self.logger.debug('Running YUM24.Remove()') - - if self.autodep: - pkgtool = "/usr/bin/yum -d0 -y erase %s" - else: - pkgtool = "/usr/bin/yum -d0 erase %s" - - erase_args = [] - for pkg in packages: - for inst in pkg: - if pkg.get('name') != 'gpg-pubkey': - pkg_arg = pkg.get('name') + '-' - if inst.get('epoch', False): - pkg_arg = pkg_arg + inst.get('epoch') + ':' - pkg_arg = pkg_arg + inst.get('version') + '-' + inst.get('release') - if inst.get('arch', False): - pkg_arg = pkg_arg + '.' + inst.get('arch') - erase_args.append(pkg_arg) - else: - pkgspec = {'name': pkg.get('name'), - 'version': inst.get('version'), - 'release': inst.get('release')} - self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ - % (pkgspec.get('name'), self.str_evra(pkgspec))) - self.logger.info(" This package will be deleted in a future version of the YUM24 driver.") - - cmdrc, output = self.cmd.run(pkgtool % " ".join(erase_args)) - if cmdrc == 0: - self.modified += packages - for pkg in erase_args: - self.logger.info("Deleted %s" % (pkg)) - else: - self.logger.info("Bulk erase failed with errors:") - self.logger.debug("Erase results = %s" % output) - self.logger.info("Attempting individual erase for each package.") - for pkg in packages: - pkg_modified = False - for inst in pkg: - if pkg.get('name') != 'gpg-pubkey': - pkg_arg = pkg.get('name') + '-' - if 'epoch' in inst.attrib: - pkg_arg = pkg_arg + inst.get('epoch') + ':' - pkg_arg = pkg_arg + inst.get('version') + '-' + inst.get('release') - if 'arch' in inst.attrib: - pkg_arg = pkg_arg + '.' + inst.get('arch') - else: - self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ - % (pkg.get('name'), self.str_evra(pkg))) - self.logger.info(" This package will be deleted in a future version of the YUM24 driver.") - continue - - cmdrc, output = self.cmd.run(self.pkgtool % pkg_arg) - if cmdrc == 0: - pkg_modified = True - self.logger.info("Deleted %s" % pkg_arg) - else: - self.logger.error("unable to delete %s" % pkg_arg) - self.logger.debug("Failure = %s" % output) - if pkg_modified == True: - self.modified.append(pkg) - - self.RefreshPackages() - self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/YUMng.py b/src/lib/Bcfg2/Client/Tools/YUMng.py deleted file mode 100644 index 22fbba537..000000000 --- a/src/lib/Bcfg2/Client/Tools/YUMng.py +++ /dev/null @@ -1,9 +0,0 @@ -""" YUM driver called 'YUMng' for backwards compat """ - -from Bcfg2.Client.Tools.YUM import YUM - - -class YUMng(YUM): - """ YUM driver called 'YUMng' for backwards compat """ - deprecated = True - conflicts = ['YUM24', 'RPM', 'RPMng'] -- cgit v1.2.3-1-g7c22 From 9be9cfec322518f764be9766b27d24132fc6a66f Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 16 Jan 2013 13:28:06 -0500 Subject: added module-level OptionParser to avoid passing it as an argument or global all over --- src/lib/Bcfg2/Client/Client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index 45e0b64e6..ea898c42b 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -22,12 +22,12 @@ from subprocess import Popen, PIPE class Client(object): """ The main Bcfg2 client class """ - def __init__(self, setup): + def __init__(self): self.toolset = None self.tools = None self.config = None self._proxy = None - self.setup = setup + self.setup = Bcfg2.Options.get_option_parser() if self.setup['debug']: level = logging.DEBUG -- cgit v1.2.3-1-g7c22 From 25cb6db5ccb0c8e8302c220a90344a95baf3909b Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 5 Feb 2013 14:04:09 -0500 Subject: moved some libraries in Bcfg2/ into more specific (Server/ or Client/) places --- src/lib/Bcfg2/Client/Client.py | 22 +-- src/lib/Bcfg2/Client/Proxy.py | 358 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+), 11 deletions(-) create mode 100644 src/lib/Bcfg2/Client/Proxy.py (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index ea898c42b..e26a6d07a 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -8,10 +8,10 @@ import fcntl import socket import logging import tempfile -import Bcfg2.Proxy import Bcfg2.Logger import Bcfg2.Options import Bcfg2.Client.XML +import Bcfg2.Client.Proxy import Bcfg2.Client.Frame import Bcfg2.Client.Tools from Bcfg2.Compat import xmlrpclib @@ -121,7 +121,7 @@ class Client(object): def proxy(self): """ get an XML-RPC proxy to the server """ if self._proxy is None: - self._proxy = Bcfg2.Proxy.ComponentProxy( + self._proxy = Bcfg2.Client.Proxy.ComponentProxy( self.setup['server'], self.setup['user'], self.setup['password'], @@ -141,8 +141,8 @@ class Client(object): try: probes = Bcfg2.Client.XML.XML(str(self.proxy.GetProbes())) - except (Bcfg2.Proxy.ProxyError, - Bcfg2.Proxy.CertificateError, + except (Bcfg2.Client.Proxy.ProxyError, + Bcfg2.Client.Proxy.CertificateError, socket.gaierror, socket.error): err = sys.exc_info()[1] @@ -165,7 +165,7 @@ class Client(object): self.proxy.RecvProbeData(Bcfg2.Client.XML.tostring( probedata, xml_declaration=False).decode('UTF-8')) - except Bcfg2.Proxy.ProxyError: + except Bcfg2.Client.Proxy.ProxyError: err = sys.exc_info()[1] self.fatal_error("Failed to upload probe data: %s" % err) @@ -191,7 +191,7 @@ class Client(object): if self.setup['profile']: try: self.proxy.AssertProfile(self.setup['profile']) - except Bcfg2.Proxy.ProxyError: + except Bcfg2.Client.Proxy.ProxyError: err = sys.exc_info()[1] self.fatal_error("Failed to set client profile: %s" % err) @@ -206,8 +206,8 @@ class Client(object): "client version") else: self.logger.error("Failed to declare version: %s" % err) - except (Bcfg2.Proxy.ProxyError, - Bcfg2.Proxy.CertificateError, + except (Bcfg2.Client.Proxy.ProxyError, + Bcfg2.Client.Proxy.CertificateError, socket.gaierror, socket.error): err = sys.exc_info()[1] @@ -221,13 +221,13 @@ class Client(object): self.proxy.GetDecisionList(self.setup['decision']) self.logger.info("Got decision list from server:") self.logger.info(self.setup['decision_list']) - except Bcfg2.Proxy.ProxyError: + except Bcfg2.Client.Proxy.ProxyError: err = sys.exc_info()[1] self.fatal_error("Failed to get decision list: %s" % err) try: rawconfig = self.proxy.GetConfig().encode('UTF-8') - except Bcfg2.Proxy.ProxyError: + except Bcfg2.Client.Proxy.ProxyError: err = sys.exc_info()[1] self.fatal_error("Failed to download configuration from " "Bcfg2: %s" % err) @@ -324,7 +324,7 @@ class Client(object): self.proxy.RecvStats(Bcfg2.Client.XML.tostring( feedback, xml_declaration=False).decode('UTF-8')) - except Bcfg2.Proxy.ProxyError: + except Bcfg2.Client.Proxy.ProxyError: err = sys.exc_info()[1] self.logger.error("Failed to upload configuration statistics: " "%s" % err) diff --git a/src/lib/Bcfg2/Client/Proxy.py b/src/lib/Bcfg2/Client/Proxy.py new file mode 100644 index 000000000..1276c3ce9 --- /dev/null +++ b/src/lib/Bcfg2/Client/Proxy.py @@ -0,0 +1,358 @@ +import logging +import re +import socket + +# The ssl module is provided by either Python 2.6 or a separate ssl +# package that works on older versions of Python (see +# http://pypi.python.org/pypi/ssl). If neither can be found, look for +# M2Crypto instead. +try: + import ssl + SSL_LIB = 'py26_ssl' + SSL_ERROR = ssl.SSLError +except ImportError: + from M2Crypto import SSL + import M2Crypto.SSL.Checker + SSL_LIB = 'm2crypto' + SSL_ERROR = SSL.SSLError + +import sys +import time + +# Compatibility imports +from Bcfg2.Compat import httplib, xmlrpclib, urlparse + +version = sys.version_info[:2] +has_py26 = version >= (2, 6) + +__all__ = ["ComponentProxy", + "RetryMethod", + "SSLHTTPConnection", + "XMLRPCTransport"] + + +class ProxyError(Exception): + """ ProxyError provides a consistent reporting interface to + the various xmlrpclib errors that might arise (mainly + ProtocolError and Fault) """ + def __init__(self, err): + msg = None + if isinstance(err, xmlrpclib.ProtocolError): + # cut out the password in the URL + url = re.sub(r'([^:]+):(.*?)@([^@]+:\d+/)', r'\1:******@\3', + err.url) + msg = "XML-RPC Protocol Error for %s: %s (%s)" % (url, + err.errmsg, + err.errcode) + elif isinstance(err, xmlrpclib.Fault): + msg = "XML-RPC Fault: %s (%s)" % (err.faultString, + err.faultCode) + else: + msg = str(err) + Exception.__init__(self, msg) + +class CertificateError(Exception): + def __init__(self, commonName): + self.commonName = commonName + def __str__(self): + return ("Got unallowed commonName %s from server" + % self.commonName) + +_orig_Method = xmlrpclib._Method + +class RetryMethod(xmlrpclib._Method): + """Method with error handling and retries built in.""" + log = logging.getLogger('xmlrpc') + max_retries = 3 + retry_delay = 1 + + def __call__(self, *args): + for retry in range(self.max_retries): + if retry >= self.max_retries - 1: + final = True + else: + final = False + msg = None + try: + return _orig_Method.__call__(self, *args) + except xmlrpclib.ProtocolError: + err = sys.exc_info()[1] + msg = "Server failure: Protocol Error: %s %s" % \ + (err.errcode, err.errmsg) + except xmlrpclib.Fault: + msg = sys.exc_info()[1] + except socket.error: + err = sys.exc_info()[1] + if hasattr(err, 'errno') and err.errno == 336265218: + msg = "SSL Key error: %s" % err + elif hasattr(err, 'errno') and err.errno == 185090050: + msg = "SSL CA error: %s" % err + elif final: + msg = "Server failure: %s" % err + except CertificateError: + err = sys.exc_info()[1] + msg = "Got unallowed commonName %s from server" % \ + err.commonName + except KeyError: + err = sys.exc_info()[1] + msg = "Server disallowed connection: %s" % err + except ProxyError: + err = sys.exc_info()[1] + msg = err + except: + raise + etype, err = sys.exc_info()[:2] + msg = "Unknown failure: %s (%s)" % (err, etype.__name__) + if msg: + if final: + self.log.error(msg) + raise ProxyError(msg) + else: + self.log.info(msg) + time.sleep(self.retry_delay) + +xmlrpclib._Method = RetryMethod + + +class SSLHTTPConnection(httplib.HTTPConnection): + """Extension of HTTPConnection that + implements SSL and related behaviors. + """ + + def __init__(self, host, port=None, strict=None, timeout=90, key=None, + cert=None, ca=None, scns=None, protocol='xmlrpc/ssl'): + """Initializes the `httplib.HTTPConnection` object and stores security + parameters + + Parameters + ---------- + host : string + Name of host to contact + port : int, optional + Port on which to contact the host. If none is specified, + the default port of 80 will be used unless the `host` + string has a port embedded in the form host:port. + strict : Boolean, optional + Passed to the `httplib.HTTPConnection` constructor and if + True, causes the `BadStatusLine` exception to be raised if + the status line cannot be parsed as a valid HTTP 1.0 or + 1.1 status. + timeout : int, optional + Causes blocking operations to timeout after `timeout` + seconds. + key : string, optional + The file system path to the local endpoint's SSL key. May + specify the same file as `cert` if using a file that + contains both. See + http://docs.python.org/library/ssl.html#ssl-certificates + for details. Required if using xmlrpc/ssl with client + certificate authentication. + cert : string, optional + The file system path to the local endpoint's SSL + certificate. May specify the same file as `cert` if using + a file that contains both. See + http://docs.python.org/library/ssl.html#ssl-certificates + for details. Required if using xmlrpc/ssl with client + certificate authentication. + ca : string, optional + The file system path to a set of concatenated certificate + authority certs, which are used to validate certificates + passed from the other end of the connection. + scns : array-like, optional + List of acceptable server commonNames. The peer cert's + common name must appear in this list, otherwise the + connect() call will throw a `CertificateError`. + protocol : {'xmlrpc/ssl', 'xmlrpc/tlsv1'}, optional + Communication protocol to use. + + """ + if not has_py26: + httplib.HTTPConnection.__init__(self, host, port, strict) + else: + httplib.HTTPConnection.__init__(self, host, port, strict, timeout) + self.logger = logging.getLogger("%s.%s" % (self.__class__.__module__, + self.__class__.__name__)) + + self.key = key + self.cert = cert + self.ca = ca + self.scns = scns + self.protocol = protocol + self.timeout = timeout + + def connect(self): + """Initiates a connection using previously set attributes.""" + if SSL_LIB == 'py26_ssl': + self._connect_py26ssl() + elif SSL_LIB == 'm2crypto': + self._connect_m2crypto() + else: + raise Exception("No SSL module support") + + def _connect_py26ssl(self): + """Initiates a connection using the ssl module.""" + # check for IPv6 + hostip = socket.getaddrinfo(self.host, + self.port, + socket.AF_UNSPEC, + socket.SOCK_STREAM)[0][4][0] + if ':' in hostip: + rawsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + else: + rawsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if self.protocol == 'xmlrpc/ssl': + ssl_protocol_ver = ssl.PROTOCOL_SSLv23 + elif self.protocol == 'xmlrpc/tlsv1': + ssl_protocol_ver = ssl.PROTOCOL_TLSv1 + else: + self.logger.error("Unknown protocol %s" % (self.protocol)) + raise Exception("unknown protocol %s" % self.protocol) + if self.ca: + other_side_required = ssl.CERT_REQUIRED + else: + other_side_required = ssl.CERT_NONE + self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.") + if self.cert and not self.key: + self.logger.warning("SSL cert specfied, but no key. Cannot authenticate this client with SSL.") + self.cert = None + if self.key and not self.cert: + self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.") + self.key = None + + rawsock.settimeout(self.timeout) + self.sock = ssl.SSLSocket(rawsock, cert_reqs=other_side_required, + ca_certs=self.ca, suppress_ragged_eofs=True, + keyfile=self.key, certfile=self.cert, + ssl_version=ssl_protocol_ver) + self.sock.connect((self.host, self.port)) + peer_cert = self.sock.getpeercert() + if peer_cert and self.scns: + scn = [x[0][1] for x in peer_cert['subject'] if x[0][0] == 'commonName'][0] + if scn not in self.scns: + raise CertificateError(scn) + self.sock.closeSocket = True + + def _connect_m2crypto(self): + """Initiates a connection using the M2Crypto module.""" + + if self.protocol == 'xmlrpc/ssl': + ctx = SSL.Context('sslv23') + elif self.protocol == 'xmlrpc/tlsv1': + ctx = SSL.Context('tlsv1') + else: + self.logger.error("Unknown protocol %s" % (self.protocol)) + raise Exception("unknown protocol %s" % self.protocol) + + if self.ca: + # Use the certificate authority to validate the cert + # presented by the server + ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9) + if ctx.load_verify_locations(self.ca) != 1: + raise Exception('No CA certs') + else: + self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.") + + if self.cert and self.key: + # A cert/key is defined, use them to support client + # authentication to the server + ctx.load_cert(self.cert, self.key) + elif self.cert: + self.logger.warning("SSL cert specfied, but no key. Cannot authenticate this client with SSL.") + elif self.key: + self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.") + + self.sock = SSL.Connection(ctx) + if re.match('\\d+\\.\\d+\\.\\d+\\.\\d+', self.host): + # host is ip address + try: + hostname = socket.gethostbyaddr(self.host)[0] + except: + # fall back to ip address + hostname = self.host + else: + hostname = self.host + try: + self.sock.connect((hostname, self.port)) + # automatically checks cert matches host + except M2Crypto.SSL.Checker.WrongHost: + wr = sys.exc_info()[1] + raise CertificateError(wr) + + +class XMLRPCTransport(xmlrpclib.Transport): + def __init__(self, key=None, cert=None, ca=None, + scns=None, use_datetime=0, timeout=90): + if hasattr(xmlrpclib.Transport, '__init__'): + xmlrpclib.Transport.__init__(self, use_datetime) + self.key = key + self.cert = cert + self.ca = ca + self.scns = scns + self.timeout = timeout + + def make_connection(self, host): + host, self._extra_headers = self.get_host_info(host)[0:2] + return SSLHTTPConnection(host, + key=self.key, + cert=self.cert, + ca=self.ca, + scns=self.scns, + timeout=self.timeout) + + def request(self, host, handler, request_body, verbose=0): + """Send request to server and return response.""" + try: + conn = self.send_request(host, handler, request_body, False) + response = conn.getresponse() + errcode = response.status + errmsg = response.reason + headers = response.msg + except (socket.error, SSL_ERROR): + err = sys.exc_info()[1] + raise ProxyError(xmlrpclib.ProtocolError(host + handler, + 408, + str(err), + self._extra_headers)) + + if errcode != 200: + raise ProxyError(xmlrpclib.ProtocolError(host + handler, + errcode, + errmsg, + headers)) + + self.verbose = verbose + return self.parse_response(response) + + if sys.hexversion < 0x03000000: + def send_request(self, host, handler, request_body, debug): + """ send_request() changed significantly in py3k.""" + conn = self.make_connection(host) + xmlrpclib.Transport.send_request(self, conn, handler, request_body) + self.send_host(conn, host) + self.send_user_agent(conn) + self.send_content(conn, request_body) + return conn + + +def ComponentProxy(url, user=None, password=None, key=None, cert=None, ca=None, + allowedServerCNs=None, timeout=90, retries=3, delay=1): + + """Constructs proxies to components. + + Arguments: + component_name -- name of the component to connect to + + Additional arguments are passed to the ServerProxy constructor. + + """ + xmlrpclib._Method.max_retries = retries + xmlrpclib._Method.retry_delay = delay + + if user and password: + method, path = urlparse(url)[:2] + newurl = "%s://%s:%s@%s" % (method, user, password, path) + else: + newurl = url + ssl_trans = XMLRPCTransport(key, cert, ca, + allowedServerCNs, timeout=float(timeout)) + return xmlrpclib.ServerProxy(newurl, allow_none=True, transport=ssl_trans) -- cgit v1.2.3-1-g7c22 From 76350ecc5f05ee5236dd98e37291061813b8e709 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 8 Feb 2013 13:44:32 -0500 Subject: made Action entries in Independent structures run with when="always" --- src/lib/Bcfg2/Client/Frame.py | 62 ++++++++++++++++++++++-------------- src/lib/Bcfg2/Client/Tools/Action.py | 4 +-- 2 files changed, 40 insertions(+), 26 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index a95c0a7a6..f230aacb7 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -323,10 +323,10 @@ class Frame(object): for bundle in self.setup['bundle']: if bundle not in all_bundle_names: self.logger.info("Warning: Bundle %s not found" % bundle) - bundles = filter(lambda b: b.get('name') in self.setup['bundle'], - bundles) + bundles = [b for b in bundles + if b.get('name') in self.setup['bundle']] elif self.setup['indep']: - bundles = filter(lambda b: b.tag != 'Bundle', bundles) + bundles = [b for b in bundles if b.tag != 'Bundle'] if self.setup['skipbundle']: # warn if non-existent bundle given if not self.setup['bundle_quick']: @@ -334,39 +334,41 @@ class Frame(object): if bundle not in all_bundle_names: self.logger.info("Warning: Bundle %s not found" % bundle) - bundles = filter(lambda b: - b.get('name') not in self.setup['skipbundle'], - bundles) + bundles = [b for b in bundles + if b.get('name') not in self.setup['skipbundle']] if self.setup['skipindep']: - bundles = filter(lambda b: b.tag == 'Bundle', bundles) + bundles = [b for b in bundles if b.tag == 'Bundle'] self.whitelist = [e for e in self.whitelist - if True in [e in b for b in bundles]] + if any(e in b for b in bundles)] # first process prereq actions for bundle in bundles[:]: - if bundle.tag != 'Bundle': - continue - bmodified = len([item for item in bundle - if item in self.whitelist]) + if bundle.tag == 'Bundle': + bmodified = any(item in self.whitelist for item in bundle) + else: + bmodified = False actions = [a for a in bundle.findall('./Action') - if (a.get('timing') != 'post' and + if (a.get('timing') in ['pre', 'both'] and (bmodified or a.get('when') == 'always'))] # now we process all "always actions" if self.setup['interactive']: self.promptFilter(prompt, actions) self.DispatchInstallCalls(actions) + if bundle.tag != 'Bundle': + continue + # need to test to fail entries in whitelist - if False in [self.states[a] for a in actions]: + if not all(self.states[a] for a in actions): # then display bundles forced off with entries - self.logger.info("Bundle %s failed prerequisite action" % - (bundle.get('name'))) + self.logger.info("%s %s failed prerequisite action" % + (bundle.tag, bundle.get('name'))) bundles.remove(bundle) b_to_remv = [ent for ent in self.whitelist if ent in bundle] if b_to_remv: - self.logger.info("Not installing entries from Bundle %s" % - (bundle.get('name'))) + self.logger.info("Not installing entries from %s %s" % + (bundle.tag, bundle.get('name'))) self.logger.info(["%s:%s" % (e.tag, e.get('name')) for e in b_to_remv]) for ent in b_to_remv: @@ -432,14 +434,26 @@ class Frame(object): # prune out unspecified bundles when running with -b continue for tool in self.tools: + if bundle in mbundles: + func = tool.BundleUpdated + else: + func = tool.BundleNotUpdated try: - if bundle in mbundles: - tool.BundleUpdated(bundle, self.states) - else: - tool.BundleNotUpdated(bundle, self.states) + func(bundle, self.states) + except: + self.logger.error("%s.%s(%s:%s) call failed:" % + (tool.name, func.im_func.func_name, + bundle.tag, bundle.get("name")), + exc_info=1) + + for indep in self.config.findall('.//Independent'): + for tool in self.tools: + try: + tool.BundleNotUpdated(indep, self.states) except: - self.logger.error("%s.BundleNotUpdated() call failed:" % - tool.name, exc_info=1) + self.logger.error("%s.BundleNotUpdated(%s:%s) call failed:" + % (tool.name, indep.tag, + indep.get("name")), exc_info=1) def Remove(self): """Remove extra entries.""" diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index 5aada9c0b..d5caf3231 100644 --- a/src/lib/Bcfg2/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -82,8 +82,8 @@ class Action(Bcfg2.Client.Tools.Tool): def BundleNotUpdated(self, bundle, states): """Run Actions when bundles have not been updated.""" for action in bundle.findall("Action"): - if action.get('timing') in ['post', 'both'] and \ - action.get('when') != 'modified': + if (action.get('timing') in ['post', 'both'] and + action.get('when') != 'modified'): if not self._action_allowed(action): continue states[action] = self.RunAction(action) -- cgit v1.2.3-1-g7c22 From 2162f28d494691101d6847c48851939dcfe0302a Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 12 Feb 2013 07:53:17 -0500 Subject: removed experimental flag from plugins/tools added in 1.3 --- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index 7c8a4d578..53ceb4e3c 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -102,7 +102,6 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): ('POSIXGroup', None)] __req__ = dict(POSIXUser=['name'], POSIXGroup=['name']) - experimental = True #: A mapping of XML entry attributes to the indexes of #: corresponding values in the get{pw|gr}all data structures -- cgit v1.2.3-1-g7c22 From 9bec4d6bbab599bee72256c7e09fe214cb849a1b Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 5 Feb 2013 12:13:20 -0500 Subject: abstracted similar digit range classes in POSIXUsers/GroupPatterns into Bcfg2.Utils --- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 45 ++++---------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index 53ceb4e3c..3248cef9c 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -5,44 +5,9 @@ import sys import pwd import grp import subprocess +from Bcfg2.Utils import PackedDigitRange import Bcfg2.Client.XML import Bcfg2.Client.Tools -from Bcfg2.Compat import any # pylint: disable=W0622 - - -class IDRangeSet(object): - """ Representation of a set of integer ranges. Used to describe - which UID/GID ranges are managed or unmanaged. """ - - def __init__(self, *ranges): - self.ranges = [] - self.ints = [] - self.str = ",".join(str(r) for r in ranges) - for item in ranges: - item = str(item).strip() - if item.endswith("-"): - self.ranges.append((int(item[:-1]), None)) - elif '-' in str(item): - self.ranges.append(tuple(int(x) for x in item.split('-'))) - else: - self.ints.append(int(item)) - - def __contains__(self, other): - other = int(other) - if other in self.ints: - return True - return any((end is None and other >= start) or - (end is not None and other >= start and other <= end) - for start, end in self.ranges) - - def __repr__(self): - return "%s:%s" % (self.__class__.__name__, str(self)) - - def __str__(self): - return "[%s]" % self.str - - def __len__(self): - return len(self.ranges) + len(self.ints) class ExecutionError(Exception): @@ -123,16 +88,16 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): self._blacklist = dict(POSIXUser=None, POSIXGroup=None) if self.setup['posix_uid_whitelist']: self._whitelist['POSIXUser'] = \ - IDRangeSet(*self.setup['posix_uid_whitelist']) + PackedDigitRange(*self.setup['posix_uid_whitelist']) else: self._blacklist['POSIXUser'] = \ - IDRangeSet(*self.setup['posix_uid_blacklist']) + PackedDigitRange(*self.setup['posix_uid_blacklist']) if self.setup['posix_gid_whitelist']: self._whitelist['POSIXGroup'] = \ - IDRangeSet(*self.setup['posix_gid_whitelist']) + PackedDigitRange(*self.setup['posix_gid_whitelist']) else: self._blacklist['POSIXGroup'] = \ - IDRangeSet(*self.setup['posix_gid_blacklist']) + PackedDigitRange(*self.setup['posix_gid_blacklist']) @property def existing(self): -- cgit v1.2.3-1-g7c22 From fd67a2735ada342251cb6baaa4e678532566e975 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 7 Feb 2013 10:00:37 -0500 Subject: moved common file locking code into Bcfg2.Utils --- src/lib/Bcfg2/Client/Client.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index e26a6d07a..6aa9ace1f 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -14,6 +14,7 @@ import Bcfg2.Client.XML import Bcfg2.Client.Proxy import Bcfg2.Client.Frame import Bcfg2.Client.Tools +from Bcfg2.Utils import locked from Bcfg2.Compat import xmlrpclib from Bcfg2.version import __version__ from subprocess import Popen, PIPE @@ -288,11 +289,7 @@ class Client(object): #check lock here try: lockfile = open(self.setup['lockfile'], 'w') - try: - fcntl.lockf(lockfile.fileno(), - fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - # otherwise exit and give a warning to the user + if locked(lockfile.fileno()): self.fatal_error("Another instance of Bcfg2 is running. " "If you want to bypass the check, run " "with the %s option" % @@ -301,7 +298,8 @@ class Client(object): raise except: lockfile = None - self.logger.error("Failed to open lockfile") + self.logger.error("Failed to open lockfile %s: %s" % + (self.setup['lockfile'], sys.exc_info()[1])) # execute the configuration self.tools.Execute() -- cgit v1.2.3-1-g7c22 From 3a0618331e009439ce6b9c664915669884cd4aed Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 12 Feb 2013 16:02:24 -0500 Subject: better Executor class for client tools --- src/lib/Bcfg2/Client/Tools/APK.py | 7 +- src/lib/Bcfg2/Client/Tools/APT.py | 16 ++-- src/lib/Bcfg2/Client/Tools/Action.py | 11 +-- src/lib/Bcfg2/Client/Tools/Chkconfig.py | 34 ++++--- src/lib/Bcfg2/Client/Tools/DebInit.py | 59 ++++++------ src/lib/Bcfg2/Client/Tools/Encap.py | 11 +-- src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py | 2 +- src/lib/Bcfg2/Client/Tools/MacPorts.py | 5 +- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 77 +++------------ src/lib/Bcfg2/Client/Tools/Pacman.py | 5 +- src/lib/Bcfg2/Client/Tools/Portage.py | 16 ++-- src/lib/Bcfg2/Client/Tools/RPM.py | 67 ++++++------- src/lib/Bcfg2/Client/Tools/RcUpdate.py | 24 ++--- src/lib/Bcfg2/Client/Tools/SMF.py | 73 +++++++------- src/lib/Bcfg2/Client/Tools/SYSV.py | 58 +++++------ src/lib/Bcfg2/Client/Tools/Systemd.py | 31 +++--- src/lib/Bcfg2/Client/Tools/Upstart.py | 10 +- src/lib/Bcfg2/Client/Tools/__init__.py | 138 ++++++++++++++++++--------- src/lib/Bcfg2/Client/Tools/launchd.py | 110 ++++++++++----------- 19 files changed, 364 insertions(+), 390 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/APK.py b/src/lib/Bcfg2/Client/Tools/APK.py index f23fbb119..8a02b7d6d 100644 --- a/src/lib/Bcfg2/Client/Tools/APK.py +++ b/src/lib/Bcfg2/Client/Tools/APK.py @@ -19,8 +19,8 @@ class APK(Bcfg2.Client.Tools.PkgTool): def RefreshPackages(self): """Refresh memory hashes of packages.""" - names = self.cmd.run("/sbin/apk info")[1] - nameversions = self.cmd.run("/sbin/apk info -v")[1] + names = self.cmd.run("/sbin/apk info").stdout.splitlines() + nameversions = self.cmd.run("/sbin/apk info -v").stdout.splitlines() for pkg in zip(names, nameversions): pkgname = pkg[0] version = pkg[1][len(pkgname) + 1:] @@ -56,7 +56,6 @@ class APK(Bcfg2.Client.Tools.PkgTool): """Remove extra packages.""" names = [pkg.get('name') for pkg in packages] self.logger.info("Removing packages: %s" % " ".join(names)) - self.cmd.run("/sbin/apk del %s" % \ - " ".join(names)) + self.cmd.run("/sbin/apk del %s" % " ".join(names)) self.RefreshPackages() self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py index 879d2720a..0cdefa613 100644 --- a/src/lib/Bcfg2/Client/Tools/APT.py +++ b/src/lib/Bcfg2/Client/Tools/APT.py @@ -59,7 +59,8 @@ class APT(Bcfg2.Client.Tools.Tool): os.environ["DEBIAN_FRONTEND"] = 'noninteractive' self.actions = {} if self.setup['kevlar'] and not self.setup['dryrun']: - self.cmd.run("%s --force-confold --configure --pending" % self.dpkg) + self.cmd.run("%s --force-confold --configure --pending" % + self.dpkg) self.cmd.run("%s clean" % self.aptget) try: self.pkg_cache = apt.cache.Cache() @@ -88,13 +89,15 @@ class APT(Bcfg2.Client.Tools.Tool): for (name, version) in extras] def VerifyDebsums(self, entry, modlist): - output = self.cmd.run("%s -as %s" % (self.debsums, - entry.get('name')))[1] + output = \ + self.cmd.run("%s -as %s" % + (self.debsums, entry.get('name'))).stdout.splitlines() if len(output) == 1 and "no md5sums for" in output[0]: self.logger.info("Package %s has no md5sums. Cannot verify" % \ entry.get('name')) - entry.set('qtext', "Reinstall Package %s-%s to setup md5sums? (y/N) " \ - % (entry.get('name'), entry.get('version'))) + entry.set('qtext', + "Reinstall Package %s-%s to setup md5sums? (y/N) " % + (entry.get('name'), entry.get('version'))) return False files = [] for item in output: @@ -250,8 +253,7 @@ class APT(Bcfg2.Client.Tools.Tool): self.logger.error(bad_pkgs) if not ipkgs: return - rc = self.cmd.run(self.pkgcmd % (" ".join(ipkgs)))[0] - if rc: + if not self.cmd.run(self.pkgcmd % (" ".join(ipkgs))): self.logger.error("APT command failed") self.pkg_cache = apt.cache.Cache() self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index d5caf3231..7e8366928 100644 --- a/src/lib/Bcfg2/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -48,14 +48,11 @@ class Action(Bcfg2.Client.Tools.Tool): "to build mode" % entry.get('command')) return False self.logger.debug("Running Action %s" % (entry.get('name'))) - rv = self.cmd.run(entry.get('command'))[0] + rv = self.cmd.run(entry.get('command')) self.logger.debug("Action: %s got return code %s" % - (entry.get('command'), rv)) - entry.set('rc', str(rv)) - if entry.get('status', 'check') == 'ignore': - return True - else: - return rv == 0 + (entry.get('command'), rv.retval)) + entry.set('rc', str(rv.retval)) + return entry.get('status', 'check') == 'ignore' or rv.success else: self.logger.debug("In dryrun mode: not running action: %s" % (entry.get('name'))) diff --git a/src/lib/Bcfg2/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py index e1ad35861..ec7f462b3 100644 --- a/src/lib/Bcfg2/Client/Tools/Chkconfig.py +++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py @@ -24,16 +24,14 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): if entry.get('status') == 'ignore': return True - try: - cmd = "/sbin/chkconfig --list %s " % (entry.get('name')) - raw = self.cmd.run(cmd)[1] - patterns = ["error reading information", "unknown service"] - srvdata = [line.split() for line in raw for pattern in patterns \ - if pattern not in line][0] - except IndexError: - # Ocurrs when no lines are returned (service not installed) + rv = self.cmd.run("/sbin/chkconfig --list %s " % entry.get('name')) + if rv.success: + srvdata = rv.stdout.splitlines()[0].split() + else: + # service not installed entry.set('current_status', 'off') return False + if len(srvdata) == 2: # This is an xinetd service if entry.get('status') == srvdata[1]: @@ -43,7 +41,7 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): return False try: - onlevels = [level.split(':')[0] for level in srvdata[1:] \ + onlevels = [level.split(':')[0] for level in srvdata[1:] if level.split(':')[1] == 'on'] except IndexError: onlevels = [] @@ -70,25 +68,25 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): if entry.get('status') == 'off': rv &= self.cmd.run((rcmd + " --level 0123456") % (entry.get('name'), - entry.get('status')))[0] == 0 + entry.get('status'))).success if entry.get("current_status") == "on": - rv &= self.stop_service(entry) + rv &= self.stop_service(entry).success else: rv &= self.cmd.run(rcmd % (entry.get('name'), - entry.get('status')))[0] == 0 + entry.get('status'))).success if entry.get("current_status") == "off": - rv &= (self.start_service(entry) == 0) + rv &= self.start_service(entry).success return rv def FindExtra(self): """Locate extra chkconfig Services.""" allsrv = [line.split()[0] - for line in self.cmd.run("/sbin/chkconfig " - "--list 2>/dev/null|grep :on")[1]] + for line in self.cmd.run("/sbin/chkconfig", + "--list").stdout.splitlines() + if ":on" in line] 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) \ + return [Bcfg2.Client.XML.Element('Service', type='chkconfig', + name=name) for name in allsrv if name not in specified] diff --git a/src/lib/Bcfg2/Client/Tools/DebInit.py b/src/lib/Bcfg2/Client/Tools/DebInit.py index 7d5af1127..ca556e98b 100644 --- a/src/lib/Bcfg2/Client/Tools/DebInit.py +++ b/src/lib/Bcfg2/Client/Tools/DebInit.py @@ -15,7 +15,8 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): __execs__ = ['/usr/sbin/update-rc.d', '/usr/sbin/invoke-rc.d'] __handles__ = [('Service', 'deb')] __req__ = {'Service': ['name', 'status']} - svcre = re.compile("/etc/.*/(?P[SK])(?P\d+)(?P\S+)") + svcre = \ + re.compile("/etc/.*/(?P[SK])(?P\d+)(?P\S+)") # implement entry (Verify|Install) ops def VerifyService(self, entry, _): @@ -28,7 +29,7 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): files = [] try: - deb_version = open('/etc/debian_version', 'r').read().split('/', 1)[0] + deb_version = open('/etc/debian_version').read().split('/', 1)[0] except IOError: deb_version = 'unknown' @@ -59,20 +60,20 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): return False else: return True + elif files: + if start_sequence: + for filename in files: + match = self.svcre.match(filename) + file_sequence = int(match.group('sequence')) + if ((match.group('action') == 'S' and + file_sequence != start_sequence) or + (match.group('action') == 'K' and + file_sequence != kill_sequence)): + return False + return True else: - if files: - if start_sequence: - for filename in files: - match = self.svcre.match(filename) - file_sequence = int(match.group('sequence')) - if match.group('action') == 'S' and file_sequence != start_sequence: - return False - if match.group('action') == 'K' and file_sequence != kill_sequence: - return False - return True - else: - entry.set('current_status', 'off') - return False + entry.set('current_status', 'off') + return False def InstallService(self, entry): """Install Service for entry.""" @@ -80,35 +81,35 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): 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')) + self.logger.debug("Init script for service %s does not exist" % + entry.get('name')) return False if entry.get('status') == 'off': self.cmd.run("/usr/sbin/invoke-rc.d %s stop" % (entry.get('name'))) - cmdrc = self.cmd.run("/usr/sbin/update-rc.d -f %s remove" % entry.get('name'))[0] + return self.cmd.run("/usr/sbin/update-rc.d -f %s remove" % + entry.get('name')).success else: command = "/usr/sbin/update-rc.d %s defaults" % (entry.get('name')) if entry.get('sequence'): - cmdrc = self.cmd.run("/usr/sbin/update-rc.d -f %s remove" % entry.get('name'))[0] - if cmdrc != 0: + if not self.cmd.run("/usr/sbin/update-rc.d -f %s remove" % + entry.get('name')).success: return False start_sequence = int(entry.get('sequence')) kill_sequence = 100 - start_sequence command = "%s %d %d" % (command, start_sequence, kill_sequence) - cmdrc = self.cmd.run(command)[0] - return cmdrc == 0 + return self.cmd.run(command).success def FindExtra(self): """Find Extra Debian Service entries.""" specified = [entry.get('name') for entry in self.getSupportedEntries()] - extra = [] - for name in [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]: - if name not in extra: - extra.append(name) - return [Bcfg2.Client.XML.Element('Service', name=name, type='deb') for name \ - in extra] + extra = set() + for fname in glob.glob("/etc/rc[12345].d/S*"): + name = self.svcre.match(fname).group('name') + if name not in specified: + extra.add(name) + return [Bcfg2.Client.XML.Element('Service', name=name, type='deb') + for name in list(extra)] def Remove(self, _): """Remove extra service entries.""" diff --git a/src/lib/Bcfg2/Client/Tools/Encap.py b/src/lib/Bcfg2/Client/Tools/Encap.py index ca6fc7653..678e0f00c 100644 --- a/src/lib/Bcfg2/Client/Tools/Encap.py +++ b/src/lib/Bcfg2/Client/Tools/Encap.py @@ -33,14 +33,13 @@ class Encap(Bcfg2.Client.Tools.PkgTool): 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: + success = self.cmd.run("/usr/local/bin/epkg -q -S -k %s-%s" % + (entry.get('name'), + entry.get('version'))).success + if not success: self.logger.debug("Package %s version incorrect" % entry.get('name')) - else: - return True - return False + return success def Remove(self, packages): """Deal with extra configuration detected.""" diff --git a/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py b/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py index ded84bef4..635318805 100644 --- a/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py +++ b/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py @@ -20,7 +20,7 @@ class FreeBSDPackage(Bcfg2.Client.Tools.PkgTool): def RefreshPackages(self): self.installed = {} - packages = self.cmd.run("/usr/sbin/pkg_info -a -E")[1] + packages = self.cmd.run("/usr/sbin/pkg_info -a -E").stdout.splitlines() pattern = re.compile('(.*)-(\d.*)') for pkg in packages: if pattern.match(pkg): diff --git a/src/lib/Bcfg2/Client/Tools/MacPorts.py b/src/lib/Bcfg2/Client/Tools/MacPorts.py index be441135e..bc3765ec6 100644 --- a/src/lib/Bcfg2/Client/Tools/MacPorts.py +++ b/src/lib/Bcfg2/Client/Tools/MacPorts.py @@ -19,7 +19,8 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool): def RefreshPackages(self): """Refresh memory hashes of packages.""" - pkgcache = self.cmd.run("/opt/local/bin/port installed")[1] + pkgcache = self.cmd.run(["/opt/local/bin/port", + "installed"]).stdout.splitlines() self.installed = {} for pkg in pkgcache: if pkg.startswith("Warning:"): @@ -65,7 +66,7 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool): """Remove extra packages.""" names = [pkg.get('name') for pkg in packages] self.logger.info("Removing packages: %s" % " ".join(names)) - self.cmd.run("/opt/local/bin/port uninstall %s" % \ + self.cmd.run("/opt/local/bin/port uninstall %s" % " ".join(names)) self.RefreshPackages() self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index 3248cef9c..e9db33d16 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -1,60 +1,11 @@ """ A tool to handle creating users and groups with useradd/mod/del and groupadd/mod/del """ -import sys import pwd import grp -import subprocess -from Bcfg2.Utils import PackedDigitRange import Bcfg2.Client.XML import Bcfg2.Client.Tools - - -class ExecutionError(Exception): - """ Raised when running an external command fails """ - - def __init__(self, msg, retval=None): - Exception.__init__(self, msg) - self.retval = retval - - def __str__(self): - return "%s (rv: %s)" % (Exception.__str__(self), - self.retval) - - -class Executor(object): - """ A better version of Bcfg2.Client.Tool.Executor, which captures - stderr, raises exceptions on error, and doesn't use the shell to - execute by default """ - - def __init__(self, logger): - self.logger = logger - self.stdout = None - self.stderr = None - self.retval = None - - def run(self, command, inputdata=None, shell=False): - """ Run a command, given as a list, optionally giving it the - specified input data """ - self.logger.debug("Running: %s" % " ".join(command)) - proc = subprocess.Popen(command, shell=shell, bufsize=16384, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, close_fds=True) - if inputdata: - for line in inputdata.splitlines(): - self.logger.debug('> %s' % line) - (self.stdout, self.stderr) = proc.communicate(inputdata) - else: - (self.stdout, self.stderr) = proc.communicate() - for line in self.stdout.splitlines(): # pylint: disable=E1103 - self.logger.debug('< %s' % line) - self.retval = proc.wait() - if self.retval == 0: - for line in self.stderr.splitlines(): # pylint: disable=E1103 - self.logger.warning(line) - return True - else: - raise ExecutionError(self.stderr, self.retval) +from Bcfg2.Utils import PackedDigitRange class POSIXUsers(Bcfg2.Client.Tools.Tool): @@ -82,7 +33,6 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) self.set_defaults = dict(POSIXUser=self.populate_user_entry, POSIXGroup=lambda g: g) - self.cmd = Executor(logger) self._existing = None self._whitelist = dict(POSIXUser=None, POSIXGroup=None) self._blacklist = dict(POSIXUser=None, POSIXGroup=None) @@ -273,16 +223,14 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): action = "add" else: action = "mod" - try: - self.cmd.run(self._get_cmd(action, - self.set_defaults[entry.tag](entry))) + rv = self.cmd.run(self._get_cmd(action, + self.set_defaults[entry.tag](entry))) + if rv.success: self.modified.append(entry) - return True - except ExecutionError: + else: self.logger.error("POSIXUsers: Error creating %s %s: %s" % - (entry.tag, entry.get("name"), - sys.exc_info()[1])) - return False + (entry.tag, entry.get("name"), rv.error)) + return rv.success def _get_cmd(self, action, entry): """ Get a command to perform the appropriate action (add, mod, @@ -337,11 +285,8 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): def _remove(self, entry): """ Remove an entry """ - try: - self.cmd.run(self._get_cmd("del", entry)) - return True - except ExecutionError: + rv = self.cmd.run(self._get_cmd("del", entry)) + if not rv.success: self.logger.error("POSIXUsers: Error deleting %s %s: %s" % - (entry.tag, entry.get("name"), - sys.exc_info()[1])) - return False + (entry.tag, entry.get("name"), rv.error)) + return rv.success diff --git a/src/lib/Bcfg2/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py index 9c14a3de6..12785afee 100644 --- a/src/lib/Bcfg2/Client/Tools/Pacman.py +++ b/src/lib/Bcfg2/Client/Tools/Pacman.py @@ -20,9 +20,8 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): def RefreshPackages(self): '''Refresh memory hashes of packages''' - pkgcache = self.cmd.run("/usr/bin/pacman -Q")[1] self.installed = {} - for pkg in pkgcache: + for pkg in self.cmd.run("/usr/bin/pacman -Q").stdout.splitlines(): pkgname = pkg.split(' ')[0].strip() version = pkg.split(' ')[1].strip() #self.logger.info(" pkgname: %s, version: %s" % (pkgname, version)) @@ -62,7 +61,7 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): '''Remove extra packages''' names = [pkg.get('name') for pkg in packages] self.logger.info("Removing packages: %s" % " ".join(names)) - self.cmd.run("%s --noconfirm --noprogressbar -R %s" % \ + self.cmd.run("%s --noconfirm --noprogressbar -R %s" % (self.pkgtool, " ".join(names))) self.RefreshPackages() self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py index 9381f44e9..6cbcff2e0 100644 --- a/src/lib/Bcfg2/Client/Tools/Portage.py +++ b/src/lib/Bcfg2/Client/Tools/Portage.py @@ -3,6 +3,7 @@ import re import Bcfg2.Client.Tools + class Portage(Bcfg2.Client.Tools.PkgTool): """The Gentoo toolset implements package and service operations and inherits the rest from Toolset.Toolset.""" @@ -35,9 +36,8 @@ class Portage(Bcfg2.Client.Tools.PkgTool): if not self._initialised: return self.logger.info('Getting list of installed packages') - cache = self.cmd.run("equery -q list '*'")[1] self.installed = {} - for pkg in cache: + for pkg in self.cmd.run("equery -q list '*'").stdout.splitlines(): if self._pkg_pattern.match(pkg): name = self._pkg_pattern.match(pkg).group(1) version = self._pkg_pattern.match(pkg).group(2) @@ -73,12 +73,12 @@ class Portage(Bcfg2.Client.Tools.PkgTool): self.logger.debug('Running equery check on %s' % entry.get('name')) - output = self.cmd.run("/usr/bin/equery -N check '=%s-%s' " - "2>&1 | grep '!!!' | awk '{print $2}'" - % ((entry.get('name'), version)))[1] - if [filename for filename in output \ - if filename not in modlist]: - return False + for line in self.cmd.run(["/usr/bin/equery", "-N", "check", + '=%s-%s' % + (entry.get('name'), + version)]).stdout.splitlines(): + if '!!!' in line and line.split()[1] not in modlist: + return False # By now the package must be in one of the following states: # - Not require checking diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py index 0964fdb80..e9dff3db5 100644 --- a/src/lib/Bcfg2/Client/Tools/RPM.py +++ b/src/lib/Bcfg2/Client/Tools/RPM.py @@ -78,13 +78,12 @@ class RPM(Bcfg2.Client.Tools.PkgTool): # Many, if not most package verifies can be caused by out of # date prelinking. if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']: - cmdrc, output = self.cmd.run('/usr/sbin/prelink -a -mR') - if cmdrc == 0: + rv = self.cmd.run('/usr/sbin/prelink -a -mR') + if rv.success: self.logger.debug('Pre-emptive prelink succeeded') else: # FIXME : this is dumb - what if the output is huge? - self.logger.error('Pre-emptive prelink failed: %s' % output) - + self.logger.error('Pre-emptive prelink failed: %s' % rv.error) def RefreshPackages(self): """ @@ -591,29 +590,26 @@ class RPM(Bcfg2.Client.Tools.PkgTool): # Fix installOnlyPackages if len(install_only_pkgs) > 0: self.logger.info("Attempting to install 'install only packages'") - install_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) \ - for inst in install_only_pkgs]) - self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) - cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ - install_args) - if cmdrc == 0: + install_args = \ + " ".join(os.path.join(self.instance_status[inst].get('pkg').get('uri'), + inst.get('simplefile')) + for inst in install_only_pkgs) + if self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs " + "%s" % install_args): # The rpm command succeeded. All packages installed. self.logger.info("Single Pass for InstallOnlyPkgs Succeded") self.RefreshPackages() - else: # The rpm command failed. No packages installed. # Try installing instances individually. self.logger.error("Single Pass for InstallOnlyPackages Failed") installed_instances = [] for inst in install_only_pkgs: - install_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) - cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ - install_args) - if cmdrc == 0: + install_args = \ + os.path.join(self.instance_status[inst].get('pkg').get('uri'), + inst.get('simplefile')) + if self.cmd.run("rpm --install --quiet --oldpackage " + "--replacepkgs %s" % install_args): installed_instances.append(inst) else: self.logger.debug("InstallOnlyPackage %s %s would not install." % \ @@ -630,15 +626,15 @@ class RPM(Bcfg2.Client.Tools.PkgTool): self.logger.info("Installing GPG keys.") key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ inst.get('simplefile')) - cmdrc, output = self.cmd.run("rpm --import %s" % key_arg) - if cmdrc != 0: - self.logger.debug("Unable to install %s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) + if not self.cmd.run("rpm --import %s" % key_arg): + self.logger.debug("Unable to install %s-%s" % + (self.instance_status[inst].get('pkg').get('name'), + self.str_evra(inst))) else: - self.logger.debug("Installed %s-%s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - inst.get('version'), inst.get('release'))) + self.logger.debug("Installed %s-%s-%s" % + (self.instance_status[inst].get('pkg').get('name'), + inst.get('version'), + inst.get('release'))) self.RefreshPackages() self.gpg_keyids = self.getinstalledgpg() pkg = self.instance_status[gpg_keys[0]].get('pkg') @@ -650,13 +646,12 @@ class RPM(Bcfg2.Client.Tools.PkgTool): upgrade_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ inst.get('simplefile')) \ for inst in upgrade_pkgs]) - cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ - upgrade_args) - if cmdrc == 0: + if self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs " + "%s" % upgrade_args): # The rpm command succeeded. All packages upgraded. self.logger.info("Single Pass for Upgraded Packages Succeded") - upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ - for inst in upgrade_pkgs]) + upgrade_pkg_set = set([self.instance_status[inst].get('pkg') + for inst in upgrade_pkgs]) self.RefreshPackages() else: # The rpm command failed. No packages upgraded. @@ -668,13 +663,13 @@ class RPM(Bcfg2.Client.Tools.PkgTool): inst.get('simplefile')) #self.logger.debug("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ # upgrade_args) - cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % upgrade_args) - if cmdrc == 0: + if self.cmd.run("rpm --upgrade --quiet --oldpackage " + "--replacepkgs %s" % upgrade_args): upgraded_instances.append(inst) else: - self.logger.debug("Package %s %s would not upgrade." % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) + self.logger.debug("Package %s %s would not upgrade." % + (self.instance_status[inst].get('pkg').get('name'), + self.str_evra(inst))) upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ for inst in upgrade_pkgs]) diff --git a/src/lib/Bcfg2/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py index d5cef6e34..2e58f2564 100644 --- a/src/lib/Bcfg2/Client/Tools/RcUpdate.py +++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py @@ -23,8 +23,7 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool): # check if service is enabled cmd = '/sbin/rc-update show default | grep %s' - rv = self.cmd.run(cmd % entry.get('name'))[0] - is_enabled = (rv == 0) + is_enabled = self.cmd.run(cmd % entry.get('name')).success # check if init script exists try: @@ -36,8 +35,7 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool): # check if service is enabled cmd = '/etc/init.d/%s status | grep started' - rv = self.cmd.run(cmd % entry.attrib['name'])[0] - is_running = (rv == 0) + is_running = self.cmd.run(cmd % entry.attrib['name']).success if entry.get('status') == 'on' and not (is_enabled and is_running): entry.set('current_status', 'off') @@ -60,27 +58,25 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool): self.start_service(entry) # make sure it's enabled cmd = '/sbin/rc-update add %s default' - rv = self.cmd.run(cmd % entry.get('name'))[0] - return (rv == 0) - + return self.cmd.run(cmd % entry.get('name')).success elif entry.get('status') == 'off': if entry.get('current_status') == 'on': self.stop_service(entry) # make sure it's disabled cmd = '/sbin/rc-update del %s default' - rv = self.cmd.run(cmd % entry.get('name'))[0] - return (rv == 0) + return self.cmd.run(cmd % entry.get('name')).success return False def FindExtra(self): """Locate extra rc-update services.""" - cmd = '/bin/rc-status -s | grep started' - allsrv = [line.split()[0] for line in self.cmd.run(cmd)[1]] + cmd = '/bin/rc-status -s' + allsrv = [line.split()[0] + for line in self.cmd.run(cmd).stdout.splitlines() + if 'started' in line] 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='rc-update', - name=name) \ + return [Bcfg2.Client.XML.Element('Service', type='rc-update', + name=name) for name in allsrv if name not in specified] diff --git a/src/lib/Bcfg2/Client/Tools/SMF.py b/src/lib/Bcfg2/Client/Tools/SMF.py index 4409b40f3..68d8b2965 100644 --- a/src/lib/Bcfg2/Client/Tools/SMF.py +++ b/src/lib/Bcfg2/Client/Tools/SMF.py @@ -26,21 +26,20 @@ class SMF(Bcfg2.Client.Tools.SvcTool): def GetFMRI(self, entry): """Perform FMRI resolution for service.""" if not 'FMRI' in entry.attrib: - 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 + rv = self.cmd.run(["/usr/bin/svcs", "-H", "-o", "FMRI", + entry.get('name')]) + if rv.success: + entry.set('FMRI', rv.stdout.splitlines()[0]) else: - self.logger.info('Failed to locate FMRI for service %s' % \ + self.logger.info('Failed to locate FMRI for service %s' % entry.get('name')) - return False + return rv.success return True def VerifyService(self, entry, _): """Verify SMF Service entry.""" if not self.GetFMRI(entry): - self.logger.error("smf service %s doesn't have FMRI set" % \ + self.logger.error("smf service %s doesn't have FMRI set" % entry.get('name')) return False if entry.get('FMRI').startswith('lrc'): @@ -57,8 +56,9 @@ class SMF(Bcfg2.Client.Tools.SvcTool): (entry.get("FMRI"))) return entry.get('status') == 'off' try: - srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % \ - entry.get('FMRI'))[1][0].split() + srvdata = \ + self.cmd.run("/usr/bin/svcs -H -o STA %s" % + entry.get('FMRI')).stdout.splitlines()[0].split() except IndexError: # Occurs when no lines are returned (service not installed) return False @@ -85,31 +85,30 @@ class SMF(Bcfg2.Client.Tools.SvcTool): (loc)) return False else: - cmdrc = self.cmd.run("/usr/sbin/svcadm disable %s" % \ - (entry.get('FMRI')))[0] + return self.cmd.run("/usr/sbin/svcadm disable %s" % + entry.get('FMRI')).success + elif 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) + return True + except OSError: + self.logger.debug("Failed to rename %s to %s" % + (loc.replace('/S', '/DISABLED.S'), loc)) + return False 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 + srvdata = \ + self.cmd.run("/usr/bin/svcs -H -o STA %s" % + entry.get('FMRI'))[1].splitlines()[0].split() + if srvdata[0] == 'MNT': + cmdarg = 'clear' else: - srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % - entry.get('FMRI'))[1][0].split() - if srvdata[0] == 'MNT': - cmdarg = 'clear' - else: - cmdarg = 'enable' - cmdrc = self.cmd.run("/usr/sbin/svcadm %s -r %s" % \ - (cmdarg, entry.get('FMRI')))[0] - return cmdrc == 0 + cmdarg = 'enable' + return self.cmd.run("/usr/sbin/svcadm %s -r %s" % + (cmdarg, entry.get('FMRI'))).success def Remove(self, svcs): """Remove Extra SMF entries.""" @@ -120,12 +119,14 @@ class SMF(Bcfg2.Client.Tools.SvcTool): def FindExtra(self): """Find Extra SMF Services.""" allsrv = [name for name, version in \ - [srvc.split() for srvc in - self.cmd.run("/usr/bin/svcs -a -H -o FMRI,STATE")[1]] + [srvc.split() + for srvc in self.cmd.run([ + "/usr/bin/svcs", "-a", "-H", + "-o", "FMRI,STATE"]).stdout.splitlines()] if version != 'disabled'] for svc in self.getSupportedEntries(): if svc.get("FMRI") in allsrv: allsrv.remove(svc.get('FMRI')) - return [Bcfg2.Client.XML.Element("Service", type='smf', name=name) \ + return [Bcfg2.Client.XML.Element("Service", type='smf', name=name) for name in allsrv] diff --git a/src/lib/Bcfg2/Client/Tools/SYSV.py b/src/lib/Bcfg2/Client/Tools/SYSV.py index 9b84a14cc..38072c52e 100644 --- a/src/lib/Bcfg2/Client/Tools/SYSV.py +++ b/src/lib/Bcfg2/Client/Tools/SYSV.py @@ -1,11 +1,11 @@ """This provides bcfg2 support for Solaris SYSV packages.""" import tempfile - +from Bcfg2.Compat import any # pylint: disable=W0622 import Bcfg2.Client.Tools import Bcfg2.Client.XML - +# pylint: disable=C0103 noask = ''' mail= instance=overwrite @@ -19,6 +19,7 @@ conflict=nocheck action=nocheck basedir=default ''' +# pylint: enable=C0103 class SYSV(Bcfg2.Client.Tools.PkgTool): @@ -42,14 +43,14 @@ class SYSV(Bcfg2.Client.Tools.PkgTool): self.noaskfile.flush() self.pkgtool = (self.pkgtool[0] % ("-a %s" % (self.noaskname)), \ self.pkgtool[1]) - except: - self.pkgtool = (self.pkgtool[0] % (""), self.pkgtool[1]) + except: # pylint: disable=W0702 + 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] + lines = self.cmd.run("/usr/bin/pkginfo -x").stdout.splitlines() while lines: # Splitting on whitespace means that packages with spaces in # their version numbers don't work right. Found this with @@ -62,35 +63,36 @@ class SYSV(Bcfg2.Client.Tools.PkgTool): 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 - - desiredVersion = entry.get('version') - if desiredVersion == 'any': - desiredVersion = self.installed.get(entry.get('name'), desiredVersion) - - cmdrc = self.cmd.run("/usr/bin/pkginfo -q -v \"%s\" %s" % \ - (desiredVersion, entry.get('name')))[0] + desired_version = entry.get('version') + if desired_version == 'any': + desired_version = self.installed.get(entry.get('name'), + desired_version) - if cmdrc != 0: + if not self.cmd.run(["/usr/bin/pkginfo", "-q", "-v", + desired_version, entry.get('name')]): if entry.get('name') in self.installed: - self.logger.debug("Package %s version incorrect: have %s want %s" \ - % (entry.get('name'), self.installed[entry.get('name')], - desiredVersion)) + self.logger.debug("Package %s version incorrect: " + "have %s want %s" % + (entry.get('name'), + self.installed[entry.get('name')], + desired_version)) else: - self.logger.debug("Package %s not installed" % (entry.get("name"))) + self.logger.debug("Package %s not installed" % + entry.get("name")) else: - if self.setup['quick'] or entry.attrib.get('verify', 'true') == 'false': + 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: + rv = self.cmd.run("/usr/sbin/pkgchk -n %s" % entry.get('name')) + if rv.success: 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'))) + output = [line for line in rv.stdout.splitlines() + if line[:5] == 'ERROR'] + if any(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 @@ -99,7 +101,7 @@ class SYSV(Bcfg2.Client.Tools.PkgTool): """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.cmd.run("/usr/sbin/pkgrm -a %s -n %s" % (self.noaskname, names)) self.RefreshPackages() self.extra = self.FindExtra() diff --git a/src/lib/Bcfg2/Client/Tools/Systemd.py b/src/lib/Bcfg2/Client/Tools/Systemd.py index 43eca2eac..027d91c71 100644 --- a/src/lib/Bcfg2/Client/Tools/Systemd.py +++ b/src/lib/Bcfg2/Client/Tools/Systemd.py @@ -5,6 +5,7 @@ import Bcfg2.Client.Tools import Bcfg2.Client.XML + class Systemd(Bcfg2.Client.Tools.SvcTool): """Systemd support for Bcfg2.""" name = 'Systemd' @@ -21,35 +22,25 @@ class Systemd(Bcfg2.Client.Tools.SvcTool): return True cmd = "/bin/systemctl status %s.service " % (entry.get('name')) - raw = ''.join(self.cmd.run(cmd)[1]) + rv = self.cmd.run(cmd) - if raw.find('Loaded: error') >= 0: + if 'Loaded: error' in rv.stdout: entry.set('current_status', 'off') - status = False - - elif raw.find('Active: active') >= 0: + return False + elif 'Active: active' in rv.stdout: entry.set('current_status', 'on') - if entry.get('status') == 'off': - status = False - else: - status = True - + return entry.get('status') == 'on' else: entry.set('current_status', 'off') - if entry.get('status') == 'on': - status = False - else: - status = True - - return status + return entry.get('status') == 'off' def InstallService(self, entry): """Install Service entry.""" if entry.get('status') == 'on': - rv = self.cmd.run(self.get_svc_command(entry, 'enable'))[0] == 0 - rv &= self.cmd.run(self.get_svc_command(entry, 'start'))[0] == 0 + rv = self.cmd.run(self.get_svc_command(entry, 'enable')).success + rv &= self.cmd.run(self.get_svc_command(entry, 'start')).success else: - rv = self.cmd.run(self.get_svc_command(entry, 'stop'))[0] == 0 - rv &= self.cmd.run(self.get_svc_command(entry, 'disable'))[0] == 0 + rv = self.cmd.run(self.get_svc_command(entry, 'stop')).success + rv &= self.cmd.run(self.get_svc_command(entry, 'disable')).success return rv diff --git a/src/lib/Bcfg2/Client/Tools/Upstart.py b/src/lib/Bcfg2/Client/Tools/Upstart.py index 02ed52544..cd1c4a2bc 100644 --- a/src/lib/Bcfg2/Client/Tools/Upstart.py +++ b/src/lib/Bcfg2/Client/Tools/Upstart.py @@ -39,7 +39,8 @@ class Upstart(Bcfg2.Client.Tools.SvcTool): try: output = self.cmd.run('/usr/sbin/service %s status %s' % - (entry.get('name'), params))[1][0] + (entry.get('name'), + params)).stdout.splitlines()[0] except IndexError: self.logger.error("Service %s not an Upstart service" % entry.get('name')) @@ -71,11 +72,10 @@ class Upstart(Bcfg2.Client.Tools.SvcTool): def InstallService(self, entry): """Install Service for entry.""" if entry.get('status') == 'on': - pstatus = self.cmd.run(self.get_svc_command(entry, 'start'))[0] + cmd = "start" elif entry.get('status') == 'off': - pstatus = self.cmd.run(self.get_svc_command(entry, 'stop'))[0] - # pstatus is true if command failed - return not pstatus + cmd = "stop" + return self.cmd.run(self.get_svc_command(entry, cmd)).success def FindExtra(self): """Locate extra Upstart services.""" diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index c0dd60c1e..7014f334f 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -4,7 +4,8 @@ import os import sys import stat import select -from subprocess import Popen, PIPE +import logging +import subprocess import Bcfg2.Client.XML from Bcfg2.Compat import input, walk_packages # pylint: disable=W0622 @@ -25,34 +26,89 @@ class ToolInstantiationError(Exception): pass -class Executor: - """ This class runs shell commands. """ +class ExecutorResult(object): + """ Returned as the result of a call to + :func:`Bcfg2.Client.Tools.Executor.run`. The result can be + accessed via the instance variables, documented below, as a + boolean (which is equivalent to + :attr:`Bcfg2.Client.Tools.ExecutorResult.success`), or as a tuple, + which, for backwards compatibility, is equivalent to + ``(result.retval, result.stdout.splitlines())``. """ + + def __init__(self, stdout, stderr, retval): + #: The output of the command + self.stdout = stdout + + #: The error produced by the command + self.stderr = stderr + + #: The return value of the command. + self.retval = retval + + #: Whether or not the command was successful. If the + #: ExecutorResult is used as a boolean, ``success`` is + #: returned. + self.success = retval == 0 + + #: A friendly error message + self.error = None + if self.retval: + if self.stderr: + self.error = "%s (rv: %s)" % (self.stderr, self.retval) + elif self.stdout: + self.error = "%s (rv: %s)" % (self.stdout, self.retval) + else: + self.error = "No output or error; return value %s" % \ + self.retval + + def __repr__(self): + if self.error: + return "Errored command result: %s" % self.error + elif self.stdout: + return "Successful command result: %s" % self.stdout + else: + return "Successful command result: No output" - def __init__(self, logger): - """ - :param logger: The logger to use to produce debug logging - :type logger: logging.Logger - """ - self.logger = logger + def __getitem__(self, idx): + """ This provides compatibility with the old Executor, which + returned a tuple of (return value, stdout split by lines). """ + return (self.retval, self.stdout.splitlines())[idx] - def run(self, command): - """ Run a command inside a shell. + def __nonzero__(self): + return self.__bool__() - :param command: The command to run, given as a list as to - :class:`subprocess.Popen`. Since the command - will be run within a shell it is particularly - important to pass it as a list. - :type command: list - :returns: tuple of return value (integer) and output (list of - lines) - """ - self.logger.debug("Running: %s" % command) - proc = Popen(command, shell=True, bufsize=16384, - stdin=PIPE, stdout=PIPE, close_fds=True) - output = proc.communicate()[0].splitlines() - for line in output: + def __bool__(self): + return self.success + + +class Executor(object): + """ A better version of Bcfg2.Client.Tool.Executor, which captures + stderr, raises exceptions on error, and doesn't use the shell to + execute by default """ + + def __init__(self): + self.logger = logging.getLogger(self.__class__.__name__) + + def run(self, command, inputdata=None, shell=False): + """ Run a command, given as a list, optionally giving it the + specified input data """ + if isinstance(command, str): + cmdstr = command + else: + cmdstr = " ".join(command) + self.logger.debug("Running: %s" % cmdstr) + proc = subprocess.Popen(command, shell=shell, bufsize=16384, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + if inputdata: + for line in inputdata.splitlines(): + self.logger.debug('> %s' % line) + (stdout, stderr) = proc.communicate(input=inputdata) + for line in stdout.splitlines(): # pylint: disable=E1103 self.logger.debug('< %s' % line) - return (proc.wait(), output) + for line in stderr.splitlines(): # pylint: disable=E1103 + self.logger.info(line) + return ExecutorResult(stdout, stderr, proc.wait()) class ClassName(object): @@ -143,7 +199,7 @@ class Tool(object): #: An :class:`Bcfg2.Client.Tools.Executor` object for #: running external commands. - self.cmd = Executor(logger) + self.cmd = Executor() #: A list of entries that have been modified by this tool self.modified = [] @@ -469,10 +525,7 @@ class PkgTool(Tool): 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: + if self.cmd.run(self.pkgtool[0] % pkgargs): self.logger.info("Single Pass Succeded") # set all package states to true and flush workqueues pkgnames = [pkg.get('name') for pkg in packages] @@ -497,12 +550,11 @@ class PkgTool(Tool): else: self.logger.info("Installing pkg %s version %s" % (pkg.get('name'), pkg.get('version'))) - cmdrc = self.cmd.run( + if 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: + for field in self.pkgtool[1][1]]))): states[pkg] = True else: self.logger.error("Failed to install package %s" % @@ -557,7 +609,7 @@ class SvcTool(Tool): :class:`Bcfg2.Client.Tools.Executor.run` """ self.logger.debug('Starting service %s' % service.get('name')) - return self.cmd.run(self.get_svc_command(service, 'start'))[0] + return self.cmd.run(self.get_svc_command(service, 'start')) def stop_service(self, service): """ Stop a service. @@ -568,7 +620,7 @@ class SvcTool(Tool): :class:`Bcfg2.Client.Tools.Executor.run` """ self.logger.debug('Stopping service %s' % service.get('name')) - return self.cmd.run(self.get_svc_command(service, 'stop'))[0] + return self.cmd.run(self.get_svc_command(service, 'stop')) def restart_service(self, service): """ Restart a service. @@ -580,7 +632,7 @@ class SvcTool(Tool): """ self.logger.debug('Restarting service %s' % service.get('name')) restart_target = service.get('target', 'restart') - return self.cmd.run(self.get_svc_command(service, restart_target))[0] + return self.cmd.run(self.get_svc_command(service, restart_target)) def check_service(self, service): """ Check the status a service. @@ -590,7 +642,7 @@ class SvcTool(Tool): :returns: bool - True if the status command returned 0, False otherwise """ - return self.cmd.run(self.get_svc_command(service, 'status'))[0] == 0 + return self.cmd.run(self.get_svc_command(service, 'status')) def Remove(self, services): if self.setup['servicemode'] != 'disabled': @@ -610,10 +662,10 @@ class SvcTool(Tool): not self.setup['interactive'])): continue - rv = None + success = False if entry.get('status') == 'on': if self.setup['servicemode'] == 'build': - rv = self.stop_service(entry) + success = self.stop_service(entry) elif entry.get('name') not in self.restarted: if self.setup['interactive']: prompt = ('Restart service %s?: (y/N): ' % @@ -625,12 +677,12 @@ class SvcTool(Tool): ans = input(prompt) if ans not in ['y', 'Y']: continue - rv = self.restart_service(entry) - if not rv: + success = self.restart_service(entry) + if success: self.restarted.append(entry.get('name')) else: - rv = self.stop_service(entry) - if rv: + success = self.stop_service(entry) + if not success: self.logger.error("Failed to manipulate service %s" % (entry.get('name'))) BundleUpdated.__doc__ = Tool.BundleUpdated.__doc__ diff --git a/src/lib/Bcfg2/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py index 0a587da2e..b0661b26b 100644 --- a/src/lib/Bcfg2/Client/Tools/launchd.py +++ b/src/lib/Bcfg2/Client/Tools/launchd.py @@ -1,61 +1,58 @@ """launchd support for Bcfg2.""" import os - import Bcfg2.Client.Tools -class launchd(Bcfg2.Client.Tools.Tool): - """Support for Mac OS X launchd services.""" +class launchd(Bcfg2.Client.Tools.Tool): # pylint: disable=C0103 + """Support for Mac OS X launchd services. Currently requires the + path to the plist to load/unload, and Name is acually a + reverse-fqdn (or the label).""" __handles__ = [('Service', 'launchd')] __execs__ = ['/bin/launchctl', '/usr/bin/defaults'] - name = 'launchd' __req__ = {'Service': ['name', 'status']} - ''' - Currently requires the path to the plist to load/unload, - and Name is acually a reverse-fqdn (or the label). - ''' - def __init__(self, logger, setup, config): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) - '''Locate plist file that provides given reverse-fqdn name - /Library/LaunchAgents Per-user agents provided by the administrator. - /Library/LaunchDaemons System wide daemons provided by the administrator. - /System/Library/LaunchAgents Mac OS X Per-user agents. - /System/Library/LaunchDaemons Mac OS X System wide daemons.''' - plistLocations = ["/Library/LaunchDaemons", - "/System/Library/LaunchDaemons"] - self.plistMapping = {} - for directory in plistLocations: + # Locate plist file that provides given reverse-fqdn name: + # + # * ``/Library/LaunchAgents``: Per-user agents provided by the + # administrator. + # * ``/Library/LaunchDaemons``: System-wide daemons provided + # by the administrator. + # * ``/System/Library/LaunchAgents``: Mac OS X per-user + # agents. + # * ``/System/Library/LaunchDaemons``: Mac OS X system-wide + # daemons. + plist_locations = ["/Library/LaunchDaemons", + "/System/Library/LaunchDaemons"] + self.plist_mapping = {} + for directory in plist_locations: for daemon in os.listdir(directory): - try: - if daemon.endswith(".plist"): - d = daemon[:-6] - else: - d = daemon - label = self.cmd.run('defaults read %s/%s Label' % - (directory, d))[1][0] - self.plistMapping[label] = "%s/%s" % (directory, daemon) - except KeyError: - self.logger.warning("Could not get label from %s/%s" % - (directory, daemon)) + if daemon.endswith(".plist"): + daemon = daemon[:-6] + dpath = os.path.join(directory, daemon) + rv = self.cmd.run(['defaults', 'read', dpath, 'Label']) + if rv.success: + label = rv.stdout.splitlines()[0] + self.plist_mapping[label] = dpath + else: + self.logger.warning("Could not get label from %s" % dpath) def FindPlist(self, entry): - return self.plistMapping.get(entry.get('name'), None) + """ Find the location of the plist file for the given entry """ + return self.plist_mapping.get(entry.get('name'), None) def os_version(self): - version = "" - try: - vers = self.cmd.run('sw_vers')[1] - except: - return version - - for line in vers: - if line.startswith("ProductVersion"): - version = line.split()[-1] - return version + """ Determine the OS version """ + rv = self.cmd.run('sw_vers') + if rv: + for line in rv.stdout.splitlines(): + if line.startswith("ProductVersion"): + return line.split()[-1] + else: + return '' def VerifyService(self, entry, _): """Verify launchd service entry.""" @@ -63,7 +60,7 @@ class launchd(Bcfg2.Client.Tools.Tool): return True try: - services = self.cmd.run("/bin/launchctl list")[1] + services = self.cmd.run("/bin/launchctl list").stdout.splitlines() except IndexError: # happens when no services are running (should be never) services = [] @@ -93,15 +90,13 @@ class launchd(Bcfg2.Client.Tools.Tool): name = entry.get('name') if entry.get('status') == 'on': self.logger.error("Installing service %s" % name) - cmdrc = self.cmd.run("/bin/launchctl load -w %s" % - self.FindPlist(entry)) - cmdrc = self.cmd.run("/bin/launchctl start %s" % name) + self.cmd.run("/bin/launchctl load -w %s" % self.FindPlist(entry)) + return self.cmd.run("/bin/launchctl start %s" % name).success else: self.logger.error("Uninstalling service %s" % name) - cmdrc = self.cmd.run("/bin/launchctl stop %s" % name) - cmdrc = self.cmd.run("/bin/launchctl unload -w %s" % - self.FindPlist(entry)) - return cmdrc[0] == 0 + self.cmd.run("/bin/launchctl stop %s" % name) + return self.cmd.run("/bin/launchctl unload -w %s" % + self.FindPlist(entry)).success def Remove(self, svcs): """Remove Extra launchd entries.""" @@ -110,23 +105,24 @@ class launchd(Bcfg2.Client.Tools.Tool): def FindExtra(self): """Find Extra launchd services.""" try: - allsrv = self.cmd.run("/bin/launchctl list")[1] + allsrv = self.cmd.run("/bin/launchctl list").stdout.splitlines() except IndexError: allsrv = [] - [allsrv.remove(svc) for svc in [entry.get("name") for entry - in self.getSupportedEntries()] if svc in allsrv] - return [Bcfg2.Client.XML.Element("Service", - type='launchd', - name=name, - status='on') for name in allsrv] + for entry in self.getSupportedEntries(): + svc = entry.get("name") + if svc in allsrv: + allsrv.remove(svc) + return [Bcfg2.Client.XML.Element("Service", type='launchd', name=name, + status='on') + for name in allsrv] def BundleUpdated(self, bundle, states): """Reload launchd plist.""" 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'))) + self.logger.error("Insufficient information to restart " + "service %s" % entry.get('name')) else: name = entry.get('name') if entry.get('status') == 'on' and self.FindPlist(entry): -- cgit v1.2.3-1-g7c22 From f91163abed4aa739f7f8b772eabb403f01b94a88 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 13 Feb 2013 16:08:08 -0500 Subject: extended usage of Executor class, added client-side timeout options --- src/lib/Bcfg2/Client/Client.py | 18 +++--- src/lib/Bcfg2/Client/Tools/SELinux.py | 32 +++++----- src/lib/Bcfg2/Client/Tools/__init__.py | 104 +-------------------------------- 3 files changed, 25 insertions(+), 129 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index 6aa9ace1f..0488fcf21 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -14,10 +14,9 @@ import Bcfg2.Client.XML import Bcfg2.Client.Proxy import Bcfg2.Client.Frame import Bcfg2.Client.Tools -from Bcfg2.Utils import locked +from Bcfg2.Utils import locked, Executor from Bcfg2.Compat import xmlrpclib from Bcfg2.version import __version__ -from subprocess import Popen, PIPE class Client(object): @@ -42,6 +41,9 @@ class Client(object): to_file=self.setup['logging']) self.logger = logging.getLogger('bcfg2') self.logger.debug(self.setup) + + self.cmd = Executor(self.setup['command_timeout']) + if self.setup['bundle_quick']: if not self.setup['bundle'] and not self.setup['skipbundle']: self.logger.error("-Q option requires -b or -B") @@ -95,16 +97,14 @@ class Client(object): stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | stat.S_IWUSR) # 0755 - proc = Popen(scriptname, stdin=PIPE, stdout=PIPE, stderr=PIPE) - ret.text, err = proc.communicate() - rv = proc.wait() - if err: + rv = self.cmd.run(scriptname, timeout=self.setup['timeout']) + if rv.stderr: self.logger.warning("Probe %s has error output: %s" % - (name, err)) - if rv: + (name, rv.stderr)) + if not rv.success: self._probe_failure(name, "Return value %s" % rv) self.logger.info("Probe %s has result:" % name) - self.logger.info(ret.text) + self.logger.info(rv.stdout) finally: os.unlink(scriptname) except SystemExit: diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py index 414ca1f93..08d943251 100644 --- a/src/lib/Bcfg2/Client/Tools/SELinux.py +++ b/src/lib/Bcfg2/Client/Tools/SELinux.py @@ -12,7 +12,6 @@ import seobject import Bcfg2.Client.XML import Bcfg2.Client.Tools from Bcfg2.Client.Tools.POSIX.File import POSIXFile -from subprocess import Popen, PIPE def pack128(int_val): @@ -734,9 +733,7 @@ class SELinuxSemoduleHandler(SELinuxEntryHandler): self._all = dict() self.logger.debug("SELinux: Getting modules from semodule") try: - proc = Popen(['semodule', '-l'], stdout=PIPE, stderr=PIPE) - out = proc.communicate()[0] - rv = proc.wait() + rv = self.tool.cmd.run(['semodule', '-l']) except OSError: # semanage failed; probably not in $PATH. try to # get the list of modules from the filesystem @@ -745,13 +742,9 @@ class SELinuxSemoduleHandler(SELinuxEntryHandler): err) self._all.update(self._all_records_from_filesystem()) else: - if rv: - self.logger.error("SELinux: Failed to run semodule: %s" - % err) - self._all.update(self._all_records_from_filesystem()) - else: + if rv.success: # ran semodule successfully - for line in out.splitlines(): + for line in rv.stdout.splitlines(): mod, version = line.split() self._all[mod] = (version, 1) @@ -759,6 +752,10 @@ class SELinuxSemoduleHandler(SELinuxEntryHandler): for mod in self._all_records_from_filesystem().keys(): if mod not in self._all: self._all[mod] = ('', 0) + else: + self.logger.error("SELinux: Failed to run semodule: %s" + % rv.error) + self._all.update(self._all_records_from_filesystem()) return self._all def _all_records_from_filesystem(self): @@ -870,26 +867,23 @@ class SELinuxSemoduleHandler(SELinuxEntryHandler): self.logger.debug("Install SELinux module %s with semodule -i %s" % (entry.get('name'), self._filepath(entry))) try: - proc = Popen(['semodule', '-i', self._filepath(entry)], - stdout=PIPE, stderr=PIPE) - err = proc.communicate()[1] - rv = proc.wait() + rv = self.tool.cmd.run(['semodule', '-i', self._filepath(entry)]) except OSError: err = sys.exc_info()[1] self.logger.error("Failed to install SELinux module %s with " "semodule: %s" % (entry.get("name"), err)) return False - if rv: - self.logger.error("Failed to install SELinux module %s with " - "semodule: %s" % (entry.get("name"), err)) - return False - else: + if rv.success: if entry.get("disabled", "false").lower() == "true": self.logger.warning("SELinux: Cannot disable modules with " "semodule") return False else: return True + else: + self.logger.error("Failed to install SELinux module %s with " + "semodule: %s" % (entry.get("name"), rv.error)) + return False def _addargs(self, entry): """ argument list for adding entries """ diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index 7014f334f..cd86a2a4b 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -4,9 +4,8 @@ import os import sys import stat import select -import logging -import subprocess import Bcfg2.Client.XML +from Bcfg2.Utils import Executor, ClassName from Bcfg2.Compat import input, walk_packages # pylint: disable=W0622 __all__ = [m[1] for m in walk_packages(path=__path__)] @@ -26,103 +25,6 @@ class ToolInstantiationError(Exception): pass -class ExecutorResult(object): - """ Returned as the result of a call to - :func:`Bcfg2.Client.Tools.Executor.run`. The result can be - accessed via the instance variables, documented below, as a - boolean (which is equivalent to - :attr:`Bcfg2.Client.Tools.ExecutorResult.success`), or as a tuple, - which, for backwards compatibility, is equivalent to - ``(result.retval, result.stdout.splitlines())``. """ - - def __init__(self, stdout, stderr, retval): - #: The output of the command - self.stdout = stdout - - #: The error produced by the command - self.stderr = stderr - - #: The return value of the command. - self.retval = retval - - #: Whether or not the command was successful. If the - #: ExecutorResult is used as a boolean, ``success`` is - #: returned. - self.success = retval == 0 - - #: A friendly error message - self.error = None - if self.retval: - if self.stderr: - self.error = "%s (rv: %s)" % (self.stderr, self.retval) - elif self.stdout: - self.error = "%s (rv: %s)" % (self.stdout, self.retval) - else: - self.error = "No output or error; return value %s" % \ - self.retval - - def __repr__(self): - if self.error: - return "Errored command result: %s" % self.error - elif self.stdout: - return "Successful command result: %s" % self.stdout - else: - return "Successful command result: No output" - - def __getitem__(self, idx): - """ This provides compatibility with the old Executor, which - returned a tuple of (return value, stdout split by lines). """ - return (self.retval, self.stdout.splitlines())[idx] - - def __nonzero__(self): - return self.__bool__() - - def __bool__(self): - return self.success - - -class Executor(object): - """ A better version of Bcfg2.Client.Tool.Executor, which captures - stderr, raises exceptions on error, and doesn't use the shell to - execute by default """ - - def __init__(self): - self.logger = logging.getLogger(self.__class__.__name__) - - def run(self, command, inputdata=None, shell=False): - """ Run a command, given as a list, optionally giving it the - specified input data """ - if isinstance(command, str): - cmdstr = command - else: - cmdstr = " ".join(command) - self.logger.debug("Running: %s" % cmdstr) - proc = subprocess.Popen(command, shell=shell, bufsize=16384, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, close_fds=True) - if inputdata: - for line in inputdata.splitlines(): - self.logger.debug('> %s' % line) - (stdout, stderr) = proc.communicate(input=inputdata) - for line in stdout.splitlines(): # pylint: disable=E1103 - self.logger.debug('< %s' % line) - for line in stderr.splitlines(): # pylint: disable=E1103 - self.logger.info(line) - return ExecutorResult(stdout, stderr, proc.wait()) - - -class ClassName(object): - """ This very simple descriptor class exists only to get the name - of the owner class. This is used because, for historical reasons, - we expect every tool to have a ``name`` attribute that is in - almost all cases the same as the ``__class__.__name__`` attribute - of the plugin object. This makes that more dynamic so that each - plugin isn't repeating its own name.""" - - def __get__(self, inst, owner): - return owner.__name__ - - class Tool(object): """ The base tool class. All tools subclass this. @@ -197,9 +99,9 @@ class Tool(object): #: The XML configuration for this client self.config = config - #: An :class:`Bcfg2.Client.Tools.Executor` object for + #: An :class:`Bcfg2.Utils.Executor` object for #: running external commands. - self.cmd = Executor() + self.cmd = Executor(timeout=self.setup['command_timeout']) #: A list of entries that have been modified by this tool self.modified = [] -- cgit v1.2.3-1-g7c22 From 4b339bf61244714661fc73a192296ca37b12ab9f Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 20 Feb 2013 10:38:57 -0500 Subject: fixed unit tests for merge --- src/lib/Bcfg2/Client/Tools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index 41759655d..d197354a3 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -448,7 +448,7 @@ class PkgTool(Tool): self.logger.info("Trying single pass package install for pkgtype %s" % self.pkgtype) - if self.cmd.run(self.pkgtool[0] % pkgargs): + if self.cmd.run(self._get_package_command(packages)): self.logger.info("Single Pass Succeded") # set all package states to true and flush workqueues pkgnames = [pkg.get('name') for pkg in packages] -- cgit v1.2.3-1-g7c22 From d8002c666c6a450e99c9fe476a5a3dcfb23f05db Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 20 Feb 2013 10:55:06 -0500 Subject: removed passing setup and logger objects between client tools --- src/lib/Bcfg2/Client/Client.py | 5 +--- src/lib/Bcfg2/Client/Frame.py | 10 ++++---- src/lib/Bcfg2/Client/Tools/APK.py | 5 ---- src/lib/Bcfg2/Client/Tools/APT.py | 10 ++++---- src/lib/Bcfg2/Client/Tools/Blast.py | 4 ++-- src/lib/Bcfg2/Client/Tools/FreeBSDInit.py | 4 ++-- src/lib/Bcfg2/Client/Tools/IPS.py | 5 ++-- src/lib/Bcfg2/Client/Tools/MacPorts.py | 5 ---- src/lib/Bcfg2/Client/Tools/OpenCSW.py | 4 ++-- src/lib/Bcfg2/Client/Tools/POSIX/__init__.py | 8 +++---- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 4 ++-- src/lib/Bcfg2/Client/Tools/Pacman.py | 5 ---- src/lib/Bcfg2/Client/Tools/Portage.py | 5 ++-- src/lib/Bcfg2/Client/Tools/RPM.py | 4 ++-- src/lib/Bcfg2/Client/Tools/SELinux.py | 25 ++++++++++---------- src/lib/Bcfg2/Client/Tools/SYSV.py | 4 ++-- src/lib/Bcfg2/Client/Tools/YUM.py | 35 +++++++++++++++------------- src/lib/Bcfg2/Client/Tools/__init__.py | 20 +++++++--------- src/lib/Bcfg2/Client/Tools/launchd.py | 4 ++-- 19 files changed, 75 insertions(+), 91 deletions(-) (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index 0488fcf21..08f56a720 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -280,10 +280,7 @@ class Client(object): newconfig.append(bundle) self.config = newconfig - self.tools = Bcfg2.Client.Frame.Frame(self.config, - self.setup, - times, self.setup['drivers'], - self.setup['dryrun']) + self.tools = Bcfg2.Client.Frame.Frame(self.config, times) if not self.setup['omit_lock_check']: #check lock here diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index baf8a14f2..3f6eef893 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -7,6 +7,7 @@ import logging import Bcfg2.Client.Tools from Bcfg2.Client import prompt from Bcfg2.Compat import any, all # pylint: disable=W0622 +from Bcfg2.Options import get_option_parser def cmpent(ent1, ent2): @@ -48,18 +49,19 @@ def passes_black_list(entry, blacklist): class Frame(object): """Frame is the container for all Tool objects and state information.""" - def __init__(self, config, setup, times, drivers, dryrun): + def __init__(self, config, times): + self.setup = get_option_parser() self.config = config self.times = times - self.dryrun = dryrun + self.dryrun = self.setup['dryrun'] self.times['initialization'] = time.time() - self.setup = setup self.tools = [] self.states = {} self.whitelist = [] self.blacklist = [] self.removal = [] self.logger = logging.getLogger(__name__) + drivers = self.setup['drivers'] for driver in drivers[:]: if driver not in Bcfg2.Client.Tools.drivers and \ isinstance(driver, str): @@ -83,7 +85,7 @@ class Frame(object): for tool in list(tclass.values()): try: - self.tools.append(tool(self.logger, setup, config)) + self.tools.append(tool(config)) except Bcfg2.Client.Tools.ToolInstantiationError: continue except: diff --git a/src/lib/Bcfg2/Client/Tools/APK.py b/src/lib/Bcfg2/Client/Tools/APK.py index 8a02b7d6d..553339fc9 100644 --- a/src/lib/Bcfg2/Client/Tools/APK.py +++ b/src/lib/Bcfg2/Client/Tools/APK.py @@ -12,11 +12,6 @@ class APK(Bcfg2.Client.Tools.PkgTool): pkgtype = 'apk' pkgtool = ("/sbin/apk add %s", ("%s", ["name"])) - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.installed = {} - self.RefreshPackages() - def RefreshPackages(self): """Refresh memory hashes of packages.""" names = self.cmd.run("/sbin/apk info").stdout.splitlines() diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py index 0cdefa613..e44668bf2 100644 --- a/src/lib/Bcfg2/Client/Tools/APT.py +++ b/src/lib/Bcfg2/Client/Tools/APT.py @@ -18,12 +18,12 @@ class APT(Bcfg2.Client.Tools.Tool): __handles__ = [('Package', 'deb'), ('Path', 'ignore')] __req__ = {'Package': ['name', 'version'], 'Path': ['type']} - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.Tool.__init__(self, config) - self.install_path = setup.get('apt_install_path', '/usr') - self.var_path = setup.get('apt_var_path', '/var') - self.etc_path = setup.get('apt_etc_path', '/etc') + self.install_path = self.setup.get('apt_install_path', '/usr') + self.var_path = self.setup.get('apt_var_path', '/var') + self.etc_path = self.setup.get('apt_etc_path', '/etc') self.debsums = '%s/bin/debsums' % self.install_path self.aptget = '%s/bin/apt-get' % self.install_path self.dpkg = '%s/bin/dpkg' % self.install_path diff --git a/src/lib/Bcfg2/Client/Tools/Blast.py b/src/lib/Bcfg2/Client/Tools/Blast.py index 2627c42fe..fd594b4f4 100644 --- a/src/lib/Bcfg2/Client/Tools/Blast.py +++ b/src/lib/Bcfg2/Client/Tools/Blast.py @@ -13,9 +13,9 @@ class Blast(Bcfg2.Client.Tools.SYSV.SYSV): __handles__ = [('Package', 'blast')] __req__ = {'Package': ['name', 'version', 'bname']} - def __init__(self, logger, setup, config): + def __init__(self, config): # dont use the sysv constructor - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + Bcfg2.Client.Tools.PkgTool.__init__(self, config) noaskfile = tempfile.NamedTemporaryFile() self.noaskname = noaskfile.name try: diff --git a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py index 8ff26d8f3..2ab64f86d 100644 --- a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py +++ b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py @@ -15,8 +15,8 @@ class FreeBSDInit(Bcfg2.Client.Tools.SvcTool): __handles__ = [('Service', 'freebsd')] __req__ = {'Service': ['name', 'status']} - def __init__(self, logger, cfg, setup): - Bcfg2.Client.Tools.Tool.__init__(self, logger, cfg, setup) + def __init__(self, config): + Bcfg2.Client.Tools.SvcTool.__init__(self, config) if os.uname()[0] != 'FreeBSD': raise Bcfg2.Client.Tools.ToolInstantiationError diff --git a/src/lib/Bcfg2/Client/Tools/IPS.py b/src/lib/Bcfg2/Client/Tools/IPS.py index dc4d48235..e4c9d605d 100644 --- a/src/lib/Bcfg2/Client/Tools/IPS.py +++ b/src/lib/Bcfg2/Client/Tools/IPS.py @@ -15,14 +15,13 @@ class IPS(Bcfg2.Client.Tools.PkgTool): __req__ = {'Package': ['name', 'version']} pkgtool = ('pkg install --no-refresh %s', ('%s', ['name'])) - def __init__(self, logger, setup, cfg): + def __init__(self, config): self.installed = {} self.pending_upgrades = set() self.image = image.Image() self.image.find_root('/', False) self.image.load_config() - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, cfg) - self.cfg = cfg + Bcfg2.Client.Tools.PkgTool.__init__(self, config) def RefreshPackages(self): self.installed = dict() diff --git a/src/lib/Bcfg2/Client/Tools/MacPorts.py b/src/lib/Bcfg2/Client/Tools/MacPorts.py index bc3765ec6..5b395a170 100644 --- a/src/lib/Bcfg2/Client/Tools/MacPorts.py +++ b/src/lib/Bcfg2/Client/Tools/MacPorts.py @@ -12,11 +12,6 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool): pkgtype = 'macport' pkgtool = ('/opt/local/bin/port install %s', ('%s', ['name'])) - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.installed = {} - self.RefreshPackages() - def RefreshPackages(self): """Refresh memory hashes of packages.""" pkgcache = self.cmd.run(["/opt/local/bin/port", diff --git a/src/lib/Bcfg2/Client/Tools/OpenCSW.py b/src/lib/Bcfg2/Client/Tools/OpenCSW.py index 60e362e64..3ea9d835e 100644 --- a/src/lib/Bcfg2/Client/Tools/OpenCSW.py +++ b/src/lib/Bcfg2/Client/Tools/OpenCSW.py @@ -14,9 +14,9 @@ class OpenCSW(Bcfg2.Client.Tools.SYSV.SYSV): __handles__ = [('Package', 'opencsw')] __req__ = {'Package': ['name', 'version', 'bname']} - def __init__(self, logger, setup, config): + def __init__(self, config): # dont use the sysv constructor - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + Bcfg2.Client.Tools.PkgTool.__init__(self, config) noaskfile = tempfile.NamedTemporaryFile() self.noaskname = noaskfile.name try: diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py index 7708c4f72..cfb433c9c 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py @@ -14,10 +14,10 @@ class POSIX(Bcfg2.Client.Tools.Tool): """POSIX File support code.""" name = 'POSIX' - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) - self.ppath = setup['ppath'] - self.max_copies = setup['max_copies'] + def __init__(self, config): + Bcfg2.Client.Tools.Tool.__init__(self, config) + self.ppath = self.setup['ppath'] + self.max_copies = self.setup['max_copies'] self._handlers = self._load_handlers() self.logger.debug("POSIX: Handlers loaded: %s" % (", ".join(self._handlers.keys()))) diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index e9db33d16..84db04b83 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -29,8 +29,8 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): #: user or group id_mapping = dict(POSIXUser="uid", POSIXGroup="gid") - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.Tool.__init__(self, config) self.set_defaults = dict(POSIXUser=self.populate_user_entry, POSIXGroup=lambda g: g) self._existing = None diff --git a/src/lib/Bcfg2/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py index 12785afee..fd310441c 100644 --- a/src/lib/Bcfg2/Client/Tools/Pacman.py +++ b/src/lib/Bcfg2/Client/Tools/Pacman.py @@ -13,11 +13,6 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): pkgtype = 'pacman' pkgtool = ("/usr/bin/pacman --needed --noconfirm --noprogressbar") - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.installed = {} - self.RefreshPackages() - def RefreshPackages(self): '''Refresh memory hashes of packages''' self.installed = {} diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py index 6cbcff2e0..aa1254b46 100644 --- a/src/lib/Bcfg2/Client/Tools/Portage.py +++ b/src/lib/Bcfg2/Client/Tools/Portage.py @@ -17,14 +17,13 @@ class Portage(Bcfg2.Client.Tools.PkgTool): ['name', 'version'])) pkgtool = ('emerge %s', ('=%s-%s', ['name', 'version'])) - def __init__(self, logger, cfg, setup): + def __init__(self, config): self._initialised = False - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, cfg, setup) + Bcfg2.Client.Tools.PkgTool.__init__(self, config) self._initialised = True self.__important__ = self.__important__ + ['/etc/make.conf'] self._pkg_pattern = re.compile('(.*)-(\d.*)') self._ebuild_pattern = re.compile('(ebuild|binary)') - self.cfg = cfg self.installed = {} self._binpkgonly = self.setup.get('portage_binpkgonly', False) if self._binpkgonly: diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py index e9dff3db5..18eddbb44 100644 --- a/src/lib/Bcfg2/Client/Tools/RPM.py +++ b/src/lib/Bcfg2/Client/Tools/RPM.py @@ -29,8 +29,8 @@ class RPM(Bcfg2.Client.Tools.PkgTool): pkgtype = 'rpm' pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"])) - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.PkgTool.__init__(self, config) # create a global ignore list used when ignoring particular # files during package verification diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py index 451495be2..e6408922f 100644 --- a/src/lib/Bcfg2/Client/Tools/SELinux.py +++ b/src/lib/Bcfg2/Client/Tools/SELinux.py @@ -7,11 +7,13 @@ import copy import glob import struct import socket +import logging import selinux import seobject import Bcfg2.Client.XML import Bcfg2.Client.Tools from Bcfg2.Client.Tools.POSIX.File import POSIXFile +from Bcfg2.Options import get_option_parser def pack128(int_val): @@ -76,14 +78,13 @@ class SELinux(Bcfg2.Client.Tools.Tool): SEPort=['name', 'selinuxtype'], SEUser=['name', 'roles', 'prefix']) - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.Tool.__init__(self, config) self.handlers = {} for handler in self.__handles__: etype = handler[0] self.handlers[etype] = \ - globals()["SELinux%sHandler" % etype.title()](self, logger, - setup, config) + globals()["SELinux%sHandler" % etype.title()](self, config) self.txn = False self.post_txn_queue = [] @@ -173,10 +174,10 @@ class SELinuxEntryHandler(object): custom_re = re.compile(' (?P\S+)$') custom_format = None - def __init__(self, tool, logger, setup, config): + def __init__(self, tool, config): self.tool = tool - self.logger = logger - self.setup = setup + self.logger = logging.getLogger(self.__class__.__name__) + self.setup = get_option_parser() self.config = config self._records = None self._all = None @@ -619,8 +620,8 @@ class SELinuxSeuserHandler(SELinuxEntryHandler): etype = "user" value_format = ("prefix", None, None, "roles") - def __init__(self, tool, logger, setup, config): - SELinuxEntryHandler.__init__(self, tool, logger, setup, config) + def __init__(self, tool, config): + SELinuxEntryHandler.__init__(self, tool, config) self.needs_prefix = False @property @@ -711,9 +712,9 @@ class SELinuxSemoduleHandler(SELinuxEntryHandler): etype = "module" value_format = (None, "disabled") - def __init__(self, tool, logger, setup, config): - SELinuxEntryHandler.__init__(self, tool, logger, setup, config) - self.filetool = POSIXFile(logger, setup, config) + def __init__(self, tool, config): + SELinuxEntryHandler.__init__(self, tool, config) + self.filetool = POSIXFile(config) try: self.setype = selinux.selinux_getpolicytype()[1] except IndexError: diff --git a/src/lib/Bcfg2/Client/Tools/SYSV.py b/src/lib/Bcfg2/Client/Tools/SYSV.py index 38072c52e..b5afa629d 100644 --- a/src/lib/Bcfg2/Client/Tools/SYSV.py +++ b/src/lib/Bcfg2/Client/Tools/SYSV.py @@ -32,8 +32,8 @@ class SYSV(Bcfg2.Client.Tools.PkgTool): pkgtype = 'sysv' pkgtool = ("/usr/sbin/pkgadd %s -n -d %%s", (('%s %s', ['url', 'name']))) - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.PkgTool.__init__(self, config) # noaskfile needs to live beyond __init__ otherwise file is removed self.noaskfile = tempfile.NamedTemporaryFile() self.noaskname = self.noaskfile.name diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py index 4539a6a36..4d86ac8fe 100644 --- a/src/lib/Bcfg2/Client/Tools/YUM.py +++ b/src/lib/Bcfg2/Client/Tools/YUM.py @@ -3,6 +3,7 @@ import copy import os.path import sys +import logging import yum import yum.packages import yum.rpmtrans @@ -12,6 +13,7 @@ import yum.misc import rpmUtils.arch import Bcfg2.Client.XML import Bcfg2.Client.Tools +from Bcfg2.Options import get_option_parser def build_yname(pkgname, inst): @@ -65,13 +67,13 @@ class RPMDisplay(yum.rpmtrans.RPMBaseCallback): """We subclass the default RPM transaction callback so that we can control Yum's verbosity and pipe it through the right logger.""" - def __init__(self, logger): + def __init__(self): yum.rpmtrans.RPMBaseCallback.__init__(self) # we want to log events to *both* the Bcfg2 logger (which goes # to stderr or syslog or wherever the user wants it to go) # *and* the yum file logger, which will go to yum.log (ticket # #1103) - self.bcfg2_logger = logger + self.bcfg2_logger = logging.getLogger(self.__class__.__name__) self.state = None self.package = None @@ -110,9 +112,9 @@ class YumDisplay(yum.callbacks.ProcessTransBaseCallback): """Class to handle display of what step we are in the Yum transaction such as downloading packages, etc.""" - def __init__(self, logger): + def __init__(self): yum.callbacks.ProcessTransBaseCallback.__init__(self) - self.logger = logger + self.logger = logging.getLogger(self.__class__.__name__) class YUM(Bcfg2.Client.Tools.PkgTool): @@ -128,9 +130,9 @@ class YUM(Bcfg2.Client.Tools.PkgTool): conflicts = ['RPM'] - def __init__(self, logger, setup, config): - self.yumbase = self._loadYumBase(setup=setup, logger=logger) - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + def __init__(self, config): + self.yumbase = self._loadYumBase() + Bcfg2.Client.Tools.PkgTool.__init__(self, config) self.ignores = [entry.get('name') for struct in config \ for entry in struct \ if entry.tag == 'Path' and \ @@ -190,22 +192,23 @@ class YUM(Bcfg2.Client.Tools.PkgTool): self.logger.debug("Yum: installonlypkgs: %s" % self.installonlypkgs) self.logger.debug("Yum: verify_flags: %s" % self.verify_flags) - def _loadYumBase(self, setup=None, logger=None): + def _loadYumBase(self): ''' this may be called before PkgTool.__init__() is called on this object (when the YUM object is first instantiated; PkgTool.__init__() calls RefreshPackages(), which requires a YumBase object already exist), or after __init__() has completed, when we reload the yum config before installing - packages. Consequently, we support both methods by allowing - setup and logger, the only object properties we use in this - function, to be passed as keyword arguments or to be omitted - and drawn from the object itself.''' + packages. ''' rv = yum.YumBase() # pylint: disable=C0103 - if setup is None: + if hasattr(self, "setup"): setup = self.setup - if logger is None: + else: + setup = get_option_parser() + if hasattr(self, "logger"): logger = self.logger + else: + logger = logging.getLogger(self.name) if setup['debug']: debuglevel = 3 @@ -744,8 +747,8 @@ class YUM(Bcfg2.Client.Tools.PkgTool): self.yumbase.closeRpmDB() self.RefreshPackages() - rpm_display = RPMDisplay(self.logger) - yum_display = YumDisplay(self.logger) + rpm_display = RPMDisplay() + yum_display = YumDisplay() # Run the Yum Transaction try: rescode, restring = self.yumbase.buildTransaction() diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index d197354a3..f604e6198 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -3,8 +3,10 @@ import os import sys import stat +import logging import Bcfg2.Client import Bcfg2.Client.XML +from Bcfg2.Options import get_option_parser from Bcfg2.Utils import Executor, ClassName from Bcfg2.Compat import walk_packages # pylint: disable=W0622 @@ -78,23 +80,19 @@ class Tool(object): #: runtime with a warning. conflicts = [] - def __init__(self, logger, setup, config): + def __init__(self, config): """ - :param logger: Logger that will be used for logging by this tool - :type logger: logging.Logger - :param setup: The option set Bcfg2 was invoked with - :type setup: Bcfg2.Options.OptionParser :param config: The XML configuration for this client :type config: lxml.etree._Element :raises: :exc:`Bcfg2.Client.Tools.ToolInstantiationError` """ #: A :class:`Bcfg2.Options.OptionParser` object describing the #: option set Bcfg2 was invoked with - self.setup = setup + self.setup = get_option_parser() #: A :class:`logging.Logger` object that will be used by this #: tool for logging - self.logger = logger + self.logger = logging.getLogger(self.name) #: The XML configuration for this client self.config = config @@ -397,8 +395,8 @@ class PkgTool(Tool): #: The ``type`` attribute of Packages handled by this tool. pkgtype = 'echo' - def __init__(self, logger, setup, config): - Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Tool.__init__(self, config) #: A dict of installed packages; the keys should be package #: names and the values should be simple strings giving the @@ -501,8 +499,8 @@ class PkgTool(Tool): class SvcTool(Tool): """ Base class for tools that handle Service entries """ - def __init__(self, logger, setup, config): - Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Tool.__init__(self, config) #: List of services that have been restarted self.restarted = [] __init__.__doc__ = Tool.__init__.__doc__ diff --git a/src/lib/Bcfg2/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py index b0661b26b..64aa45e4e 100644 --- a/src/lib/Bcfg2/Client/Tools/launchd.py +++ b/src/lib/Bcfg2/Client/Tools/launchd.py @@ -12,8 +12,8 @@ class launchd(Bcfg2.Client.Tools.Tool): # pylint: disable=C0103 __execs__ = ['/bin/launchctl', '/usr/bin/defaults'] __req__ = {'Service': ['name', 'status']} - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + def __init__(self, config): + Bcfg2.Client.Tools.Tool.__init__(self, config) # Locate plist file that provides given reverse-fqdn name: # -- cgit v1.2.3-1-g7c22 From acb1dde9ba48b04d1ceb701ce849e96cef3d0070 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 21 Feb 2013 08:47:59 -0500 Subject: removed in-place modification of "states" dict in client tools --- src/lib/Bcfg2/Client/Client.py | 2 +- src/lib/Bcfg2/Client/Frame.py | 16 +- src/lib/Bcfg2/Client/Tools/APT.py | 4 +- src/lib/Bcfg2/Client/Tools/Action.py | 8 +- src/lib/Bcfg2/Client/Tools/POSIX/__init__.py | 2 +- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 9 +- src/lib/Bcfg2/Client/Tools/Pacman.py | 2 +- src/lib/Bcfg2/Client/Tools/RPM.py | 1103 +++++++++++++++++++++++++- src/lib/Bcfg2/Client/Tools/SELinux.py | 17 +- src/lib/Bcfg2/Client/Tools/YUM.py | 11 +- src/lib/Bcfg2/Client/Tools/__init__.py | 86 +- src/lib/Bcfg2/Client/Tools/launchd.py | 6 +- src/lib/Bcfg2/Client/Tools/rpmtools.py | 1091 ------------------------- 13 files changed, 1169 insertions(+), 1188 deletions(-) delete mode 100755 src/lib/Bcfg2/Client/Tools/rpmtools.py (limited to 'src/lib/Bcfg2/Client') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index 08f56a720..e2521c0f8 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -53,7 +53,7 @@ class Client(object): raise SystemExit(1) if 'drivers' in self.setup and self.setup['drivers'] == 'help': self.logger.info("The following drivers are available:") - self.logger.info(Bcfg2.Client.Tools.drivers) + self.logger.info(Bcfg2.Client.Tools.__all__) raise SystemExit(0) if self.setup['remove'] and 'services' in self.setup['remove'].lower(): self.logger.error("Service removal is nonsensical; " diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index 3f6eef893..82512130c 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -56,6 +56,10 @@ class Frame(object): self.dryrun = self.setup['dryrun'] self.times['initialization'] = time.time() self.tools = [] + + #: A dict of the state of each entry. Keys are the entries. + #: Values are boolean: True means that the entry is good, + #: False means that the entry is bad. self.states = {} self.whitelist = [] self.blacklist = [] @@ -63,7 +67,7 @@ class Frame(object): self.logger = logging.getLogger(__name__) drivers = self.setup['drivers'] for driver in drivers[:]: - if driver not in Bcfg2.Client.Tools.drivers and \ + if driver not in Bcfg2.Client.Tools.__all__ and \ isinstance(driver, str): self.logger.error("Tool driver %s is not available" % driver) drivers.remove(driver) @@ -263,7 +267,7 @@ class Frame(object): self.states[entry] = False for tool in self.tools: try: - tool.Inventory(self.states) + self.states.update(tool.Inventory()) except: self.logger.error("%s.Inventory() call failed:" % tool.name, exc_info=1) @@ -382,7 +386,7 @@ class Frame(object): if not handled: continue try: - tool.Install(handled, self.states) + self.states.update(tool.Install(handled)) except: self.logger.error("%s.Install() call failed:" % tool.name, exc_info=1) @@ -402,7 +406,7 @@ class Frame(object): tbm = [(t, b) for t in self.tools for b in mbundles] for tool, bundle in tbm: try: - tool.Inventory(self.states, [bundle]) + self.states.update(tool.Inventory(structures=[bundle])) except: self.logger.error("%s.Inventory() call failed:" % tool.name, @@ -428,7 +432,7 @@ class Frame(object): else: func = tool.BundleNotUpdated try: - func(bundle, self.states) + self.states.update(func(bundle)) except: self.logger.error("%s.%s(%s:%s) call failed:" % (tool.name, func.im_func.func_name, @@ -438,7 +442,7 @@ class Frame(object): for indep in self.config.findall('.//Independent'): for tool in self.tools: try: - tool.BundleNotUpdated(indep, self.states) + self.states.update(tool.BundleNotUpdated(indep)) except: self.logger.error("%s.BundleNotUpdated(%s:%s) call failed:" % (tool.name, indep.tag, diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py index e44668bf2..cc2f657d0 100644 --- a/src/lib/Bcfg2/Client/Tools/APT.py +++ b/src/lib/Bcfg2/Client/Tools/APT.py @@ -217,7 +217,7 @@ class APT(Bcfg2.Client.Tools.Tool): self.modified += packages self.extra = self.FindExtra() - def Install(self, packages, states): + def Install(self, packages): # it looks like you can't install arbitrary versions of software # out of the pkg cache, we will still need to call apt-get ipkgs = [] @@ -257,10 +257,12 @@ class APT(Bcfg2.Client.Tools.Tool): self.logger.error("APT command failed") self.pkg_cache = apt.cache.Cache() self.extra = self.FindExtra() + states = dict() for package in packages: states[package] = self.VerifyPackage(package, [], checksums=False) if states[package]: self.modified.append(package) + return states def VerifyPath(self, entry, _): """Do nothing here since we only verify Path type=ignore.""" diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index 7e8366928..7b62f61c7 100644 --- a/src/lib/Bcfg2/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -68,19 +68,23 @@ class Action(Bcfg2.Client.Tools.Tool): return self.RunAction(entry) return True - def BundleUpdated(self, bundle, states): + def BundleUpdated(self, bundle): """Run postinstalls when bundles have been updated.""" + states = dict() for action in bundle.findall("Action"): if action.get('timing') in ['post', 'both']: if not self._action_allowed(action): continue states[action] = self.RunAction(action) + return states - def BundleNotUpdated(self, bundle, states): + def BundleNotUpdated(self, bundle): """Run Actions when bundles have not been updated.""" + states = dict() for action in bundle.findall("Action"): if (action.get('timing') in ['post', 'both'] and action.get('when') != 'modified'): if not self._action_allowed(action): continue states[action] = self.RunAction(action) + return states diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py index cfb433c9c..4f1f8e5aa 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py @@ -53,7 +53,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): if POSIXTool in hdlr.__mro__: # figure out what entry type this handler handles etype = hdlr.__name__[5:].lower() - rv[etype] = hdlr(self.logger, self.setup, self.config) + rv[etype] = hdlr(self.config) return rv def canVerify(self, entry): diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index 84db04b83..8ba1944d8 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -86,7 +86,7 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): return False return True - def Inventory(self, states, structures=None): + def Inventory(self, structures=None): if not structures: structures = self.config.getchildren() # we calculate a list of all POSIXUser and POSIXGroup entries, @@ -106,7 +106,8 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): (group, entry.get("name"))) struct.append(Bcfg2.Client.XML.Element("POSIXGroup", name=group)) - return Bcfg2.Client.Tools.Tool.Inventory(self, states, structures) + return Bcfg2.Client.Tools.Tool.Inventory(self, structures) + Inventory.__doc__ = Bcfg2.Client.Tools.Tool.Inventory.__doc__ def FindExtra(self): extra = [] @@ -206,7 +207,8 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): entry.set('qtext', "\n".join([entry.get('qtext', '')] + errors)) return len(errors) == 0 - def Install(self, entries, states): + def Install(self, entries): + states = dict() for entry in entries: # install groups first, so that all groups exist for # users that might need them @@ -216,6 +218,7 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): if entry.tag == 'POSIXUser': states[entry] = self._install(entry) self._existing = None + return states def _install(self, entry): """ add or modify a user or group using the appropriate command """ diff --git a/src/lib/Bcfg2/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py index fd310441c..15fab53bd 100644 --- a/src/lib/Bcfg2/Client/Tools/Pacman.py +++ b/src/lib/Bcfg2/Client/Tools/Pacman.py @@ -61,7 +61,7 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): self.RefreshPackages() self.extra = self.FindExtra() - def Install(self, packages, states): + def Install(self, packages): ''' Pacman Install ''' diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py index 18eddbb44..be5ad01e2 100644 --- a/src/lib/Bcfg2/Client/Tools/RPM.py +++ b/src/lib/Bcfg2/Client/Tools/RPM.py @@ -1,9 +1,1077 @@ """Bcfg2 Support for RPMS""" -import os.path +import os import rpm -import rpmtools import Bcfg2.Client.Tools +import grp +import optparse +import pwd +import stat +import sys +try: + import hashlib + py24compat = False +except ImportError: + # FIXME: Remove when client python dep is 2.5 or greater + py24compat = True + import md5 + +# Determine what prelink tools we have available. +# The isprelink module is a python extension that examines the ELF headers +# to see if the file has been prelinked. If it is not present a lot of files +# are unnecessarily run through the prelink command. +try: + from isprelink import * + isprelink_imported = True +except ImportError: + isprelink_imported = False + +# If the prelink command is installed on the system then we need to do +# prelink -y on files. +if os.access('/usr/sbin/prelink', os.X_OK): + prelink_exists = True +else: + prelink_exists = False + +# If we don't have isprelink then we will use the prelink configuration file to +# filter what we have to put through prelink -y. +import re +blacklist = [] +whitelist = [] +try: + f = open('/etc/prelink.conf', mode='r') + for line in f: + if line.startswith('#'): + continue + option, pattern = line.split() + if pattern.startswith('*.'): + pattern = pattern.replace('*.', '\.') + pattern += '$' + elif pattern.startswith('/'): + pattern = '^' + pattern + if option == '-b': + blacklist.append(pattern) + elif option == '-l': + whitelist.append(pattern) + f.close() +except IOError: + pass + +blacklist_re = re.compile('|'.join(blacklist)) +whitelist_re = re.compile('|'.join(whitelist)) + +# Flags that are not defined in rpm-python. +# They are defined in lib/rpmcli.h +# Bit(s) for verifyFile() attributes. +# +RPMVERIFY_NONE = 0 # /*!< */ +RPMVERIFY_MD5 = 1 # 1 << 0 # /*!< from %verify(md5) */ +RPMVERIFY_FILESIZE = 2 # 1 << 1 # /*!< from %verify(size) */ +RPMVERIFY_LINKTO = 4 # 1 << 2 # /*!< from %verify(link) */ +RPMVERIFY_USER = 8 # 1 << 3 # /*!< from %verify(user) */ +RPMVERIFY_GROUP = 16 # 1 << 4 # /*!< from %verify(group) */ +RPMVERIFY_MTIME = 32 # 1 << 5 # /*!< from %verify(mtime) */ +RPMVERIFY_MODE = 64 # 1 << 6 # /*!< from %verify(mode) */ +RPMVERIFY_RDEV = 128 # 1 << 7 # /*!< from %verify(rdev) */ +RPMVERIFY_CONTEXTS = 32768 # (1 << 15) # /*!< from --nocontexts */ +RPMVERIFY_READLINKFAIL = 268435456 # (1 << 28) # /*!< readlink failed */ +RPMVERIFY_READFAIL = 536870912 # (1 << 29) # /*!< file read failed */ +RPMVERIFY_LSTATFAIL = 1073741824 # (1 << 30) # /*!< lstat failed */ +RPMVERIFY_LGETFILECONFAIL = 2147483648 # (1 << 31) # /*!< lgetfilecon failed */ + +RPMVERIFY_FAILURES = \ + (RPMVERIFY_LSTATFAIL|RPMVERIFY_READFAIL|RPMVERIFY_READLINKFAIL| \ + RPMVERIFY_LGETFILECONFAIL) + +# Bit(s) to control rpm_verify() operation. +# +VERIFY_DEFAULT = 0, # /*!< */ +VERIFY_MD5 = 1 << 0 # /*!< from --nomd5 */ +VERIFY_SIZE = 1 << 1 # /*!< from --nosize */ +VERIFY_LINKTO = 1 << 2 # /*!< from --nolinkto */ +VERIFY_USER = 1 << 3 # /*!< from --nouser */ +VERIFY_GROUP = 1 << 4 # /*!< from --nogroup */ +VERIFY_MTIME = 1 << 5 # /*!< from --nomtime */ +VERIFY_MODE = 1 << 6 # /*!< from --nomode */ +VERIFY_RDEV = 1 << 7 # /*!< from --nodev */ +# /* bits 8-14 unused, reserved for rpmVerifyAttrs */ +VERIFY_CONTEXTS = 1 << 15 # /*!< verify: from --nocontexts */ +VERIFY_FILES = 1 << 16 # /*!< verify: from --nofiles */ +VERIFY_DEPS = 1 << 17 # /*!< verify: from --nodeps */ +VERIFY_SCRIPT = 1 << 18 # /*!< verify: from --noscripts */ +VERIFY_DIGEST = 1 << 19 # /*!< verify: from --nodigest */ +VERIFY_SIGNATURE = 1 << 20 # /*!< verify: from --nosignature */ +VERIFY_PATCHES = 1 << 21 # /*!< verify: from --nopatches */ +VERIFY_HDRCHK = 1 << 22 # /*!< verify: from --nohdrchk */ +VERIFY_FOR_LIST = 1 << 23 # /*!< query: from --list */ +VERIFY_FOR_STATE = 1 << 24 # /*!< query: from --state */ +VERIFY_FOR_DOCS = 1 << 25 # /*!< query: from --docfiles */ +VERIFY_FOR_CONFIG = 1 << 26 # /*!< query: from --configfiles */ +VERIFY_FOR_DUMPFILES = 1 << 27 # /*!< query: from --dump */ +# /* bits 28-31 used in rpmVerifyAttrs */ + +# Comes from C cource. lib/rpmcli.h +VERIFY_ATTRS = \ + (VERIFY_MD5 | VERIFY_SIZE | VERIFY_LINKTO | VERIFY_USER | VERIFY_GROUP | \ + VERIFY_MTIME | VERIFY_MODE | VERIFY_RDEV | VERIFY_CONTEXTS) + +VERIFY_ALL = \ + (VERIFY_ATTRS | VERIFY_FILES | VERIFY_DEPS | VERIFY_SCRIPT | VERIFY_DIGEST |\ + VERIFY_SIGNATURE | VERIFY_HDRCHK) + + +# Some masks for what checks to NOT do on these file types. +# The C code actiually resets these up for every file. +DIR_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \ + RPMVERIFY_LINKTO) + +# These file types all have the same mask, but hopefully this will make the +# code more readable. +FIFO_FLAGS = CHR_FLAGS = BLK_FLAGS = GHOST_FLAGS = DIR_FLAGS + +LINK_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \ + RPMVERIFY_MODE | RPMVERIFY_USER | RPMVERIFY_GROUP) + +REG_FLAGS = ~(RPMVERIFY_LINKTO) + + +def s_isdev(mode): + """ + Check to see if a file is a device. + + """ + return stat.S_ISBLK(mode) | stat.S_ISCHR(mode) + +def rpmpackagelist(rts): + """ + Equivalent of rpm -qa. Intended for RefreshPackages() in the RPM Driver. + Requires rpmtransactionset() to be run first to get a ts. + Returns a list of pkgspec dicts. + + e.g. [ {'name':'foo', 'epoch':'20', 'version':'1.2', 'release':'5', 'arch':'x86_64' }, + {'name':'bar', 'epoch':'10', 'version':'5.2', 'release':'2', 'arch':'x86_64' } ] + + """ + return [{'name':header[rpm.RPMTAG_NAME], + 'epoch':header[rpm.RPMTAG_EPOCH], + 'version':header[rpm.RPMTAG_VERSION], + 'release':header[rpm.RPMTAG_RELEASE], + 'arch':header[rpm.RPMTAG_ARCH], + 'gpgkeyid':header.sprintf("%|SIGGPG?{%{SIGGPG:pgpsig}}:{None}|").split()[-1]} + for header in rts.dbMatch()] + +def getindexbykeyword(index_ts, **kwargs): + """ + Return list of indexs from the rpmdb matching keywords + ex: getHeadersByKeyword(name='foo', version='1', release='1') + + Can be passed any structure that can be indexed by the pkgspec + keyswords as other keys are filtered out. + + """ + lst = [] + name = kwargs.get('name') + if name: + index_mi = index_ts.dbMatch(rpm.RPMTAG_NAME, name) + else: + index_mi = index_ts.dbMatch() + + if 'epoch' in kwargs: + if kwargs['epoch'] != None and kwargs['epoch'] != 'None': + kwargs['epoch'] = int(kwargs['epoch']) + else: + del(kwargs['epoch']) + + keywords = [key for key in list(kwargs.keys()) \ + if key in ('name', 'epoch', 'version', 'release', 'arch')] + keywords_len = len(keywords) + for hdr in index_mi: + match = 0 + for keyword in keywords: + if hdr[keyword] == kwargs[keyword]: + match += 1 + if match == keywords_len: + lst.append(index_mi.instance()) + del index_mi + return lst + +def getheadersbykeyword(header_ts, **kwargs): + """ + Borrowed parts of this from from Yum. Need to fix it though. + Epoch is not handled right. + + Return list of headers from the rpmdb matching keywords + ex: getHeadersByKeyword(name='foo', version='1', release='1') + + Can be passed any structure that can be indexed by the pkgspec + keyswords as other keys are filtered out. + + """ + lst = [] + name = kwargs.get('name') + if name: + header_mi = header_ts.dbMatch(rpm.RPMTAG_NAME, name) + else: + header_mi = header_ts.dbMatch() + + if 'epoch' in kwargs: + if kwargs['epoch'] != None and kwargs['epoch'] != 'None': + kwargs['epoch'] = int(kwargs['epoch']) + else: + del(kwargs['epoch']) + + keywords = [key for key in list(kwargs.keys()) \ + if key in ('name', 'epoch', 'version', 'release', 'arch')] + keywords_len = len(keywords) + for hdr in header_mi: + match = 0 + for keyword in keywords: + if hdr[keyword] == kwargs[keyword]: + match += 1 + if match == keywords_len: + lst.append(hdr) + del header_mi + return lst + +def prelink_md5_check(filename): + """ + Checks if a file is prelinked. If it is run it through prelink -y + to get the unprelinked md5 and file size. + + Return 0 if the file was not prelinked, otherwise return the file size. + Always return the md5. + + """ + prelink = False + try: + plf = open(filename, "rb") + except IOError: + return False, 0 + + if prelink_exists: + if isprelink_imported: + plfd = plf.fileno() + if isprelink(plfd): + plf.close() + cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ + % (re.escape(filename)) + plf = os.popen(cmd, 'rb') + prelink = True + elif whitelist_re.search(filename) and not blacklist_re.search(filename): + plf.close() + cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ + % (re.escape(filename)) + plf = os.popen(cmd, 'rb') + prelink = True + + fsize = 0 + if py24compat: + chksum = md5.new() + else: + chksum = hashlib.md5() + while 1: + data = plf.read() + if not data: + break + fsize += len(data) + chksum.update(data) + plf.close() + file_md5 = chksum.hexdigest() + if prelink: + return file_md5, fsize + else: + return file_md5, 0 + +def prelink_size_check(filename): + """ + This check is only done if the prelink_md5_check() is not done first. + + Checks if a file is prelinked. If it is run it through prelink -y + to get the unprelinked file size. + + Return 0 if the file was not prelinked, otherwise return the file size. + + """ + fsize = 0 + try: + plf = open(filename, "rb") + except IOError: + return False + + if prelink_exists: + if isprelink_imported: + plfd = plf.fileno() + if isprelink(plfd): + plf.close() + cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ + % (re.escape(filename)) + plf = os.popen(cmd, 'rb') + + while 1: + data = plf.read() + if not data: + break + fsize += len(data) + + elif whitelist_re.search(filename) and not blacklist_re.search(filename): + plf.close() + cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ + % (re.escape(filename)) + plf = os.popen(cmd, 'rb') + + while 1: + data = plf.read() + if not data: + break + fsize += len(data) + + plf.close() + + return fsize + +def debug_verify_flags(vflags): + """ + Decodes the verify flags bits. + """ + if vflags & RPMVERIFY_MD5: + print('RPMVERIFY_MD5') + if vflags & RPMVERIFY_FILESIZE: + print('RPMVERIFY_FILESIZE') + if vflags & RPMVERIFY_LINKTO: + print('RPMVERIFY_LINKTO') + if vflags & RPMVERIFY_USER: + print('RPMVERIFY_USER') + if vflags & RPMVERIFY_GROUP: + print('RPMVERIFY_GROUP') + if vflags & RPMVERIFY_MTIME: + print('RPMVERIFY_MTIME') + if vflags & RPMVERIFY_MODE: + print('RPMVERIFY_MODE') + if vflags & RPMVERIFY_RDEV: + print('RPMVERIFY_RDEV') + if vflags & RPMVERIFY_CONTEXTS: + print('RPMVERIFY_CONTEXTS') + if vflags & RPMVERIFY_READLINKFAIL: + print('RPMVERIFY_READLINKFAIL') + if vflags & RPMVERIFY_READFAIL: + print('RPMVERIFY_READFAIL') + if vflags & RPMVERIFY_LSTATFAIL: + print('RPMVERIFY_LSTATFAIL') + if vflags & RPMVERIFY_LGETFILECONFAIL: + print('RPMVERIFY_LGETFILECONFAIL') + +def debug_file_flags(fflags): + """ + Decodes the file flags bits. + """ + if fflags & rpm.RPMFILE_CONFIG: + print('rpm.RPMFILE_CONFIG') + + if fflags & rpm.RPMFILE_DOC: + print('rpm.RPMFILE_DOC') + + if fflags & rpm.RPMFILE_ICON: + print('rpm.RPMFILE_ICON') + + if fflags & rpm.RPMFILE_MISSINGOK: + print('rpm.RPMFILE_MISSINGOK') + + if fflags & rpm.RPMFILE_NOREPLACE: + print('rpm.RPMFILE_NOREPLACE') + + if fflags & rpm.RPMFILE_GHOST: + print('rpm.RPMFILE_GHOST') + + if fflags & rpm.RPMFILE_LICENSE: + print('rpm.RPMFILE_LICENSE') + + if fflags & rpm.RPMFILE_README: + print('rpm.RPMFILE_README') + + if fflags & rpm.RPMFILE_EXCLUDE: + print('rpm.RPMFILE_EXLUDE') + + if fflags & rpm.RPMFILE_UNPATCHED: + print('rpm.RPMFILE_UNPATCHED') + + if fflags & rpm.RPMFILE_PUBKEY: + print('rpm.RPMFILE_PUBKEY') + +def rpm_verify_file(fileinfo, rpmlinktos, omitmask): + """ + Verify all the files in a package. + + Returns a list of error flags, the file type and file name. The list + entries are strings that are the same as the labels for the bitwise + flags used in the C code. + + """ + (fname, fsize, fmode, fmtime, fflags, frdev, finode, fnlink, fstate, \ + vflags, fuser, fgroup, fmd5) = fileinfo + + # 1. rpmtsRootDir stuff. What does it do and where to I get it from? + + file_results = [] + flags = vflags + + # Check to see if the file was installed - if not pretend all is ok. + # This is what the rpm C code does! + if fstate != rpm.RPMFILE_STATE_NORMAL: + return file_results + + # Get the installed files stats + try: + lstat = os.lstat(fname) + except OSError: + if not (fflags & (rpm.RPMFILE_MISSINGOK|rpm.RPMFILE_GHOST)): + file_results.append('RPMVERIFY_LSTATFAIL') + #file_results.append(fname) + return file_results + + # 5. Contexts? SELinux stuff? + + # Setup what checks to do. This is straight out of the C code. + if stat.S_ISDIR(lstat.st_mode): + flags &= DIR_FLAGS + elif stat.S_ISLNK(lstat.st_mode): + flags &= LINK_FLAGS + elif stat.S_ISFIFO(lstat.st_mode): + flags &= FIFO_FLAGS + elif stat.S_ISCHR(lstat.st_mode): + flags &= CHR_FLAGS + elif stat.S_ISBLK(lstat.st_mode): + flags &= BLK_FLAGS + else: + flags &= REG_FLAGS + + if (fflags & rpm.RPMFILE_GHOST): + flags &= GHOST_FLAGS + + flags &= ~(omitmask | RPMVERIFY_FAILURES) + + # 8. SELinux stuff. + + prelink_size = 0 + if flags & RPMVERIFY_MD5: + prelink_md5, prelink_size = prelink_md5_check(fname) + if prelink_md5 == False: + file_results.append('RPMVERIFY_MD5') + file_results.append('RPMVERIFY_READFAIL') + elif prelink_md5 != fmd5: + file_results.append('RPMVERIFY_MD5') + + if flags & RPMVERIFY_LINKTO: + linkto = os.readlink(fname) + if not linkto: + file_results.append('RPMVERIFY_READLINKFAIL') + file_results.append('RPMVERIFY_LINKTO') + else: + if len(rpmlinktos) == 0 or linkto != rpmlinktos: + file_results.append('RPMVERIFY_LINKTO') + + if flags & RPMVERIFY_FILESIZE: + if not (flags & RPMVERIFY_MD5): # prelink check hasn't been done. + prelink_size = prelink_size_check(fname) + if (prelink_size != 0): # This is a prelinked file. + if (prelink_size != fsize): + file_results.append('RPMVERIFY_FILESIZE') + elif lstat.st_size != fsize: # It wasn't a prelinked file. + file_results.append('RPMVERIFY_FILESIZE') + + if flags & RPMVERIFY_MODE: + metamode = fmode + filemode = lstat.st_mode + + # Comparing the type of %ghost files is meaningless, but perms are ok. + if fflags & rpm.RPMFILE_GHOST: + metamode &= ~0xf000 + filemode &= ~0xf000 + + if (stat.S_IFMT(metamode) != stat.S_IFMT(filemode)) or \ + (stat.S_IMODE(metamode) != stat.S_IMODE(filemode)): + file_results.append('RPMVERIFY_MODE') + + if flags & RPMVERIFY_RDEV: + if (stat.S_ISCHR(fmode) != stat.S_ISCHR(lstat.st_mode) or + stat.S_ISBLK(fmode) != stat.S_ISBLK(lstat.st_mode)): + file_results.append('RPMVERIFY_RDEV') + elif (s_isdev(fmode) & s_isdev(lstat.st_mode)): + st_rdev = lstat.st_rdev + if frdev != st_rdev: + file_results.append('RPMVERIFY_RDEV') + + if flags & RPMVERIFY_MTIME: + if lstat.st_mtime != fmtime: + file_results.append('RPMVERIFY_MTIME') + + if flags & RPMVERIFY_USER: + try: + user = pwd.getpwuid(lstat.st_uid)[0] + except KeyError: + user = None + if not user or not fuser or (user != fuser): + file_results.append('RPMVERIFY_USER') + + if flags & RPMVERIFY_GROUP: + try: + group = grp.getgrgid(lstat.st_gid)[0] + except KeyError: + group = None + if not group or not fgroup or (group != fgroup): + file_results.append('RPMVERIFY_GROUP') + + return file_results + +def rpm_verify_dependencies(header): + """ + Check package dependencies. Header is an rpm.hdr. + + Don't like opening another ts to do this, but + it was the only way I could find of clearing the ts + out. + + Have asked on the rpm-maint list on how to do + this the right way (28 Feb 2007). + + ts.check() returns: + + ((name, version, release), (reqname, reqversion), \ + flags, suggest, sense) + + """ + _ts1 = rpmtransactionset() + _ts1.addInstall(header, 'Dep Check', 'i') + dep_errors = _ts1.check() + _ts1.closeDB() + return dep_errors + +def rpm_verify_package(vp_ts, header, verify_options): + """ + Verify a single package specified by header. Header is an rpm.hdr. + + If errors are found it returns a dictionary of errors. + + """ + # Set some transaction level flags. + vsflags = 0 + if 'nodigest' in verify_options: + vsflags |= rpm._RPMVSF_NODIGESTS + if 'nosignature' in verify_options: + vsflags |= rpm._RPMVSF_NOSIGNATURES + ovsflags = vp_ts.setVSFlags(vsflags) + + # Map from the Python options to the rpm bitwise flags. + omitmask = 0 + + if 'nolinkto' in verify_options: + omitmask |= VERIFY_LINKTO + if 'nomd5' in verify_options: + omitmask |= VERIFY_MD5 + if 'nosize' in verify_options: + omitmask |= VERIFY_SIZE + if 'nouser' in verify_options: + omitmask |= VERIFY_USER + if 'nogroup' in verify_options: + omitmask |= VERIFY_GROUP + if 'nomtime' in verify_options: + omitmask |= VERIFY_MTIME + if 'nomode' in verify_options: + omitmask |= VERIFY_MODE + if 'nordev' in verify_options: + omitmask |= VERIFY_RDEV + + omitmask = ((~omitmask & VERIFY_ATTRS) ^ VERIFY_ATTRS) + + package_results = {} + + # Check Signatures and Digests. + # No idea what this might return. Need to break something to see. + # Setting the vsflags above determines what gets checked in the header. + hdr_stat = vp_ts.hdrCheck(header.unload()) + if hdr_stat: + package_results['hdr'] = hdr_stat + + # Check Package Depencies. + if 'nodeps' not in verify_options: + dep_stat = rpm_verify_dependencies(header) + if dep_stat: + package_results['deps'] = dep_stat + + # Check all the package files. + if 'nofiles' not in verify_options: + vp_fi = header.fiFromHeader() + for fileinfo in vp_fi: + # Do not bother doing anything with ghost files. + # This is what RPM does. + if fileinfo[4] & rpm.RPMFILE_GHOST: + continue + + # This is only needed because of an inconsistency in the + # rpm.fi interface. + linktos = vp_fi.FLink() + + file_stat = rpm_verify_file(fileinfo, linktos, omitmask) + + #if len(file_stat) > 0 or options.verbose: + if len(file_stat) > 0: + fflags = fileinfo[4] + if fflags & rpm.RPMFILE_CONFIG: + file_stat.append('c') + elif fflags & rpm.RPMFILE_DOC: + file_stat.append('d') + elif fflags & rpm.RPMFILE_GHOST: + file_stat.append('g') + elif fflags & rpm.RPMFILE_LICENSE: + file_stat.append('l') + elif fflags & rpm.RPMFILE_PUBKEY: + file_stat.append('P') + elif fflags & rpm.RPMFILE_README: + file_stat.append('r') + else: + file_stat.append(' ') + + file_stat.append(fileinfo[0]) # The filename. + package_results.setdefault('files', []).append(file_stat) + + # Run the verify script if there is one. + # Do we want this? + #if 'noscripts' not in verify_options: + # script_stat = rpmVerifyscript() + # if script_stat: + # package_results['script'] = script_stat + + # If there have been any errors, add the package nevra to the result. + if len(package_results) > 0: + package_results.setdefault('nevra', (header[rpm.RPMTAG_NAME], \ + header[rpm.RPMTAG_EPOCH], \ + header[rpm.RPMTAG_VERSION], \ + header[rpm.RPMTAG_RELEASE], \ + header[rpm.RPMTAG_ARCH])) + else: + package_results = None + + # Put things back the way we found them. + vsflags = vp_ts.setVSFlags(ovsflags) + + return package_results + +def rpm_verify(verify_ts, verify_pkgspec, verify_options=[]): + """ + Requires rpmtransactionset() to be run first to get a ts. + + pkgspec is a dict specifying the package + e.g.: + For a single package + { name='foo', epoch='20', version='1', release='1', arch='x86_64'} + + For all packages + {} + + Or any combination of keywords to select one or more packages to verify. + + options is a list of 'rpm --verify' options. Default is to check everything. + e.g.: + [ 'nodeps', 'nodigest', 'nofiles', 'noscripts', 'nosignature', + 'nolinkto' 'nomd5', 'nosize', 'nouser', 'nogroup', 'nomtime', + 'nomode', 'nordev' ] + + Returns a list. One list entry per package. Each list entry is a + dictionary. Dict keys are 'files', 'deps', 'nevra' and 'hdr'. + Entries only get added for the failures. If nothing failed, None is + returned. + + Its all a bit messy and probably needs reviewing. + + [ { 'hdr': [???], + 'deps: [((name, version, release), (reqname, reqversion), + flags, suggest, sense), .... ] + 'files': [ ['filename1', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER' ], + ['filename2', 'RPMVERFIY_LSTATFAIL']] + 'nevra': ['name1', 'epoch1', 'version1', 'release1', 'arch1'] } + { 'hdr': [???], + 'deps: [((name, version, release), (reqname, reqversion), + flags, suggest, sense), .... ] + 'files': [ ['filename', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER" ], + ['filename2', 'RPMVERFIY_LSTATFAIL']] + 'nevra': ['name2', 'epoch2', 'version2', 'release2', 'arch2'] } ] + + """ + verify_results = [] + headers = getheadersbykeyword(verify_ts, **verify_pkgspec) + for header in headers: + result = rpm_verify_package(verify_ts, header, verify_options) + if result: + verify_results.append(result) + + return verify_results + +def rpmtransactionset(): + """ + A simple wrapper for rpm.TransactionSet() to keep everthiing together. + Might use it to set some ts level flags later. + + """ + ts = rpm.TransactionSet() + return ts + +class Rpmtscallback(object): + """ + Callback for ts.run(). Used for adding, upgrading and removing packages. + Starting with all possible reasons codes, but bcfg2 will probably only + make use of a few of them. + + Mostly just printing stuff at the moment to understand how the callback + is used. + + """ + def __init__(self): + self.fdnos = {} + + def callback(self, reason, amount, total, key, client_data): + """ + Generic rpmts call back. + """ + if reason == rpm.RPMCALLBACK_INST_OPEN_FILE: + pass + elif reason == rpm.RPMCALLBACK_INST_CLOSE_FILE: + pass + elif reason == rpm.RPMCALLBACK_INST_START: + pass + elif reason == rpm.RPMCALLBACK_TRANS_PROGRESS or \ + reason == rpm.RPMCALLBACK_INST_PROGRESS: + pass + # rpm.RPMCALLBACK_INST_PROGRESS' + elif reason == rpm.RPMCALLBACK_TRANS_START: + pass + elif reason == rpm.RPMCALLBACK_TRANS_STOP: + pass + elif reason == rpm.RPMCALLBACK_REPACKAGE_START: + pass + elif reason == rpm.RPMCALLBACK_REPACKAGE_PROGRESS: + pass + elif reason == rpm.RPMCALLBACK_REPACKAGE_STOP: + pass + elif reason == rpm.RPMCALLBACK_UNINST_PROGRESS: + pass + elif reason == rpm.RPMCALLBACK_UNINST_START: + pass + elif reason == rpm.RPMCALLBACK_UNINST_STOP: + pass + # How do we get at this? + # RPM.modified += key + elif reason == rpm.RPMCALLBACK_UNPACK_ERROR: + pass + elif reason == rpm.RPMCALLBACK_CPIO_ERROR: + pass + elif reason == rpm.RPMCALLBACK_UNKNOWN: + pass + else: + print('ERROR - Fell through callBack') + + +def rpm_erase(erase_pkgspecs, erase_flags): + """ + pkgspecs is a list of pkgspec dicts specifying packages + e.g.: + For a single package + { name='foo', epoch='20', version='1', release='1', arch='x86_64'} + + """ + erase_ts_flags = 0 + if 'noscripts' in erase_flags: + erase_ts_flags |= rpm.RPMTRANS_FLAG_NOSCRIPTS + if 'notriggers' in erase_flags: + erase_ts_flags |= rpm.RPMTRANS_FLAG_NOTRIGGERS + if 'repackage' in erase_flags: + erase_ts_flags |= rpm.RPMTRANS_FLAG_REPACKAGE + + erase_ts = rpmtransactionset() + erase_ts.setFlags(erase_ts_flags) + + for pkgspec in erase_pkgspecs: + idx_list = getindexbykeyword(erase_ts, **pkgspec) + if len(idx_list) > 1 and not 'allmatches' in erase_flags: + #pass + print('ERROR - Multiple package match for erase', pkgspec) + else: + for idx in idx_list: + erase_ts.addErase(idx) + + #for te in erase_ts: + + erase_problems = [] + if 'nodeps' not in erase_flags: + erase_problems = erase_ts.check() + + if erase_problems == []: + erase_ts.order() + erase_callback = Rpmtscallback() + erase_ts.run(erase_callback.callback, 'Erase') + #else: + + erase_ts.closeDB() + del erase_ts + return erase_problems + +def display_verify_file(file_results): + ''' + Display file results similar to rpm --verify. + ''' + filename = file_results[-1] + filetype = file_results[-2] + + result_string = '' + + if 'RPMVERIFY_LSTATFAIL' in file_results: + result_string = 'missing ' + else: + if 'RPMVERIFY_FILESIZE' in file_results: + result_string = result_string + 'S' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_MODE' in file_results: + result_string = result_string + 'M' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_MD5' in file_results: + if 'RPMVERIFY_READFAIL' in file_results: + result_string = result_string + '?' + else: + result_string = result_string + '5' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_RDEV' in file_results: + result_string = result_string + 'D' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_LINKTO' in file_results: + if 'RPMVERIFY_READLINKFAIL' in file_results: + result_string = result_string + '?' + else: + result_string = result_string + 'L' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_USER' in file_results: + result_string = result_string + 'U' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_GROUP' in file_results: + result_string = result_string + 'G' + else: + result_string = result_string + '.' + + if 'RPMVERIFY_MTIME' in file_results: + result_string = result_string + 'T' + else: + result_string = result_string + '.' + + print(result_string + ' ' + filetype + ' ' + filename) + sys.stdout.flush() + +#=============================================================================== +# Some options and output to assist with development and testing. +# These are not intended for normal use. +if __name__ == "__main__": + + p = optparse.OptionParser() + + p.add_option('--name', action='store', \ + default=None, \ + help='''Package name to verify. + + ****************************************** + NOT SPECIFYING A NAME MEANS 'ALL' PACKAGES. + ****************************************** + + The specified operation will be carried out on all + instances of packages that match the package specification + (name, epoch, version, release, arch).''') + + p.add_option('--epoch', action='store', \ + default=None, \ + help='''Package epoch.''') + + p.add_option('--version', action='store', \ + default=None, \ + help='''Package version.''') + + p.add_option('--release', action='store', \ + default=None, \ + help='''Package release.''') + + p.add_option('--arch', action='store', \ + default=None, \ + help='''Package arch.''') + + p.add_option('--erase', '-e', action='store_true', \ + default=None, \ + help='''**************************************************** + REMOVE PACKAGES. THERE ARE NO WARNINGS. MULTIPLE + PACKAGES WILL BE REMOVED IF A FULL PACKAGE SPEC IS NOT + GIVEN. E.G. IF JUST A NAME IS GIVEN ALL INSTALLED + INSTANCES OF THAT PACKAGE WILL BE REMOVED PROVIDED + DEPENDENCY CHECKS PASS. IF JUST AN EPOCH IS GIVEN + ALL PACKAGE INSTANCES WITH THAT EPOCH WILL BE REMOVED. + ****************************************************''') + + p.add_option('--list', '-l', action='store_true', \ + help='''List package identity info. rpm -qa ish equivalent + intended for use in RefreshPackages().''') + + p.add_option('--verify', action='store_true', \ + help='''Verify Package(s). Output is only produced after all + packages has been verified. Be patient.''') + + p.add_option('--verbose', '-v', action='store_true', \ + help='''Verbose output for --verify option. Output is the + same as rpm -v --verify.''') + + p.add_option('--nodeps', action='store_true', \ + default=False, \ + help='Do not do dependency testing.') + + p.add_option('--nodigest', action='store_true', \ + help='Do not check package digests.') + + p.add_option('--nofiles', action='store_true', \ + help='Do not do file checks.') + + p.add_option('--noscripts', action='store_true', \ + help='Do not run verification scripts.') + + p.add_option('--nosignature', action='store_true', \ + help='Do not do package signature verification.') + + p.add_option('--nolinkto', action='store_true', \ + help='Do not do symlink tests.') + + p.add_option('--nomd5', action='store_true', \ + help='''Do not do MD5 checksums on files. Note that this does + not work for prelink files yet.''') + + p.add_option('--nosize', action='store_true', \ + help='''Do not do file size tests. Note that this does not work + for prelink files yet.''') + + p.add_option('--nouser', action='store_true', \ + help='Do not check file user ownership.') + + p.add_option('--nogroup', action='store_true', \ + help='Do not check file group ownership.') + + p.add_option('--nomtime', action='store_true', \ + help='Do not check file modification times.') + + p.add_option('--nomode', action='store_true', \ + help='Do not check file modes (permissions).') + + p.add_option('--nordev', action='store_true', \ + help='Do not check device node.') + + p.add_option('--notriggers', action='store_true', \ + help='Do not do not generate triggers on erase.') + + p.add_option('--repackage', action='store_true', \ + help='''Do repackage on erase.i Packages are put + in /var/spool/repackage.''') + + p.add_option('--allmatches', action='store_true', \ + help='''Remove all package instances that match the + pkgspec. + + *************************************************** + NO WARNINGS ARE GIVEN. IF THERE IS NO PACKAGE SPEC + THAT MEANS ALL PACKAGES!!!! + ***************************************************''') + + options, arguments = p.parse_args() + + pkgspec = {} + rpm_options = [] + + if options.nodeps: + rpm_options.append('nodeps') + + if options.nodigest: + rpm_options.append('nodigest') + + if options.nofiles: + rpm_options.append('nofiles') + + if options.noscripts: + rpm_options.append('noscripts') + + if options.nosignature: + rpm_options.append('nosignature') + + if options.nolinkto: + rpm_options.append('nolinkto') + + if options.nomd5: + rpm_options.append('nomd5') + + if options.nosize: + rpm_options.append('nosize') + + if options.nouser: + rpm_options.append('nouser') + + if options.nogroup: + rpm_options.append('nogroup') + + if options.nomtime: + rpm_options.append('nomtime') + + if options.nomode: + rpm_options.append('nomode') + + if options.nordev: + rpm_options.append('nordev') + + if options.repackage: + rpm_options.append('repackage') + + if options.allmatches: + rpm_options.append('allmatches') + + main_ts = rpmtransactionset() + + cmdline_pkgspec = {} + if options.name != 'all': + if options.name: + cmdline_pkgspec['name'] = str(options.name) + if options.epoch: + cmdline_pkgspec['epoch'] = str(options.epoch) + if options.version: + cmdline_pkgspec['version'] = str(options.version) + if options.release: + cmdline_pkgspec['release'] = str(options.release) + if options.arch: + cmdline_pkgspec['arch'] = str(options.arch) + + if options.verify: + results = rpm_verify(main_ts, cmdline_pkgspec, rpm_options) + for r in results: + files = r.get('files', '') + for f in files: + display_verify_file(f) + + elif options.list: + for p in rpmpackagelist(main_ts): + print(p) + + elif options.erase: + if options.name: + rpm_erase([cmdline_pkgspec], rpm_options) + else: + print('You must specify the "--name" option') + class RPM(Bcfg2.Client.Tools.PkgTool): """Support for RPM packages.""" @@ -102,11 +1170,11 @@ class RPM(Bcfg2.Client.Tools.PkgTool): 'arch':'x86_64'} ] """ self.installed = {} - refresh_ts = rpmtools.rpmtransactionset() + refresh_ts = rpmtransactionset() # Don't bother with signature checks at this stage. The GPG keys might # not be installed. refresh_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) - for nevra in rpmtools.rpmpackagelist(refresh_ts): + for nevra in rpmpackagelist(refresh_ts): self.installed.setdefault(nevra['name'], []).append(nevra) if self.setup['debug']: print("The following package instances are installed:") @@ -213,7 +1281,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): self.logger.debug(' Disabling signature check.') if self.setup.get('quick', False): - if rpmtools.prelink_exists: + if prelink_exists: flags += ['nomd5', 'nosize'] else: flags += ['nomd5'] @@ -222,9 +1290,9 @@ class RPM(Bcfg2.Client.Tools.PkgTool): if inst.get('verify', 'true') == 'false': self.instance_status[inst]['verify'] = None else: - vp_ts = rpmtools.rpmtransactionset() + vp_ts = rpmtransactionset() self.instance_status[inst]['verify'] = \ - rpmtools.rpm_verify( vp_ts, pkg, flags) + rpm_verify( vp_ts, pkg, flags) vp_ts.closeDB() del vp_ts @@ -272,7 +1340,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): self.logger.info(' Disabling signature check.') if self.setup.get('quick', False): - if rpmtools.prelink_exists: + if prelink_exists: flags += ['nomd5', 'nosize'] else: flags += ['nomd5'] @@ -281,9 +1349,9 @@ class RPM(Bcfg2.Client.Tools.PkgTool): if inst.get('verify', 'true') == 'false': self.instance_status[inst]['verify'] = None else: - vp_ts = rpmtools.rpmtransactionset() + vp_ts = rpmtransactionset() self.instance_status[inst]['verify'] = \ - rpmtools.rpm_verify( vp_ts, pkg, flags ) + rpm_verify( vp_ts, pkg, flags ) vp_ts.closeDB() del vp_ts @@ -434,7 +1502,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): self.logger.info(" This package will be deleted in a future version of the RPM driver.") #pkgspec_list.append(pkg_spec) - erase_results = rpmtools.rpm_erase(pkgspec_list, self.erase_flags) + erase_results = rpm_erase(pkgspec_list, self.erase_flags) if erase_results == []: self.modified += packages for pkg in pkgspec_list: @@ -462,7 +1530,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): % (pkgspec.get('name'), self.str_evra(pkgspec))) self.logger.info(" This package will be deleted in a future version of the RPM driver.") continue # Don't delete the gpg-pubkey packages for now. - erase_results = rpmtools.rpm_erase([pkgspec], self.erase_flags) + erase_results = rpm_erase([pkgspec], self.erase_flags) if erase_results == []: pkg_modified = True self.logger.info("Deleted %s %s" % \ @@ -536,7 +1604,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): return fix - def Install(self, packages, states): + def Install(self, packages): """ Try and fix everything that RPM.VerifyPackages() found wrong for each Package Entry. This can result in individual RPMs being @@ -557,6 +1625,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool): """ self.logger.info('Runing RPM.Install()') + states = dict() install_only_pkgs = [] gpg_keys = [] upgrade_pkgs = [] @@ -681,8 +1750,8 @@ class RPM(Bcfg2.Client.Tools.PkgTool): states[pkg_entry] = self.VerifyPackage(pkg_entry, \ self.modlists.get(pkg_entry, [])) - for entry in [ent for ent in packages if states[ent]]: - self.modified.append(entry) + self.modified.extend(ent for ent in packages if states[ent]) + return states def canInstall(self, entry): """Test if entry has enough information to be installed.""" @@ -964,9 +2033,9 @@ class RPM(Bcfg2.Client.Tools.PkgTool): (big-endian) of the key ID which is good enough for our purposes. """ - init_ts = rpmtools.rpmtransactionset() + init_ts = rpmtransactionset() init_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) - gpg_hdrs = rpmtools.getheadersbykeyword(init_ts, **{'name':'gpg-pubkey'}) + gpg_hdrs = getheadersbykeyword(init_ts, **{'name':'gpg-pubkey'}) keyids = [ header[rpm.RPMTAG_VERSION] for header in gpg_hdrs] keyids.append('None') init_ts.closeDB() diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py index e6408922f..baf930610 100644 --- a/src/lib/Bcfg2/Client/Tools/SELinux.py +++ b/src/lib/Bcfg2/Client/Tools/SELinux.py @@ -13,7 +13,6 @@ import seobject import Bcfg2.Client.XML import Bcfg2.Client.Tools from Bcfg2.Client.Tools.POSIX.File import POSIXFile -from Bcfg2.Options import get_option_parser def pack128(int_val): @@ -100,10 +99,6 @@ class SELinux(Bcfg2.Client.Tools.Tool): # http://docs.python.org/2/reference/datamodel.html#object.__getattr__ # for details - def BundleUpdated(self, _, states): - for handler in self.handlers.values(): - handler.BundleUpdated(states) - def FindExtra(self): extra = [] for handler in self.handlers.values(): @@ -119,7 +114,7 @@ class SELinux(Bcfg2.Client.Tools.Tool): in the specification """ return self.handlers[entry.tag].primarykey(entry) - def Install(self, entries, states): + def Install(self, entries): # start a transaction semanage = seobject.semanageRecords("") if hasattr(semanage, "start"): @@ -129,13 +124,14 @@ class SELinux(Bcfg2.Client.Tools.Tool): else: self.logger.debug("SELinux transactions not supported; this may " "slow things down considerably") - Bcfg2.Client.Tools.Tool.Install(self, entries, states) + states = Bcfg2.Client.Tools.Tool.Install(self, entries) if hasattr(semanage, "finish"): self.logger.debug("Committing SELinux transaction") semanage.finish() self.txn = False for func, arg, kwargs in self.post_txn_queue: states[arg] = func(*arg, **kwargs) + return states def GenericSEInstall(self, entry): """Dispatch install to the proper method according to entry tag""" @@ -177,7 +173,7 @@ class SELinuxEntryHandler(object): def __init__(self, tool, config): self.tool = tool self.logger = logging.getLogger(self.__class__.__name__) - self.setup = get_option_parser() + self.setup = tool.setup self.config = config self._records = None self._all = None @@ -370,11 +366,6 @@ class SELinuxEntryHandler(object): for key in records.keys() if key not in specified] - def BundleUpdated(self, states): - """ perform any additional magic tasks that need to be run - when a bundle is updated """ - pass - class SELinuxSebooleanHandler(SELinuxEntryHandler): """ handle SELinux boolean entries """ diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py index 4d86ac8fe..648d30d15 100644 --- a/src/lib/Bcfg2/Client/Tools/YUM.py +++ b/src/lib/Bcfg2/Client/Tools/YUM.py @@ -13,7 +13,7 @@ import yum.misc import rpmUtils.arch import Bcfg2.Client.XML import Bcfg2.Client.Tools -from Bcfg2.Options import get_option_parser +import Bcfg2.Options def build_yname(pkgname, inst): @@ -204,7 +204,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool): if hasattr(self, "setup"): setup = self.setup else: - setup = get_option_parser() + setup = Bcfg2.Options.get_option_parser() if hasattr(self, "logger"): logger = self.logger else: @@ -797,7 +797,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool): cleanup() - def Install(self, packages, states): # pylint: disable=R0912,R0914 + def Install(self, packages): # pylint: disable=R0912,R0914 """ Try and fix everything that Yum.VerifyPackages() found wrong for each Package Entry. This can result in individual RPMs being installed (for the first time), deleted, downgraded @@ -815,6 +815,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool): entry is set to True. """ self.logger.debug('Running Yum.Install()') + states = dict() install_pkgs = [] gpg_keys = [] upgrade_pkgs = [] @@ -944,8 +945,8 @@ class YUM(Bcfg2.Client.Tools.PkgTool): states[pkg_entry] = self.VerifyPackage(pkg_entry, self.modlists.get(pkg_entry, [])) - for entry in [ent for ent in packages if states[ent]]: - self.modified.append(entry) + self.modified.extend(ent for ent in packages if states[ent]) + return states def Remove(self, packages): """ diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index f604e6198..39a664df1 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -6,21 +6,12 @@ import stat import logging import Bcfg2.Client import Bcfg2.Client.XML -from Bcfg2.Options import get_option_parser from Bcfg2.Utils import Executor, ClassName from Bcfg2.Compat import walk_packages # pylint: disable=W0622 +import Bcfg2.Options __all__ = [m[1] for m in walk_packages(path=__path__)] -# pylint: disable=C0103 -#: All available tools -drivers = [item for item in __all__ if item not in ['rpmtools']] - -#: The default set of tools that will be used if "drivers" is not set -#: in bcfg2.conf -default = drivers[:] -# pylint: enable=C0103 - class ToolInstantiationError(Exception): """ This error is raised if the toolset cannot be instantiated. """ @@ -88,7 +79,7 @@ class Tool(object): """ #: A :class:`Bcfg2.Options.OptionParser` object describing the #: option set Bcfg2 was invoked with - self.setup = get_option_parser() + self.setup = Bcfg2.Options.get_option_parser() #: A :class:`logging.Logger` object that will be used by this #: tool for logging @@ -140,27 +131,27 @@ class Tool(object): (self.name, filename)) - def BundleUpdated(self, bundle, states): # pylint: disable=W0613 + def BundleUpdated(self, bundle): # pylint: disable=W0613 """ Callback that is invoked when a bundle has been updated. :param bundle: The bundle that has been updated :type bundle: lxml.etree._Element - :param states: The :attr:`Bcfg2.Client.Frame.Frame.states` dict - :type states: dict - :returns: None """ - return + :returns: dict - A dict of the state of entries suitable for + updating :attr:`Bcfg2.Client.Frame.Frame.states` + """ + return dict() - def BundleNotUpdated(self, bundle, states): # pylint: disable=W0613 + def BundleNotUpdated(self, bundle): # pylint: disable=W0613 """ Callback that is invoked when a bundle has been updated. :param bundle: The bundle that has been updated :type bundle: lxml.etree._Element - :param states: The :attr:`Bcfg2.Client.Frame.Frame.states` dict - :type states: dict - :returns: None """ - return + :returns: dict - A dict of the state of entries suitable for + updating :attr:`Bcfg2.Client.Frame.Frame.states` + """ + return dict() - def Inventory(self, states, structures=None): + def Inventory(self, structures=None): """ Take an inventory of the system as it exists. This involves two steps: @@ -175,18 +166,19 @@ class Tool(object): is the entry tag. E.g., a Path entry would be verified by calling :func:`VerifyPath`. - :param states: The :attr:`Bcfg2.Client.Frame.Frame.states` dict - :type states: dict :param structures: The list of structures (i.e., bundles) to get entries from. If this is not given, all children of :attr:`Bcfg2.Client.Tools.Tool.config` will be used. :type structures: list of lxml.etree._Element - :returns: None """ + :returns: dict - A dict of the state of entries suitable for + updating :attr:`Bcfg2.Client.Frame.Frame.states` + """ if not structures: structures = self.config.getchildren() mods = self.buildModlist() + states = dict() for struct in structures: for entry in struct.getchildren(): if self.canVerify(entry): @@ -204,8 +196,9 @@ class Tool(object): self.primarykey(entry)), exc_info=1) self.extra = self.FindExtra() + return states - def Install(self, entries, states): + def Install(self, entries): """ Install entries. 'Install' in this sense means either initially install, or update as necessary to match the specification. @@ -217,9 +210,10 @@ class Tool(object): :param entries: The entries to install :type entries: list of lxml.etree._Element - :param states: The :attr:`Bcfg2.Client.Frame.Frame.states` dict - :type states: dict - :returns: None """ + :returns: dict - A dict of the state of entries suitable for + updating :attr:`Bcfg2.Client.Frame.Frame.states` + """ + states = dict() for entry in entries: try: func = getattr(self, "Install%s" % entry.tag) @@ -235,6 +229,7 @@ class Tool(object): self.logger.error("%s: Unexpected failure installing %s" % (self.name, self.primarykey(entry)), exc_info=1) + return states def Remove(self, entries): """ Remove specified extra entries. @@ -433,30 +428,27 @@ class PkgTool(Tool): for pkg in packages) return self.pkgtool[0] % pkgargs - def Install(self, packages, states): + def Install(self, packages): """ Run a one-pass install where all required packages are installed with a single command, followed by single package installs in case of failure. :param entries: The entries to install :type entries: list of lxml.etree._Element - :param states: The :attr:`Bcfg2.Client.Frame.Frame.states` dict - :type states: dict - :returns: None """ + :returns: dict - A dict of the state of entries suitable for + updating :attr:`Bcfg2.Client.Frame.Frame.states` + """ self.logger.info("Trying single pass package install for pkgtype %s" % self.pkgtype) + states = dict() if self.cmd.run(self._get_package_command(packages)): 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 list(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')) - states[entry] = True + for entry in packages: + self.logger.debug('Setting state to true for %s' % + self.primarykey(entry)) + states[entry] = True self.RefreshPackages() else: self.logger.error("Single Pass Failed") @@ -474,10 +466,13 @@ class PkgTool(Tool): if self.cmd.run(self._get_package_command([pkg])): states[pkg] = True else: + states[pkg] = False self.logger.error("Failed to install package %s" % pkg.get('name')) self.RefreshPackages() - self.modified.extend(entry for entry in packages if states[entry]) + self.modified.extend(entry for entry in packages + if entry in states and states[entry]) + return states def RefreshPackages(self): """ Refresh the internal representation of the package @@ -567,7 +562,7 @@ class SvcTool(Tool): self.InstallService(entry) Remove.__doc__ = Tool.Remove.__doc__ - def BundleUpdated(self, bundle, states): + def BundleUpdated(self, bundle): if self.setup['servicemode'] == 'disabled': return @@ -597,9 +592,10 @@ class SvcTool(Tool): if not success: self.logger.error("Failed to manipulate service %s" % (entry.get('name'))) + return dict() BundleUpdated.__doc__ = Tool.BundleUpdated.__doc__ - def Install(self, entries, states): + def Install(self, entries): install_entries = [] for entry in entries: if entry.get('install', 'true').lower() == 'false': @@ -607,7 +603,7 @@ class SvcTool(Tool): (entry.tag, entry.get('name'))) else: install_entries.append(entry) - return Tool.Install(self, install_entries, states) + return Tool.Install(self, install_entries) Install.__doc__ = Tool.Install.__doc__ def InstallService(self, entry): diff --git a/src/lib/Bcfg2/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py index 64aa45e4e..a4aeab6c7 100644 --- a/src/lib/Bcfg2/Client/Tools/launchd.py +++ b/src/lib/Bcfg2/Client/Tools/launchd.py @@ -117,9 +117,11 @@ class launchd(Bcfg2.Client.Tools.Tool): # pylint: disable=C0103 status='on') for name in allsrv] - def BundleUpdated(self, bundle, states): + def BundleUpdated(self, bundle): """Reload launchd plist.""" - for entry in [entry for entry in bundle if self.handlesEntry(entry)]: + for entry in bundle: + if not self.handlesEntry(entry): + continue if not self.canInstall(entry): self.logger.error("Insufficient information to restart " "service %s" % entry.get('name')) diff --git a/src/lib/Bcfg2/Client/Tools/rpmtools.py b/src/lib/Bcfg2/Client/Tools/rpmtools.py deleted file mode 100755 index 32a04262d..000000000 --- a/src/lib/Bcfg2/Client/Tools/rpmtools.py +++ /dev/null @@ -1,1091 +0,0 @@ -#!/usr/bin/env python -""" - Module that uses rpm-python to implement the following rpm - functionality for the bcfg2 RPM and YUM client drivers: - - rpm -qa - rpm --verify - rpm --erase - - The code closely follows the rpm C code. - - The code was written to be used in the bcfg2 RPM/YUM drivers. - - Some command line options have been provided to assist with - testing and development, but the output isn't pretty and looks - nothing like rpm output. - - Run 'rpmtools' -h for the options. - -""" - -import grp -import optparse -import os -import pwd -import rpm -import stat -import sys -if sys.version_info >= (2, 5): - import hashlib - py24compat = False -else: - # FIXME: Remove when client python dep is 2.5 or greater - py24compat = True - import md5 - -# Determine what prelink tools we have available. -# The isprelink module is a python extension that examines the ELF headers -# to see if the file has been prelinked. If it is not present a lot of files -# are unnecessarily run through the prelink command. -try: - from isprelink import * - isprelink_imported = True -except ImportError: - isprelink_imported = False - -# If the prelink command is installed on the system then we need to do -# prelink -y on files. -if os.access('/usr/sbin/prelink', os.X_OK): - prelink_exists = True -else: - prelink_exists = False - -# If we don't have isprelink then we will use the prelink configuration file to -# filter what we have to put through prelink -y. -import re -blacklist = [] -whitelist = [] -try: - f = open('/etc/prelink.conf', mode='r') - for line in f: - if line.startswith('#'): - continue - option, pattern = line.split() - if pattern.startswith('*.'): - pattern = pattern.replace('*.', '\.') - pattern += '$' - elif pattern.startswith('/'): - pattern = '^' + pattern - if option == '-b': - blacklist.append(pattern) - elif option == '-l': - whitelist.append(pattern) - f.close() -except IOError: - pass - -blacklist_re = re.compile('|'.join(blacklist)) -whitelist_re = re.compile('|'.join(whitelist)) - -# Flags that are not defined in rpm-python. -# They are defined in lib/rpmcli.h -# Bit(s) for verifyFile() attributes. -# -RPMVERIFY_NONE = 0 # /*!< */ -RPMVERIFY_MD5 = 1 # 1 << 0 # /*!< from %verify(md5) */ -RPMVERIFY_FILESIZE = 2 # 1 << 1 # /*!< from %verify(size) */ -RPMVERIFY_LINKTO = 4 # 1 << 2 # /*!< from %verify(link) */ -RPMVERIFY_USER = 8 # 1 << 3 # /*!< from %verify(user) */ -RPMVERIFY_GROUP = 16 # 1 << 4 # /*!< from %verify(group) */ -RPMVERIFY_MTIME = 32 # 1 << 5 # /*!< from %verify(mtime) */ -RPMVERIFY_MODE = 64 # 1 << 6 # /*!< from %verify(mode) */ -RPMVERIFY_RDEV = 128 # 1 << 7 # /*!< from %verify(rdev) */ -RPMVERIFY_CONTEXTS = 32768 # (1 << 15) # /*!< from --nocontexts */ -RPMVERIFY_READLINKFAIL = 268435456 # (1 << 28) # /*!< readlink failed */ -RPMVERIFY_READFAIL = 536870912 # (1 << 29) # /*!< file read failed */ -RPMVERIFY_LSTATFAIL = 1073741824 # (1 << 30) # /*!< lstat failed */ -RPMVERIFY_LGETFILECONFAIL = 2147483648 # (1 << 31) # /*!< lgetfilecon failed */ - -RPMVERIFY_FAILURES = \ - (RPMVERIFY_LSTATFAIL|RPMVERIFY_READFAIL|RPMVERIFY_READLINKFAIL| \ - RPMVERIFY_LGETFILECONFAIL) - -# Bit(s) to control rpm_verify() operation. -# -VERIFY_DEFAULT = 0, # /*!< */ -VERIFY_MD5 = 1 << 0 # /*!< from --nomd5 */ -VERIFY_SIZE = 1 << 1 # /*!< from --nosize */ -VERIFY_LINKTO = 1 << 2 # /*!< from --nolinkto */ -VERIFY_USER = 1 << 3 # /*!< from --nouser */ -VERIFY_GROUP = 1 << 4 # /*!< from --nogroup */ -VERIFY_MTIME = 1 << 5 # /*!< from --nomtime */ -VERIFY_MODE = 1 << 6 # /*!< from --nomode */ -VERIFY_RDEV = 1 << 7 # /*!< from --nodev */ -# /* bits 8-14 unused, reserved for rpmVerifyAttrs */ -VERIFY_CONTEXTS = 1 << 15 # /*!< verify: from --nocontexts */ -VERIFY_FILES = 1 << 16 # /*!< verify: from --nofiles */ -VERIFY_DEPS = 1 << 17 # /*!< verify: from --nodeps */ -VERIFY_SCRIPT = 1 << 18 # /*!< verify: from --noscripts */ -VERIFY_DIGEST = 1 << 19 # /*!< verify: from --nodigest */ -VERIFY_SIGNATURE = 1 << 20 # /*!< verify: from --nosignature */ -VERIFY_PATCHES = 1 << 21 # /*!< verify: from --nopatches */ -VERIFY_HDRCHK = 1 << 22 # /*!< verify: from --nohdrchk */ -VERIFY_FOR_LIST = 1 << 23 # /*!< query: from --list */ -VERIFY_FOR_STATE = 1 << 24 # /*!< query: from --state */ -VERIFY_FOR_DOCS = 1 << 25 # /*!< query: from --docfiles */ -VERIFY_FOR_CONFIG = 1 << 26 # /*!< query: from --configfiles */ -VERIFY_FOR_DUMPFILES = 1 << 27 # /*!< query: from --dump */ -# /* bits 28-31 used in rpmVerifyAttrs */ - -# Comes from C cource. lib/rpmcli.h -VERIFY_ATTRS = \ - (VERIFY_MD5 | VERIFY_SIZE | VERIFY_LINKTO | VERIFY_USER | VERIFY_GROUP | \ - VERIFY_MTIME | VERIFY_MODE | VERIFY_RDEV | VERIFY_CONTEXTS) - -VERIFY_ALL = \ - (VERIFY_ATTRS | VERIFY_FILES | VERIFY_DEPS | VERIFY_SCRIPT | VERIFY_DIGEST |\ - VERIFY_SIGNATURE | VERIFY_HDRCHK) - - -# Some masks for what checks to NOT do on these file types. -# The C code actiually resets these up for every file. -DIR_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \ - RPMVERIFY_LINKTO) - -# These file types all have the same mask, but hopefully this will make the -# code more readable. -FIFO_FLAGS = CHR_FLAGS = BLK_FLAGS = GHOST_FLAGS = DIR_FLAGS - -LINK_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \ - RPMVERIFY_MODE | RPMVERIFY_USER | RPMVERIFY_GROUP) - -REG_FLAGS = ~(RPMVERIFY_LINKTO) - - -def s_isdev(mode): - """ - Check to see if a file is a device. - - """ - return stat.S_ISBLK(mode) | stat.S_ISCHR(mode) - -def rpmpackagelist(rts): - """ - Equivalent of rpm -qa. Intended for RefreshPackages() in the RPM Driver. - Requires rpmtransactionset() to be run first to get a ts. - Returns a list of pkgspec dicts. - - e.g. [ {'name':'foo', 'epoch':'20', 'version':'1.2', 'release':'5', 'arch':'x86_64' }, - {'name':'bar', 'epoch':'10', 'version':'5.2', 'release':'2', 'arch':'x86_64' } ] - - """ - return [{'name':header[rpm.RPMTAG_NAME], - 'epoch':header[rpm.RPMTAG_EPOCH], - 'version':header[rpm.RPMTAG_VERSION], - 'release':header[rpm.RPMTAG_RELEASE], - 'arch':header[rpm.RPMTAG_ARCH], - 'gpgkeyid':header.sprintf("%|SIGGPG?{%{SIGGPG:pgpsig}}:{None}|").split()[-1]} - for header in rts.dbMatch()] - -def getindexbykeyword(index_ts, **kwargs): - """ - Return list of indexs from the rpmdb matching keywords - ex: getHeadersByKeyword(name='foo', version='1', release='1') - - Can be passed any structure that can be indexed by the pkgspec - keyswords as other keys are filtered out. - - """ - lst = [] - name = kwargs.get('name') - if name: - index_mi = index_ts.dbMatch(rpm.RPMTAG_NAME, name) - else: - index_mi = index_ts.dbMatch() - - if 'epoch' in kwargs: - if kwargs['epoch'] != None and kwargs['epoch'] != 'None': - kwargs['epoch'] = int(kwargs['epoch']) - else: - del(kwargs['epoch']) - - keywords = [key for key in list(kwargs.keys()) \ - if key in ('name', 'epoch', 'version', 'release', 'arch')] - keywords_len = len(keywords) - for hdr in index_mi: - match = 0 - for keyword in keywords: - if hdr[keyword] == kwargs[keyword]: - match += 1 - if match == keywords_len: - lst.append(index_mi.instance()) - del index_mi - return lst - -def getheadersbykeyword(header_ts, **kwargs): - """ - Borrowed parts of this from from Yum. Need to fix it though. - Epoch is not handled right. - - Return list of headers from the rpmdb matching keywords - ex: getHeadersByKeyword(name='foo', version='1', release='1') - - Can be passed any structure that can be indexed by the pkgspec - keyswords as other keys are filtered out. - - """ - lst = [] - name = kwargs.get('name') - if name: - header_mi = header_ts.dbMatch(rpm.RPMTAG_NAME, name) - else: - header_mi = header_ts.dbMatch() - - if 'epoch' in kwargs: - if kwargs['epoch'] != None and kwargs['epoch'] != 'None': - kwargs['epoch'] = int(kwargs['epoch']) - else: - del(kwargs['epoch']) - - keywords = [key for key in list(kwargs.keys()) \ - if key in ('name', 'epoch', 'version', 'release', 'arch')] - keywords_len = len(keywords) - for hdr in header_mi: - match = 0 - for keyword in keywords: - if hdr[keyword] == kwargs[keyword]: - match += 1 - if match == keywords_len: - lst.append(hdr) - del header_mi - return lst - -def prelink_md5_check(filename): - """ - Checks if a file is prelinked. If it is run it through prelink -y - to get the unprelinked md5 and file size. - - Return 0 if the file was not prelinked, otherwise return the file size. - Always return the md5. - - """ - prelink = False - try: - plf = open(filename, "rb") - except IOError: - return False, 0 - - if prelink_exists: - if isprelink_imported: - plfd = plf.fileno() - if isprelink(plfd): - plf.close() - cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ - % (re.escape(filename)) - plf = os.popen(cmd, 'rb') - prelink = True - elif whitelist_re.search(filename) and not blacklist_re.search(filename): - plf.close() - cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ - % (re.escape(filename)) - plf = os.popen(cmd, 'rb') - prelink = True - - fsize = 0 - if py24compat: - chksum = md5.new() - else: - chksum = hashlib.md5() - while 1: - data = plf.read() - if not data: - break - fsize += len(data) - chksum.update(data) - plf.close() - file_md5 = chksum.hexdigest() - if prelink: - return file_md5, fsize - else: - return file_md5, 0 - -def prelink_size_check(filename): - """ - This check is only done if the prelink_md5_check() is not done first. - - Checks if a file is prelinked. If it is run it through prelink -y - to get the unprelinked file size. - - Return 0 if the file was not prelinked, otherwise return the file size. - - """ - fsize = 0 - try: - plf = open(filename, "rb") - except IOError: - return False - - if prelink_exists: - if isprelink_imported: - plfd = plf.fileno() - if isprelink(plfd): - plf.close() - cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ - % (re.escape(filename)) - plf = os.popen(cmd, 'rb') - - while 1: - data = plf.read() - if not data: - break - fsize += len(data) - - elif whitelist_re.search(filename) and not blacklist_re.search(filename): - plf.close() - cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \ - % (re.escape(filename)) - plf = os.popen(cmd, 'rb') - - while 1: - data = plf.read() - if not data: - break - fsize += len(data) - - plf.close() - - return fsize - -def debug_verify_flags(vflags): - """ - Decodes the verify flags bits. - """ - if vflags & RPMVERIFY_MD5: - print('RPMVERIFY_MD5') - if vflags & RPMVERIFY_FILESIZE: - print('RPMVERIFY_FILESIZE') - if vflags & RPMVERIFY_LINKTO: - print('RPMVERIFY_LINKTO') - if vflags & RPMVERIFY_USER: - print('RPMVERIFY_USER') - if vflags & RPMVERIFY_GROUP: - print('RPMVERIFY_GROUP') - if vflags & RPMVERIFY_MTIME: - print('RPMVERIFY_MTIME') - if vflags & RPMVERIFY_MODE: - print('RPMVERIFY_MODE') - if vflags & RPMVERIFY_RDEV: - print('RPMVERIFY_RDEV') - if vflags & RPMVERIFY_CONTEXTS: - print('RPMVERIFY_CONTEXTS') - if vflags & RPMVERIFY_READLINKFAIL: - print('RPMVERIFY_READLINKFAIL') - if vflags & RPMVERIFY_READFAIL: - print('RPMVERIFY_READFAIL') - if vflags & RPMVERIFY_LSTATFAIL: - print('RPMVERIFY_LSTATFAIL') - if vflags & RPMVERIFY_LGETFILECONFAIL: - print('RPMVERIFY_LGETFILECONFAIL') - -def debug_file_flags(fflags): - """ - Decodes the file flags bits. - """ - if fflags & rpm.RPMFILE_CONFIG: - print('rpm.RPMFILE_CONFIG') - - if fflags & rpm.RPMFILE_DOC: - print('rpm.RPMFILE_DOC') - - if fflags & rpm.RPMFILE_ICON: - print('rpm.RPMFILE_ICON') - - if fflags & rpm.RPMFILE_MISSINGOK: - print('rpm.RPMFILE_MISSINGOK') - - if fflags & rpm.RPMFILE_NOREPLACE: - print('rpm.RPMFILE_NOREPLACE') - - if fflags & rpm.RPMFILE_GHOST: - print('rpm.RPMFILE_GHOST') - - if fflags & rpm.RPMFILE_LICENSE: - print('rpm.RPMFILE_LICENSE') - - if fflags & rpm.RPMFILE_README: - print('rpm.RPMFILE_README') - - if fflags & rpm.RPMFILE_EXCLUDE: - print('rpm.RPMFILE_EXLUDE') - - if fflags & rpm.RPMFILE_UNPATCHED: - print('rpm.RPMFILE_UNPATCHED') - - if fflags & rpm.RPMFILE_PUBKEY: - print('rpm.RPMFILE_PUBKEY') - -def rpm_verify_file(fileinfo, rpmlinktos, omitmask): - """ - Verify all the files in a package. - - Returns a list of error flags, the file type and file name. The list - entries are strings that are the same as the labels for the bitwise - flags used in the C code. - - """ - (fname, fsize, fmode, fmtime, fflags, frdev, finode, fnlink, fstate, \ - vflags, fuser, fgroup, fmd5) = fileinfo - - # 1. rpmtsRootDir stuff. What does it do and where to I get it from? - - file_results = [] - flags = vflags - - # Check to see if the file was installed - if not pretend all is ok. - # This is what the rpm C code does! - if fstate != rpm.RPMFILE_STATE_NORMAL: - return file_results - - # Get the installed files stats - try: - lstat = os.lstat(fname) - except OSError: - if not (fflags & (rpm.RPMFILE_MISSINGOK|rpm.RPMFILE_GHOST)): - file_results.append('RPMVERIFY_LSTATFAIL') - #file_results.append(fname) - return file_results - - # 5. Contexts? SELinux stuff? - - # Setup what checks to do. This is straight out of the C code. - if stat.S_ISDIR(lstat.st_mode): - flags &= DIR_FLAGS - elif stat.S_ISLNK(lstat.st_mode): - flags &= LINK_FLAGS - elif stat.S_ISFIFO(lstat.st_mode): - flags &= FIFO_FLAGS - elif stat.S_ISCHR(lstat.st_mode): - flags &= CHR_FLAGS - elif stat.S_ISBLK(lstat.st_mode): - flags &= BLK_FLAGS - else: - flags &= REG_FLAGS - - if (fflags & rpm.RPMFILE_GHOST): - flags &= GHOST_FLAGS - - flags &= ~(omitmask | RPMVERIFY_FAILURES) - - # 8. SELinux stuff. - - prelink_size = 0 - if flags & RPMVERIFY_MD5: - prelink_md5, prelink_size = prelink_md5_check(fname) - if prelink_md5 == False: - file_results.append('RPMVERIFY_MD5') - file_results.append('RPMVERIFY_READFAIL') - elif prelink_md5 != fmd5: - file_results.append('RPMVERIFY_MD5') - - if flags & RPMVERIFY_LINKTO: - linkto = os.readlink(fname) - if not linkto: - file_results.append('RPMVERIFY_READLINKFAIL') - file_results.append('RPMVERIFY_LINKTO') - else: - if len(rpmlinktos) == 0 or linkto != rpmlinktos: - file_results.append('RPMVERIFY_LINKTO') - - if flags & RPMVERIFY_FILESIZE: - if not (flags & RPMVERIFY_MD5): # prelink check hasn't been done. - prelink_size = prelink_size_check(fname) - if (prelink_size != 0): # This is a prelinked file. - if (prelink_size != fsize): - file_results.append('RPMVERIFY_FILESIZE') - elif lstat.st_size != fsize: # It wasn't a prelinked file. - file_results.append('RPMVERIFY_FILESIZE') - - if flags & RPMVERIFY_MODE: - metamode = fmode - filemode = lstat.st_mode - - # Comparing the type of %ghost files is meaningless, but perms are ok. - if fflags & rpm.RPMFILE_GHOST: - metamode &= ~0xf000 - filemode &= ~0xf000 - - if (stat.S_IFMT(metamode) != stat.S_IFMT(filemode)) or \ - (stat.S_IMODE(metamode) != stat.S_IMODE(filemode)): - file_results.append('RPMVERIFY_MODE') - - if flags & RPMVERIFY_RDEV: - if (stat.S_ISCHR(fmode) != stat.S_ISCHR(lstat.st_mode) or - stat.S_ISBLK(fmode) != stat.S_ISBLK(lstat.st_mode)): - file_results.append('RPMVERIFY_RDEV') - elif (s_isdev(fmode) & s_isdev(lstat.st_mode)): - st_rdev = lstat.st_rdev - if frdev != st_rdev: - file_results.append('RPMVERIFY_RDEV') - - if flags & RPMVERIFY_MTIME: - if lstat.st_mtime != fmtime: - file_results.append('RPMVERIFY_MTIME') - - if flags & RPMVERIFY_USER: - try: - user = pwd.getpwuid(lstat.st_uid)[0] - except KeyError: - user = None - if not user or not fuser or (user != fuser): - file_results.append('RPMVERIFY_USER') - - if flags & RPMVERIFY_GROUP: - try: - group = grp.getgrgid(lstat.st_gid)[0] - except KeyError: - group = None - if not group or not fgroup or (group != fgroup): - file_results.append('RPMVERIFY_GROUP') - - return file_results - -def rpm_verify_dependencies(header): - """ - Check package dependencies. Header is an rpm.hdr. - - Don't like opening another ts to do this, but - it was the only way I could find of clearing the ts - out. - - Have asked on the rpm-maint list on how to do - this the right way (28 Feb 2007). - - ts.check() returns: - - ((name, version, release), (reqname, reqversion), \ - flags, suggest, sense) - - """ - _ts1 = rpmtransactionset() - _ts1.addInstall(header, 'Dep Check', 'i') - dep_errors = _ts1.check() - _ts1.closeDB() - return dep_errors - -def rpm_verify_package(vp_ts, header, verify_options): - """ - Verify a single package specified by header. Header is an rpm.hdr. - - If errors are found it returns a dictionary of errors. - - """ - # Set some transaction level flags. - vsflags = 0 - if 'nodigest' in verify_options: - vsflags |= rpm._RPMVSF_NODIGESTS - if 'nosignature' in verify_options: - vsflags |= rpm._RPMVSF_NOSIGNATURES - ovsflags = vp_ts.setVSFlags(vsflags) - - # Map from the Python options to the rpm bitwise flags. - omitmask = 0 - - if 'nolinkto' in verify_options: - omitmask |= VERIFY_LINKTO - if 'nomd5' in verify_options: - omitmask |= VERIFY_MD5 - if 'nosize' in verify_options: - omitmask |= VERIFY_SIZE - if 'nouser' in verify_options: - omitmask |= VERIFY_USER - if 'nogroup' in verify_options: - omitmask |= VERIFY_GROUP - if 'nomtime' in verify_options: - omitmask |= VERIFY_MTIME - if 'nomode' in verify_options: - omitmask |= VERIFY_MODE - if 'nordev' in verify_options: - omitmask |= VERIFY_RDEV - - omitmask = ((~omitmask & VERIFY_ATTRS) ^ VERIFY_ATTRS) - - package_results = {} - - # Check Signatures and Digests. - # No idea what this might return. Need to break something to see. - # Setting the vsflags above determines what gets checked in the header. - hdr_stat = vp_ts.hdrCheck(header.unload()) - if hdr_stat: - package_results['hdr'] = hdr_stat - - # Check Package Depencies. - if 'nodeps' not in verify_options: - dep_stat = rpm_verify_dependencies(header) - if dep_stat: - package_results['deps'] = dep_stat - - # Check all the package files. - if 'nofiles' not in verify_options: - vp_fi = header.fiFromHeader() - for fileinfo in vp_fi: - # Do not bother doing anything with ghost files. - # This is what RPM does. - if fileinfo[4] & rpm.RPMFILE_GHOST: - continue - - # This is only needed because of an inconsistency in the - # rpm.fi interface. - linktos = vp_fi.FLink() - - file_stat = rpm_verify_file(fileinfo, linktos, omitmask) - - #if len(file_stat) > 0 or options.verbose: - if len(file_stat) > 0: - fflags = fileinfo[4] - if fflags & rpm.RPMFILE_CONFIG: - file_stat.append('c') - elif fflags & rpm.RPMFILE_DOC: - file_stat.append('d') - elif fflags & rpm.RPMFILE_GHOST: - file_stat.append('g') - elif fflags & rpm.RPMFILE_LICENSE: - file_stat.append('l') - elif fflags & rpm.RPMFILE_PUBKEY: - file_stat.append('P') - elif fflags & rpm.RPMFILE_README: - file_stat.append('r') - else: - file_stat.append(' ') - - file_stat.append(fileinfo[0]) # The filename. - package_results.setdefault('files', []).append(file_stat) - - # Run the verify script if there is one. - # Do we want this? - #if 'noscripts' not in verify_options: - # script_stat = rpmVerifyscript() - # if script_stat: - # package_results['script'] = script_stat - - # If there have been any errors, add the package nevra to the result. - if len(package_results) > 0: - package_results.setdefault('nevra', (header[rpm.RPMTAG_NAME], \ - header[rpm.RPMTAG_EPOCH], \ - header[rpm.RPMTAG_VERSION], \ - header[rpm.RPMTAG_RELEASE], \ - header[rpm.RPMTAG_ARCH])) - else: - package_results = None - - # Put things back the way we found them. - vsflags = vp_ts.setVSFlags(ovsflags) - - return package_results - -def rpm_verify(verify_ts, verify_pkgspec, verify_options=[]): - """ - Requires rpmtransactionset() to be run first to get a ts. - - pkgspec is a dict specifying the package - e.g.: - For a single package - { name='foo', epoch='20', version='1', release='1', arch='x86_64'} - - For all packages - {} - - Or any combination of keywords to select one or more packages to verify. - - options is a list of 'rpm --verify' options. Default is to check everything. - e.g.: - [ 'nodeps', 'nodigest', 'nofiles', 'noscripts', 'nosignature', - 'nolinkto' 'nomd5', 'nosize', 'nouser', 'nogroup', 'nomtime', - 'nomode', 'nordev' ] - - Returns a list. One list entry per package. Each list entry is a - dictionary. Dict keys are 'files', 'deps', 'nevra' and 'hdr'. - Entries only get added for the failures. If nothing failed, None is - returned. - - Its all a bit messy and probably needs reviewing. - - [ { 'hdr': [???], - 'deps: [((name, version, release), (reqname, reqversion), - flags, suggest, sense), .... ] - 'files': [ ['filename1', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER' ], - ['filename2', 'RPMVERFIY_LSTATFAIL']] - 'nevra': ['name1', 'epoch1', 'version1', 'release1', 'arch1'] } - { 'hdr': [???], - 'deps: [((name, version, release), (reqname, reqversion), - flags, suggest, sense), .... ] - 'files': [ ['filename', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER" ], - ['filename2', 'RPMVERFIY_LSTATFAIL']] - 'nevra': ['name2', 'epoch2', 'version2', 'release2', 'arch2'] } ] - - """ - verify_results = [] - headers = getheadersbykeyword(verify_ts, **verify_pkgspec) - for header in headers: - result = rpm_verify_package(verify_ts, header, verify_options) - if result: - verify_results.append(result) - - return verify_results - -def rpmtransactionset(): - """ - A simple wrapper for rpm.TransactionSet() to keep everthiing together. - Might use it to set some ts level flags later. - - """ - ts = rpm.TransactionSet() - return ts - -class Rpmtscallback(object): - """ - Callback for ts.run(). Used for adding, upgrading and removing packages. - Starting with all possible reasons codes, but bcfg2 will probably only - make use of a few of them. - - Mostly just printing stuff at the moment to understand how the callback - is used. - - """ - def __init__(self): - self.fdnos = {} - - def callback(self, reason, amount, total, key, client_data): - """ - Generic rpmts call back. - """ - if reason == rpm.RPMCALLBACK_INST_OPEN_FILE: - pass - elif reason == rpm.RPMCALLBACK_INST_CLOSE_FILE: - pass - elif reason == rpm.RPMCALLBACK_INST_START: - pass - elif reason == rpm.RPMCALLBACK_TRANS_PROGRESS or \ - reason == rpm.RPMCALLBACK_INST_PROGRESS: - pass - # rpm.RPMCALLBACK_INST_PROGRESS' - elif reason == rpm.RPMCALLBACK_TRANS_START: - pass - elif reason == rpm.RPMCALLBACK_TRANS_STOP: - pass - elif reason == rpm.RPMCALLBACK_REPACKAGE_START: - pass - elif reason == rpm.RPMCALLBACK_REPACKAGE_PROGRESS: - pass - elif reason == rpm.RPMCALLBACK_REPACKAGE_STOP: - pass - elif reason == rpm.RPMCALLBACK_UNINST_PROGRESS: - pass - elif reason == rpm.RPMCALLBACK_UNINST_START: - pass - elif reason == rpm.RPMCALLBACK_UNINST_STOP: - pass - # How do we get at this? - # RPM.modified += key - elif reason == rpm.RPMCALLBACK_UNPACK_ERROR: - pass - elif reason == rpm.RPMCALLBACK_CPIO_ERROR: - pass - elif reason == rpm.RPMCALLBACK_UNKNOWN: - pass - else: - print('ERROR - Fell through callBack') - - -def rpm_erase(erase_pkgspecs, erase_flags): - """ - pkgspecs is a list of pkgspec dicts specifying packages - e.g.: - For a single package - { name='foo', epoch='20', version='1', release='1', arch='x86_64'} - - """ - erase_ts_flags = 0 - if 'noscripts' in erase_flags: - erase_ts_flags |= rpm.RPMTRANS_FLAG_NOSCRIPTS - if 'notriggers' in erase_flags: - erase_ts_flags |= rpm.RPMTRANS_FLAG_NOTRIGGERS - if 'repackage' in erase_flags: - erase_ts_flags |= rpm.RPMTRANS_FLAG_REPACKAGE - - erase_ts = rpmtransactionset() - erase_ts.setFlags(erase_ts_flags) - - for pkgspec in erase_pkgspecs: - idx_list = getindexbykeyword(erase_ts, **pkgspec) - if len(idx_list) > 1 and not 'allmatches' in erase_flags: - #pass - print('ERROR - Multiple package match for erase', pkgspec) - else: - for idx in idx_list: - erase_ts.addErase(idx) - - #for te in erase_ts: - - erase_problems = [] - if 'nodeps' not in erase_flags: - erase_problems = erase_ts.check() - - if erase_problems == []: - erase_ts.order() - erase_callback = Rpmtscallback() - erase_ts.run(erase_callback.callback, 'Erase') - #else: - - erase_ts.closeDB() - del erase_ts - return erase_problems - -def display_verify_file(file_results): - ''' - Display file results similar to rpm --verify. - ''' - filename = file_results[-1] - filetype = file_results[-2] - - result_string = '' - - if 'RPMVERIFY_LSTATFAIL' in file_results: - result_string = 'missing ' - else: - if 'RPMVERIFY_FILESIZE' in file_results: - result_string = result_string + 'S' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_MODE' in file_results: - result_string = result_string + 'M' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_MD5' in file_results: - if 'RPMVERIFY_READFAIL' in file_results: - result_string = result_string + '?' - else: - result_string = result_string + '5' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_RDEV' in file_results: - result_string = result_string + 'D' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_LINKTO' in file_results: - if 'RPMVERIFY_READLINKFAIL' in file_results: - result_string = result_string + '?' - else: - result_string = result_string + 'L' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_USER' in file_results: - result_string = result_string + 'U' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_GROUP' in file_results: - result_string = result_string + 'G' - else: - result_string = result_string + '.' - - if 'RPMVERIFY_MTIME' in file_results: - result_string = result_string + 'T' - else: - result_string = result_string + '.' - - print(result_string + ' ' + filetype + ' ' + filename) - sys.stdout.flush() - -#=============================================================================== -# Some options and output to assist with development and testing. -# These are not intended for normal use. -if __name__ == "__main__": - - p = optparse.OptionParser() - - p.add_option('--name', action='store', \ - default=None, \ - help='''Package name to verify. - - ****************************************** - NOT SPECIFYING A NAME MEANS 'ALL' PACKAGES. - ****************************************** - - The specified operation will be carried out on all - instances of packages that match the package specification - (name, epoch, version, release, arch).''') - - p.add_option('--epoch', action='store', \ - default=None, \ - help='''Package epoch.''') - - p.add_option('--version', action='store', \ - default=None, \ - help='''Package version.''') - - p.add_option('--release', action='store', \ - default=None, \ - help='''Package release.''') - - p.add_option('--arch', action='store', \ - default=None, \ - help='''Package arch.''') - - p.add_option('--erase', '-e', action='store_true', \ - default=None, \ - help='''**************************************************** - REMOVE PACKAGES. THERE ARE NO WARNINGS. MULTIPLE - PACKAGES WILL BE REMOVED IF A FULL PACKAGE SPEC IS NOT - GIVEN. E.G. IF JUST A NAME IS GIVEN ALL INSTALLED - INSTANCES OF THAT PACKAGE WILL BE REMOVED PROVIDED - DEPENDENCY CHECKS PASS. IF JUST AN EPOCH IS GIVEN - ALL PACKAGE INSTANCES WITH THAT EPOCH WILL BE REMOVED. - ****************************************************''') - - p.add_option('--list', '-l', action='store_true', \ - help='''List package identity info. rpm -qa ish equivalent - intended for use in RefreshPackages().''') - - p.add_option('--verify', action='store_true', \ - help='''Verify Package(s). Output is only produced after all - packages has been verified. Be patient.''') - - p.add_option('--verbose', '-v', action='store_true', \ - help='''Verbose output for --verify option. Output is the - same as rpm -v --verify.''') - - p.add_option('--nodeps', action='store_true', \ - default=False, \ - help='Do not do dependency testing.') - - p.add_option('--nodigest', action='store_true', \ - help='Do not check package digests.') - - p.add_option('--nofiles', action='store_true', \ - help='Do not do file checks.') - - p.add_option('--noscripts', action='store_true', \ - help='Do not run verification scripts.') - - p.add_option('--nosignature', action='store_true', \ - help='Do not do package signature verification.') - - p.add_option('--nolinkto', action='store_true', \ - help='Do not do symlink tests.') - - p.add_option('--nomd5', action='store_true', \ - help='''Do not do MD5 checksums on files. Note that this does - not work for prelink files yet.''') - - p.add_option('--nosize', action='store_true', \ - help='''Do not do file size tests. Note that this does not work - for prelink files yet.''') - - p.add_option('--nouser', action='store_true', \ - help='Do not check file user ownership.') - - p.add_option('--nogroup', action='store_true', \ - help='Do not check file group ownership.') - - p.add_option('--nomtime', action='store_true', \ - help='Do not check file modification times.') - - p.add_option('--nomode', action='store_true', \ - help='Do not check file modes (permissions).') - - p.add_option('--nordev', action='store_true', \ - help='Do not check device node.') - - p.add_option('--notriggers', action='store_true', \ - help='Do not do not generate triggers on erase.') - - p.add_option('--repackage', action='store_true', \ - help='''Do repackage on erase.i Packages are put - in /var/spool/repackage.''') - - p.add_option('--allmatches', action='store_true', \ - help='''Remove all package instances that match the - pkgspec. - - *************************************************** - NO WARNINGS ARE GIVEN. IF THERE IS NO PACKAGE SPEC - THAT MEANS ALL PACKAGES!!!! - ***************************************************''') - - options, arguments = p.parse_args() - - pkgspec = {} - rpm_options = [] - - if options.nodeps: - rpm_options.append('nodeps') - - if options.nodigest: - rpm_options.append('nodigest') - - if options.nofiles: - rpm_options.append('nofiles') - - if options.noscripts: - rpm_options.append('noscripts') - - if options.nosignature: - rpm_options.append('nosignature') - - if options.nolinkto: - rpm_options.append('nolinkto') - - if options.nomd5: - rpm_options.append('nomd5') - - if options.nosize: - rpm_options.append('nosize') - - if options.nouser: - rpm_options.append('nouser') - - if options.nogroup: - rpm_options.append('nogroup') - - if options.nomtime: - rpm_options.append('nomtime') - - if options.nomode: - rpm_options.append('nomode') - - if options.nordev: - rpm_options.append('nordev') - - if options.repackage: - rpm_options.append('repackage') - - if options.allmatches: - rpm_options.append('allmatches') - - main_ts = rpmtransactionset() - - cmdline_pkgspec = {} - if options.name != 'all': - if options.name: - cmdline_pkgspec['name'] = str(options.name) - if options.epoch: - cmdline_pkgspec['epoch'] = str(options.epoch) - if options.version: - cmdline_pkgspec['version'] = str(options.version) - if options.release: - cmdline_pkgspec['release'] = str(options.release) - if options.arch: - cmdline_pkgspec['arch'] = str(options.arch) - - if options.verify: - results = rpm_verify(main_ts, cmdline_pkgspec, rpm_options) - for r in results: - files = r.get('files', '') - for f in files: - display_verify_file(f) - - elif options.list: - for p in rpmpackagelist(main_ts): - print(p) - - elif options.erase: - if options.name: - rpm_erase([cmdline_pkgspec], rpm_options) - else: - print('You must specify the "--name" option') -- cgit v1.2.3-1-g7c22