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