summaryrefslogtreecommitdiff
path: root/whatmaps/command.py
diff options
context:
space:
mode:
Diffstat (limited to 'whatmaps/command.py')
-rwxr-xr-xwhatmaps/command.py372
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