summaryrefslogtreecommitdiffstats
path: root/src/lib/Client/Tools
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2006-10-06 21:06:16 +0000
committerNarayan Desai <desai@mcs.anl.gov>2006-10-06 21:06:16 +0000
commit7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd (patch)
tree92510fa0a9bb4de4c589b8a2f406aa097e0ab3d2 /src/lib/Client/Tools
parent53a983ba616d1c2292e1f4117084f85a1db9454f (diff)
downloadbcfg2-7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd.tar.gz
bcfg2-7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd.tar.bz2
bcfg2-7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd.zip
Merge of the client/refactor branch (revisions r2218-r2400)
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@2401 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src/lib/Client/Tools')
-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
11 files changed, 1086 insertions, 0 deletions
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')))