From f3eb3148238ea38683c1586518bbecd108353c65 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Tue, 13 Dec 2005 21:38:02 +0000 Subject: set keyword attributes on most files added Ed's client and server cleanups Modified the debian and redhat toolsets to produce nicer output ** Broke performance reports git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1623 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Client/Debian.py | 49 +++++++++------- src/lib/Client/Gentoo.py | 2 +- src/lib/Client/Redhat.py | 11 ++-- src/lib/Client/Solaris.py | 1 + src/lib/Client/Toolset.py | 143 +++++++++++++++++++++++++++++++++++----------- 5 files changed, 148 insertions(+), 58 deletions(-) (limited to 'src/lib/Client') diff --git a/src/lib/Client/Debian.py b/src/lib/Client/Debian.py index 35b398ffd..9260a2afe 100644 --- a/src/lib/Client/Debian.py +++ b/src/lib/Client/Debian.py @@ -7,32 +7,35 @@ from re import compile as regcompile import apt_pkg -from Bcfg2.Client.Toolset import Toolset, saferun +from Bcfg2.Client.Toolset import Toolset class ToolsetImpl(Toolset): '''The Debian toolset implements package and service operations and inherits the rest from Toolset.Toolset''' + __name__ = 'Debian' __important__ = ["/etc/apt/sources.list", "/var/cache/debconf/config.dat", \ "/var/cache/debconf/templates.dat", '/etc/passwd', '/etc/group', \ '/etc/apt/apt.conf'] - pkgtool = {'deb':('DEBIAN_FRONTEND=noninteractive apt-get --reinstall -q=2 --force-yes -y install %s >/dev/null 2>&1', + pkgtool = {'deb':('DEBIAN_FRONTEND=noninteractive apt-get --reinstall -q=2 --force-yes -y install %s', ('%s=%s', ['name', 'version']))} svcre = regcompile("/etc/.*/[SK]\d\d(?P\S+)") def __init__(self, cfg, setup): Toolset.__init__(self, cfg, setup) self.cfg = cfg + self.CondPrint('debug', 'Configuring Debian toolset') environ["DEBIAN_FRONTEND"] = 'noninteractive' - system("dpkg --force-confold --configure -a > /dev/null 2>&1") + self.saferun("dpkg --force-confold --configure -a") if not self.setup['build']: - system("dpkg-reconfigure -f noninteractive debconf < /dev/null > /dev/null 2>&1") - system("apt-get clean > /dev/null 2>&1") - system("apt-get -q=2 -y update > /dev/null 2>&1") + self.saferun("/usr/sbin/dpkg-reconfigure -f noninteractive debconf < /dev/null") + self.saferun("apt-get clean") + self.saferun("apt-get -q=2 -y update") self.installed = {} self.pkgwork = {'add':[], 'update':[], 'remove':[]} for pkg in [cpkg for cpkg in self.cfg.findall(".//Package") if not cpkg.attrib.has_key('type')]: pkg.set('type', 'deb') self.Refresh() + self.CondPrint('debug', 'Done configuring Debian toolset') def Refresh(self): '''Refresh memory hashes of packages''' @@ -44,7 +47,6 @@ class ToolsetImpl(Toolset): self.installed[pkg.Name] = pkg.CurrentVer.VerStr # implement entry (Verify|Install) ops - def VerifyService(self, entry): '''Verify Service status for entry''' rawfiles = glob("/etc/rc*.d/*%s" % (entry.get('name'))) @@ -74,24 +76,27 @@ class ToolsetImpl(Toolset): if self.setup['dryrun']: print "Disabling service %s" % (entry.get('name')) else: - system("/etc/init.d/%s stop > /dev/null 2>&1" % (entry.get('name'))) - cmdrc = system("/usr/sbin/update-rc.d -f %s remove > /dev/null 2>&1" % - entry.get('name')) + self.saferun("/etc/init.d/%s stop" % (entry.get('name'))) + cmdrc = self.saferun("/usr/sbin/update-rc.d -f %s remove" % entry.get('name'))[0] else: if self.setup['dryrun']: print "Enabling service %s" % (entry.attrib['name']) else: - cmdrc = system("/usr/sbin/update-rc.d %s defaults > /dev/null 2>&1" % (entry.attrib['name'])) + cmdrc = self.saferun("/usr/sbin/update-rc.d %s defaults" % (entry.attrib['name']))[0] if cmdrc: return False return True def VerifyPackage(self, entry, modlist): - '''Verify Package for entry''' + '''Verify package for entry''' + if not entry.attrib.has_key('version'): + self.CondPrint('verbose', "Cannot verify unversioned package %s" % + (entry.attrib['name'])) + return False if self.installed.has_key(entry.attrib['name']): if self.installed[entry.attrib['name']] == entry.attrib['version']: if not self.setup['quick']: - output = saferun("debsums -s %s" % entry.get('name'))[1] + output = self.saferun("/usr/bin/debsums -s %s" % entry.get('name'))[1] if [filename for filename in output if filename not in modlist]: return False return True @@ -103,7 +108,7 @@ class ToolsetImpl(Toolset): allsrv = [] [allsrv.append(self.svcre.match(fname).group('name')) for fname in glob("/etc/rc[12345].d/S*") if self.svcre.match(fname).group('name') not in allsrv] - self.CondPrint('debug', "Found active services: %s" % allsrv) + self.CondDisplayList('debug', "Found active services:", allsrv) csrv = self.cfg.findall(".//Service") [allsrv.remove(svc.get('name')) for svc in csrv if svc.get('status') == 'on' and svc.get('name') in allsrv] @@ -111,18 +116,22 @@ class ToolsetImpl(Toolset): def HandleExtra(self): '''Deal with extra configuration detected''' + if self.setup['dryrun']: + return + if len(self.pkgwork) > 0: if self.setup['remove'] in ['all', 'packages']: - self.CondPrint('verbose', "Removing packages: %s" % self.pkgwork['remove']) - if not system("apt-get remove %s" % " ".join(self.pkgwork['remove'])): + self.CondDisplayList('verbose', "Removing packages", self.pkgwork['remove']) + if not self.saferun("apt-get remove %s" % " ".join(self.pkgwork['remove']))[0]: self.pkgwork['remove'] = [] else: - self.CondPrint('verbose', "Need to remove packages: %s" % self.pkgwork['remove']) + self.CondDisplayList('verbose', "Need to remove packages:", self.pkgwork['remove']) + if len(self.extra_services) > 0: if self.setup['remove'] in ['all', 'services']: - self.CondPrint('verbose', "Removing services: %s" % self.extra_services) + self.CondDisplayList('verbose', "Removing services:", self.extra_services) [self.extra_services.remove(serv) for serv in self.extra_services if - not system("rm -f /etc/rc*.d/S??%s" % serv)] + not self.saferun("rm -f /etc/rc*.d/S??%s" % serv)[0]] else: - self.CondPrint('verbose', "Need to remove services: %s" % self.extra_services) + self.CondDisplayList('verbose', "Need to remove services:", self.extra_services) diff --git a/src/lib/Client/Gentoo.py b/src/lib/Client/Gentoo.py index 1bd698a4a..84ca313c5 100644 --- a/src/lib/Client/Gentoo.py +++ b/src/lib/Client/Gentoo.py @@ -1,5 +1,5 @@ '''This provides bcfg2 support for Gentoo''' -__revision__ = '$Revision: $' +__revision__ = '$Revision$' from os import popen, system, stat from popen2 import Popen4 diff --git a/src/lib/Client/Redhat.py b/src/lib/Client/Redhat.py index 57f1a17ba..6c9d0d7c9 100644 --- a/src/lib/Client/Redhat.py +++ b/src/lib/Client/Redhat.py @@ -1,5 +1,5 @@ # This is the bcfg2 support for redhat -# $Id: $ +# $Id$ '''This is redhat client support''' __revision__ = '$Revision$' @@ -10,6 +10,7 @@ from Bcfg2.Client.Toolset import Toolset, saferun class ToolsetImpl(Toolset): '''This class implelements support for rpm packages and standard chkconfig services''' + __name__ = 'Redhat' pkgtool = {'rpm':("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"]))} def __init__(self, cfg, setup): @@ -111,21 +112,21 @@ class ToolsetImpl(Toolset): self.pkgwork['remove'] = [] self.Inventory() else: - self.CondPrint('verbose', "Need to remove packages: %s" % self.pkgwork['remove']) + self.CondDisplayList('verbose', "Need to remove packages", self.pkgwork['remove']) if len(self.extra_services) > 0: if self.setup['remove'] in ['all', 'services']: - self.CondPrint('verbose', "Removing services: %s" % self.extra_services) + self.CondDisplayList('verbose', 'Removing services:', self.extra_services) for service in self.extra_services: if not system("/sbin/chkconfig %s off" % service): self.extra_services.remove(service) else: - self.CondPrint('verbose', "Need to remove services: %s" % self.extra_services) + self.CondDisplayList('verbose', 'Need to remove services:', self.extra_services) def Inventory(self): '''Do standard inventory plus debian extra service check''' Toolset.Inventory(self) allsrv = [line.split()[0] for line in popen("/sbin/chkconfig --list|grep :on").readlines()] - self.CondPrint('debug', "Found active services: %s" % allsrv) + self.CondDisplayList('debug', 'Found active services:', allsrv) csrv = self.cfg.findall(".//Service") [allsrv.remove(svc.get('name')) for svc in csrv if svc.get('status') == 'on' and svc.get('name') in allsrv] diff --git a/src/lib/Client/Solaris.py b/src/lib/Client/Solaris.py index a02df4cce..8ab506aa9 100644 --- a/src/lib/Client/Solaris.py +++ b/src/lib/Client/Solaris.py @@ -31,6 +31,7 @@ class ToolsetImpl(Toolset): 'encap':("/local/sbin/epkg -l -q %s", ("%s", ["url"]))} splitter = regcompile('.*/(?P[\w-]+)\-(?P[\w\.-]+)') ptypes = {} + __name__ = 'Solaris' def __init__(self, cfg, setup): Toolset.__init__(self, cfg, setup) diff --git a/src/lib/Client/Toolset.py b/src/lib/Client/Toolset.py index 32deea99b..7c2da5f14 100644 --- a/src/lib/Client/Toolset.py +++ b/src/lib/Client/Toolset.py @@ -11,10 +11,10 @@ from stat import S_IWGRP, S_IRGRP, S_IXOTH, S_IWOTH, S_IROTH, ST_MODE, S_ISDIR from stat import S_IFREG, ST_UID, ST_GID, S_ISREG, S_IFDIR, S_ISLNK from sys import exc_info import stat as statmod +from math import floor, ceil #from time import asctime, localtime from traceback import extract_tb from popen2 import Popen4 - from lxml.etree import Element, SubElement, tostring def calc_perms(initial, perms): @@ -31,19 +31,10 @@ def calc_perms(initial, perms): tempperms |= perm return tempperms -def saferun(command): - '''Run a command in a pipe dealing with stdout buffer overloads''' - runpipe = Popen4(command, bufsize=16384) - output = runpipe.fromchild.read() - cmdstat = runpipe.poll() - while cmdstat == -1: - output += runpipe.fromchild.read() - cmdstat = runpipe.poll() - return (cmdstat, [line for line in output.split('\n') if line]) - class Toolset(object): '''The toolset class contains underlying command support and all states''' __important__ = [] + __name__ = 'Toolset' pkgtool = ('echo', ('%s', ['name'])) def __init__(self, cfg, setup): @@ -57,20 +48,97 @@ class Toolset(object): self.installed = {} self.pkgwork = {'add':[], 'update':[], 'remove':[]} self.extra_services = [] + (self.height, self.width) = self.get_height_width() if self.__important__: for cfile in [cfl for cfl in cfg.findall(".//ConfigFile") if cfl.get('name') in self.__important__]: self.VerifyEntry(cfile) if not self.states[cfile]: self.InstallConfigFile(cfile) + def saferun(self, command): + '''Run a command in a pipe dealing with stdout buffer overloads''' + self.CondPrint('debug', '> %s' % command) + + runpipe = Popen4(command, bufsize=16384) + output = runpipe.fromchild.read() + if len(output) > 0: + self.CondPrint('debug', '< %s' % output) + cmdstat = runpipe.poll() + while cmdstat == -1: + moreOutput = runpipe.fromchild.read() + if len(moreOutput) > 0: + self.CondPrint('debug', '< %s' % moreOutput) + output += moreOutput + cmdstat = runpipe.poll() + + return (cmdstat, [line for line in output.split('\n') if line]) + def CondPrint(self, state, msg): '''Conditionally print message''' if self.setup[state]: try: - print msg + prefix = "%s[%s]: " % (self.__name__, state) + line_len = self.width-len(prefix) + for line in msg.split('\n'): + inner_lines = int(floor(float(len(line)) / line_len))+1 + for i in xrange(inner_lines): + print "%s%s" % (prefix, line[i*line_len:(i+1)*line_len]) except IOError: pass + def get_height_width(self): + try: + import termios, struct, fcntl + height, width = struct.unpack('hhhh', + fcntl.ioctl(0, termios.TIOCGWINSZ, + "\000"*8))[0:2] + return height, width + except: + return 25, 80 + + def FormattedCondPrint(self, state, items): + items.sort() + screenWidth = self.width - len("%s[%s]:" % (self.__name__, state)) + columnWidth = 1 + for item in items: + if len(item) > columnWidth: + columnWidth = len(item) + columnWidth += 1 + + columns = int(floor(float(screenWidth) / columnWidth)) + lines = int(ceil(float(len(items)) / columns)) + + for lineNumber in xrange(lines): + lineItems = [] + for columnNumber in xrange(columns): + itemNumber = int(columnNumber*lines + lineNumber) + if itemNumber < len(items): + lineItems.append(items[itemNumber]) + format = "%%-%ss" % columnWidth + lineText = "".join([format % item for item in lineItems]) + self.CondPrint(state, lineText.rstrip()) + + def CondDisplayList(self, state, title, items): + self.CondPrint(state, title) + self.FormattedCondPrint(state, items) + self.CondPrint(state, '') + + def CondDisplayState(self, state, phase): + self.CondPrint(state, 'Phase: %s' % phase) + self.CondPrint(state, 'Correct entries:\t%d' + % self.states.values().count(True)) + self.CondPrint(state, 'Incorrect entries:\t%d' % + self.states.values().count(False)) + self.CondPrint(state, 'Total managed entries:\t%d' % + len(self.states.values())) + self.CondPrint(state, 'Unmanaged entries:\t%d' % + len(self.pkgwork['remove'])) + self.CondPrint(state, '') + + if ((self.states.values().count(False) > 0) and + not self.pkgwork['remove']): + self.CondPrint('All entries correct.') + def LogFailure(self, area, entry): '''Print tracebacks in unexpected cases''' print "Failure in %s for entry: %s" % (area, tostring(entry)) @@ -229,7 +297,7 @@ class Toolset(object): unlink(entry.get('name')) elif S_ISDIR(fmode): self.CondPrint('debug', "Directory entry already exists at %s" % (entry.get('name'))) - system("mv %s/ %s.bak" % (entry.get('name'), entry.get('name'))) + self.saferun("mv %s/ %s.bak" % (entry.get('name'), entry.get('name'))) else: unlink(entry.get('name')) except OSError: @@ -386,7 +454,7 @@ class Toolset(object): chown(newfile.name, 0, 0) chmod(newfile.name, calc_perms(S_IFREG, entry.get('perms'))) if entry.get("paranoid", False) and self.setup.get("paranoid", False): - system("cp %s /var/cache/bcfg2/%s" % (entry.get('name'))) + self.saferun("cp %s /var/cache/bcfg2/%s" % (entry.get('name'))) rename(newfile.name, entry.get('name')) return True except (OSError, IOError), err: @@ -413,9 +481,10 @@ class Toolset(object): perms = oct(sinfo[ST_MODE])[-4:] if perms == entry.get('perms'): return True - else: - self.CondPrint('debug', "Entry %s permissions incorrect" % entry.get('name')) + self.CondPrint('debug', "Entry %s permissions incorrect" % entry.get('name')) + return False + def InstallPermissions(self, entry): '''Install method for abstract permission''' try: @@ -455,15 +524,15 @@ class Toolset(object): self.CondPrint('debug', "Re-checked entry %s %s: %s" % (child.tag, child.get('name'), self.states[child])) for postinst in [entry for entry in bchildren if entry.tag == 'PostInstall']: - system(postinst.get('name')) + self.saferun(postinst.get('name')) for svc in [svc for svc in bchildren if svc.tag == 'Service' and svc.get('status', 'off') == 'on']: if self.setup['build']: # stop services in miniroot - system('/etc/init.d/%s stop' % svc.get('name')) + self.saferun('/etc/init.d/%s stop' % svc.get('name')) else: self.CondPrint('debug', 'Restarting service %s' % svc.get('name')) - system('/etc/init.d/%s %s' % (svc.get('name'), svc.get('reload', 'reload'))) + self.saferun('/etc/init.d/%s %s' % (svc.get('name'), svc.get('reload', 'reload'))) for entry in self.structures: if [strent for strent in entry.getchildren() if not self.states.get(strent, False)]: @@ -477,18 +546,27 @@ class Toolset(object): def Install(self): '''Correct detected misconfigurations''' - self.CondPrint("verbose", "Installing needed configuration changes") + if self.setup['dryrun']: + self.CondPrint("verbose", "Dry-run mode: no changes will be made") + else: + self.CondPrint("verbose", "Updating the system") + self.CondPrint("verbose", "") self.HandleExtra() # use quick package ops from here on self.setup['quick'] = True - self.CondPrint('dryrun', "Packages to update: %s" % - (" ".join([pkg.get('name') for pkg in self.pkgwork['update']]))) - self.CondPrint('dryrun', "Packages to add: %s" % - (" ".join([pkg.get('name') for pkg in self.pkgwork['add']]))) - self.CondPrint('dryrun', "Packages to remove %s" % (" ".join(self.pkgwork['remove']))) - for entry in [entry for entry in self.states if (not self.states[entry] - and (entry.tag != 'Package'))]: - self.CondPrint('dryrun', "Entry %s %s updated" % (entry.tag, entry.get('name'))) + + self.CondDisplayList('dryrun', "Packages to update:", + [pkg.get('name') for pkg in self.pkgwork['update']]) + self.CondDisplayList('dryrun', "Packages to add:", + [pkg.get('name') for pkg in self.pkgwork['add']]) + self.CondDisplayList('dryrun', "Packages to remove:", + self.pkgwork['remove']) + self.CondDisplayList('dryrun', "Entries to update:", + ["%s: %s" % (entry.tag, entry.get('name')) + for entry in self.states if not (self.states[entry] + or entry.tag == 'Package')]) + self.CondDisplayList('dryrun', "Services to remove:", self.extra_services) + if self.setup['dryrun']: return @@ -518,7 +596,7 @@ class Toolset(object): count = count + 1 old = left - self.CondPrint("verbose", "Installing Non Package entries") + self.CondPrint("verbose", "Installing non-package entries") [self.InstallEntry(ent) for ent in work if ent.tag != 'Package'] packages = [pkg for pkg in work if pkg.tag == 'Package'] @@ -545,7 +623,7 @@ class Toolset(object): self.CondPrint("debug", "Installing packages: :%s:" % pkgargs) self.CondPrint("debug", "Running command ::%s::" % (pkgtool[0] % pkgargs)) - cmdrc = system(pkgtool[0] % pkgargs) + (cmdrc, cmdoutput) = self.saferun(pkgtool[0] % pkgargs) if cmdrc == 0: self.CondPrint('verbose', "Single Pass Succeded") @@ -569,8 +647,9 @@ class Toolset(object): else: self.CondPrint("verbose", "Installing pkg %s version %s" % (pkg.get('name'), pkg.get('version'))) - cmdrc = system(pkgtool[0] % - (pkgtool[1][0]%tuple([pkg.get(field) for field in pkgtool[1][1]]))) + (cmdrc, cmdoutput) = self.saferun(pkgtool[0] % + (pkgtool[1][0] % + tuple([pkg.get(field) for field in pkgtool[1][1]]))) if cmdrc == 0: self.states[pkg] = True else: -- cgit v1.2.3-1-g7c22