diff options
Diffstat (limited to 'whatmaps/command.py')
-rwxr-xr-x | whatmaps/command.py | 372 |
1 files changed, 22 insertions, 350 deletions
diff --git a/whatmaps/command.py b/whatmaps/command.py index 502f7d3..3225f54 100755 --- a/whatmaps/command.py +++ b/whatmaps/command.py @@ -16,6 +16,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +from __future__ import print_function + import glob import os import logging @@ -26,347 +28,16 @@ import sys import errno from optparse import OptionParser try: - import apt_pkg -except ImportError: - apt_pkg = None -try: import lsb_release except ImportError: lsb_release = None -from whatmaps.process import Process - - -class PkgError(Exception): - pass - - -class Distro(object): - """ - A distribution - @cvar id: distro id as returned by lsb-release - """ - id = None - service_blacklist = set() - _pkg_services = {} - _pkg_blacklist = {} - - @classmethod - def pkg(klass, name): - """Return package object named name""" - raise NotImplementedError - - @classmethod - def pkg_by_file(klass, path): - """Return package object that contains path""" - raise NotImplementedError - - @classmethod - def restart_service_cmd(klass, service): - """Command to restart service""" - raise NotImplementedError - - @classmethod - def restart_service(klass, service): - """Restart a service""" - subprocess.call(klass.restart_service_cmd(service)) - - @classmethod - def pkg_services(klass, pkg): - """ - List of services that package pkg needs restarted that aren't part - of pkg itself - """ - try: - return klass._pkg_services[pkg.name] - except KeyError: - return [] - - @classmethod - def pkg_service_blacklist(klass, pkg): - """ - List of services in pkg that we don't want to be restarted even when - a binary from this package maps a shared lib that changed. - """ - try: - return klass._pkg_service_blacklist[pkg.name] - except KeyError: - return [] - - @classmethod - def has_apt(klass): - """Does the distribution use apt""" - return False - - -class Pkg(object): - """ - A package in a distribution - @var services: list of services provided by package - @var shared_objects: list of shared objects shipped in this package - @cvar type: package type (e.g. RPM or Debian) - @cvar _so_regex: regex that matches shared objects in the list returned by - _get_contents - @cvar _list_contents: command to list contents of a package, will be passed - to subprocess. "$pkg_name" will be replaced by the package - name. - """ - - type = None - services = None - shared_objects = None - _so_regex = re.compile(r'(?P<so>/.*\.so(\.[^/]*)$)') - _list_contents = None - - def __init__(self, name): - self.name = name - self._services = None - self._shared_objects = None - self._contents = None - - def __repr__(self): - return "<%s Pkg object name:'%s'>" % (self.type, self.name) - - def _get_contents(self): - """List of files in the package""" - if self._contents: - return self._contents - else: - cmd = [ string.Template(arg).substitute(arg, pkg_name=self.name) - for arg in self._list_contents ] - list_contents = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = list_contents.communicate()[0] - if list_contents.returncode: - raise PkgError - self.contents = output.split('\n') - return self.contents - - -class DebianDistro(Distro): - "Debian (dpkg) based distribution""" - id = 'Debian' - - _pkg_services = { 'apache2-mpm-worker': [ 'apache2' ], - 'apache2-mpm-prefork': [ 'apache2' ], - 'apache2.2-bin': [ 'apache2' ], - 'dovecot-imapd': [ 'dovecot' ], - 'dovecot-pop3d': [ 'dovecot' ], - 'exim4-daemon-light': [ 'exim4' ], - 'exim4-daemon-heavy': [ 'exim4' ], - 'qemu-system-x86_64': [ 'libvirt-guests' ], - } - - # Per package blacklist - _pkg_service_blacklist = { 'libvirt-bin': [ 'libvirt-guests' ] } - - # Per distro blacklist - service_blacklist = set(['kvm', 'qemu-kvm', 'qemu-system-x86']) - - @classmethod - def pkg(klass, name): - return DebianPkg(name) - - @classmethod - def pkg_by_file(klass, path): - find_file = subprocess.Popen(['dpkg-query', '-S', path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = find_file.communicate()[0] - if find_file.returncode: - return None - pkg = output.split(':')[0] - return DebianPkg(pkg) - - @classmethod - def restart_service_cmd(klass, name): - return ['invoke-rc.d', name, 'restart'] - - @classmethod - def has_apt(klass): - return True - - @staticmethod - def read_apt_pipeline(): - whatmaps_enabled = False - - version = sys.stdin.readline().rstrip() - if version != "VERSION 2": - logging.error("Wrong or missing VERSION from apt pipeline\n" - "(is Dpkg::Tools::Options::/usr/bin/whatmaps::Version set to 2?)") - raise PkgError - - while 1: - aptconfig = sys.stdin.readline() - if not aptconfig or aptconfig == '\n': - break - if aptconfig.startswith('Whatmaps::Enable-Restart=') and \ - aptconfig.strip().split('=', 1)[1].lower() in ["true", "1"]: - logging.debug("Service restarts enabled") - whatmaps_enabled = True - - if not whatmaps_enabled: - return None - - pkgs = {} - for line in sys.stdin.readlines(): - if not line: - break - (pkgname, oldversion, compare, newversion, filename) = line.split() - - if filename == '**CONFIGURE**': - if oldversion != '-': # Updates only - pkgs[pkgname] = DebianPkg(pkgname) - pkgs[pkgname].version = newversion - return pkgs - - - @classmethod - def _security_update_origins(klass): - "Determine security update origins from apt configuration" - codename = lsb_release.get_distro_information()['CODENAME'] - def _subst(line): - mapping = {'distro_codename' : codename, - 'distro_id' : klass.id, } - return string.Template(line).substitute(mapping) - - origins = [] - for s in apt_pkg.config.value_list('Whatmaps::Security-Update-Origins'): - (distro_id, distro_codename) = s.split() - origins.append((_subst(distro_id), - _subst(distro_codename))) - logging.debug("Security Update Origins: %s", origins) - return origins - - - @classmethod - def filter_security_updates(klass, pkgs): - """Filter on security updates""" - - apt_pkg.init() - acquire = apt_pkg.Acquire() - cache = apt_pkg.Cache() - - security_update_origins = klass._security_update_origins() - security_updates = {} - - for pkg in pkgs.values(): - cache_pkg = cache[pkg.name] - for cache_version in cache_pkg.version_list: - if pkg.version == cache_version.ver_str: - for pfile, _ in cache_version.file_list: - for origin in security_update_origins: - if pfile.origin == origin[0] and \ - pfile.archive == origin[1]: - security_updates[pkg] = pkg - break - return security_updates - - -class DebianPkg(Pkg): - type = 'Debian' - _init_script_re = re.compile('/etc/init.d/[\w\-\.]') - _list_contents = ['dpkg-query', '-L', '${pkg_name}' ] - - def __init__(self, name): - Pkg.__init__(self, name) - - @property - def shared_objects(self): - if self._shared_objects != None: - return self._shared_objects - - self._shared_objects = [] - contents = self._get_contents() - - for line in contents: - m = self._so_regex.match(line) - if m: - self._shared_objects.append(m.group('so')) - return self._shared_objects - - @property - def services(self): - if self._services != None: - return self._services - - self._services = [] - contents = self._get_contents() - # Only supports sysvinit so far: - for line in contents: - if self._init_script_re.match(line): - self._services.append(os.path.basename(line.strip())) - return self._services - - -class RedHatDistro(Distro): - "RPM based distribution""" - _pkg_re = re.compile(r'(?P<pkg>[\w\-\+]+)-(?P<ver>[\w\.]+)' - '-(?P<rel>[\w\.]+)\.(?P<arch>.+)') - - @classmethod - def pkg(klass, name): - return RpmPkg(name) - - @classmethod - def pkg_by_file(klass, path): - find_file = subprocess.Popen(['rpm', '-qf', path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = find_file.communicate()[0] - if find_file.returncode: - return None - m = klass._pkg_re.match(output.strip()) - if m: - pkg = m.group('pkg') - else: - pkg = output.strip() - return RpmPkg(pkg) - - @classmethod - def restart_service_cmd(klass, name): - return ['service', name, 'restart'] - - -class FedoraDistro(RedHatDistro): - id = 'Fedora' - - -class RpmPkg(Pkg): - type = 'RPM' - _init_script_re = re.compile('/etc/rc.d/init.d/[\w\-\.]') - _list_contents = [ 'rpm', '-ql', '$pkg_name' ] - - def __init__(self, name): - Pkg.__init__(self, name) - - @property - def shared_objects(self): - if self._shared_objects != None: - return self._shared_objects - - self._shared_objects = [] - contents = self._get_contents() - - for line in contents: - m = self._so_regex.match(line) - if m: - self._shared_objects.append(m.group('so')) - return self._shared_objects - - @property - def services(self): - if self._services != None: - return self._services - - self._services = [] - contents = self._get_contents() - # Only supports sysvinit so far: - for line in contents: - if self._init_script_re.match(line): - self._services.append(os.path.basename(line.strip())) - return self._services +from . process import Process +from . debiandistro import DebianDistro +from . redhatdistro import FedoraDistro +from . pkg import Pkg, PkgError +from . debianpkg import DebianPkg +from . rpmpkg import RpmPkg def check_maps(procs, shared_objects): @@ -374,7 +45,7 @@ def check_maps(procs, shared_objects): for proc in procs: for so in shared_objects: if proc.maps(so): - if restart_procs.has_key(proc.exe): + if proc.exe in restart_procs: restart_procs[proc.exe] += [ proc ] else: restart_procs[proc.exe] = [ proc ] @@ -427,13 +98,13 @@ def detect_distro(): def write_cmd_file(services, cmd_file, distro): "Write out commands needed to restart the services to a file" - out = file(cmd_file, 'w') - print >>out, '#! /bin/sh' + out = open(cmd_file, 'w') + print('#! /bin/sh', file=out) for service in services: logging.debug("Need to restart %s", service) - print >>out, " ".join(distro.restart_service_cmd(service)) + print(" ".join(distro.restart_service_cmd(service)), file=out) out.close() - os.chmod(cmd_file, 0755) + os.chmod(cmd_file, 0o755) def main(argv): @@ -494,13 +165,14 @@ def main(argv): logging.error("Cannot parse contents of %s" % pkg.name) return 1 logging.debug("Found shared objects:") - map(lambda x: logging.debug(" %s", x), shared_objects) + for so in shared_objects: + logging.debug(" %s", so) # Find processes that map them restart_procs = check_maps(get_all_pids(), shared_objects) logging.debug("Processes that map them:") - map(lambda (x, y): logging.debug(" Exe: %s Pids: %s", x, y), - restart_procs.items()) + for exe, pids in restart_procs.items(): + logging.debug(" Exe: %s Pids: %s", exe, pids), # Find packages that contain the binaries of these processes pkgs = {} @@ -509,15 +181,15 @@ def main(argv): if not pkg: logging.warning("No package found for '%s' - restart manually" % proc) else: - if pkgs.has_key(pkg.name): + if pkg.name in pkgs: pkgs[pkg.name].procs.append(proc) else: pkg.procs = [ proc ] pkgs[pkg.name] = pkg logging.info("Packages that ship the affected binaries:") - map(lambda x: logging.info(" Pkg: %s, binaries: %s" % (x.name, x.procs)), - pkgs.values()) + for pkg in pkgs.values(): + logging.info(" Pkg: %s, binaries: %s" % (pkg.name, pkg.procs)) all_services = set() try: @@ -548,9 +220,9 @@ def main(argv): logging.info("Restarting %s" % service) distro.restart_service(service) elif all_services: - print "Services that possibly need to be restarted:" + print("Services that possibly need to be restarted:") for s in all_services: - print s + print(s) return 0 |