summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Client/Tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Client/Tools')
-rw-r--r--src/lib/Bcfg2/Client/Tools/FreeBSDInit.py140
-rw-r--r--src/lib/Bcfg2/Client/Tools/Pkgng.py226
2 files changed, 354 insertions, 12 deletions
diff --git a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
index 2ab64f86d..24bc4cf36 100644
--- a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
+++ b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
@@ -1,27 +1,143 @@
"""FreeBSD Init Support for Bcfg2."""
-__revision__ = '$Rev$'
-
-# TODO
-# - hardcoded path to ports rc.d
-# - doesn't know about /etc/rc.d/
import os
+import re
+import Bcfg2.Options
import Bcfg2.Client.Tools
class FreeBSDInit(Bcfg2.Client.Tools.SvcTool):
"""FreeBSD service support for Bcfg2."""
name = 'FreeBSDInit'
+ __execs__ = ['/usr/sbin/service', '/usr/sbin/sysrc']
__handles__ = [('Service', 'freebsd')]
__req__ = {'Service': ['name', 'status']}
+ rcvar_re = re.compile(r'^(?P<var>[a-z_]+_enable)="[A-Z]+"$')
- def __init__(self, config):
- Bcfg2.Client.Tools.SvcTool.__init__(self, config)
- if os.uname()[0] != 'FreeBSD':
- raise Bcfg2.Client.Tools.ToolInstantiationError
+ def get_svc_command(self, service, action):
+ return '/usr/sbin/service %s %s' % (service.get('name'), action)
- def VerifyService(self, entry, _):
+ def verify_bootstatus(self, entry, bootstatus):
+ """Verify bootstatus for entry."""
+ cmd = self.get_svc_command(entry, 'enabled')
+ current_bootstatus = bool(self.cmd.run(cmd))
+
+ if bootstatus == 'off':
+ if current_bootstatus:
+ entry.set('current_bootstatus', 'on')
+ return False
+ return True
+ elif not current_bootstatus:
+ entry.set('current_bootstatus', 'off')
+ return False
return True
- def get_svc_command(self, service, action):
- return "/usr/local/etc/rc.d/%s %s" % (service.get('name'), action)
+ def check_service(self, entry):
+ # use 'onestatus' to enable status reporting for disabled services
+ cmd = self.get_svc_command(entry, 'onestatus')
+ return bool(self.cmd.run(cmd))
+
+ def stop_service(self, service):
+ # use 'onestop' to enable stopping of disabled services
+ self.logger.debug('Stopping service %s' % service.get('name'))
+ return self.cmd.run(self.get_svc_command(service, 'onestop'))
+
+
+ def VerifyService(self, entry, _):
+ """Verify Service status for entry."""
+ entry.set('target_status', entry.get('status')) # for reporting
+ bootstatus = self.get_bootstatus(entry)
+ if bootstatus is None:
+ return True
+ current_bootstatus = self.verify_bootstatus(entry, bootstatus)
+
+ if entry.get('status') == 'ignore':
+ # 'ignore' should verify
+ current_svcstatus = True
+ svcstatus = True
+ else:
+ svcstatus = self.check_service(entry)
+ if entry.get('status') == 'on':
+ if svcstatus:
+ current_svcstatus = True
+ else:
+ current_svcstatus = False
+ elif entry.get('status') == 'off':
+ if svcstatus:
+ current_svcstatus = False
+ else:
+ current_svcstatus = True
+
+ if svcstatus:
+ entry.set('current_status', 'on')
+ else:
+ entry.set('current_status', 'off')
+
+ return current_bootstatus and current_svcstatus
+
+ def InstallService(self, entry):
+ """Install Service entry."""
+ self.logger.info("Installing Service %s" % (entry.get('name')))
+ bootstatus = self.get_bootstatus(entry)
+
+ # check if service exists
+ all_services_cmd = '/usr/sbin/service -l'
+ all_services = self.cmd.run(all_services_cmd).stdout.splitlines()
+ if entry.get('name') not in all_services:
+ self.logger.debug("Service %s does not exist" % entry.get('name'))
+ return False
+
+ # get rcvar for service
+ vars = set()
+ rcvar_cmd = self.get_svc_command(entry, 'rcvar')
+ for line in self.cmd.run(rcvar_cmd).stdout.splitlines():
+ match = self.rcvar_re.match(line)
+ if match:
+ vars.add(match.group('var'))
+
+ if bootstatus is not None:
+ bootcmdrv = True
+ sysrcstatus = None
+ if bootstatus == 'on':
+ sysrcstatus = 'YES'
+ elif bootstatus == 'off':
+ sysrcstatus = 'NO'
+ if sysrcstatus is not None:
+ for var in vars:
+ if not self.cmd.run('/usr/sbin/sysrc %s="%s"' % (var, sysrcstatus)):
+ bootcmdrv = False
+ break
+
+ if Bcfg2.Options.setup.service_mode == 'disabled':
+ # 'disabled' means we don't attempt to modify running svcs
+ return bootcmdrv
+ buildmode = Bcfg2.Options.setup.service_mode == 'build'
+ if (entry.get('status') == 'on' and not buildmode) and \
+ entry.get('current_status') == 'off':
+ svccmdrv = self.start_service(entry)
+ elif (entry.get('status') == 'off' or buildmode) and \
+ entry.get('current_status') == 'on':
+ svccmdrv = self.stop_service(entry)
+ else:
+ svccmdrv = True # ignore status attribute
+ return bootcmdrv and svccmdrv
+ else:
+ # when bootstatus is 'None', status == 'ignore'
+ return True
+
+ def FindExtra(self):
+ """Find Extra FreeBSD Service entries."""
+ specified = [entry.get('name') for entry in self.getSupportedEntries()]
+ extra = set()
+ for path in self.cmd.run("/usr/sbin/service -e").stdout.splitlines():
+ name = os.path.basename(path)
+ if name not in specified:
+ extra.add(name)
+ return [Bcfg2.Client.XML.Element('Service', name=name, type='freebsd')
+ for name in list(extra)]
+
+ def Remove(self, _):
+ """Remove extra service entries."""
+ # Extra service removal is nonsensical
+ # Extra services need to be reflected in the config
+ return
diff --git a/src/lib/Bcfg2/Client/Tools/Pkgng.py b/src/lib/Bcfg2/Client/Tools/Pkgng.py
new file mode 100644
index 000000000..cd70d662d
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/Pkgng.py
@@ -0,0 +1,226 @@
+"""This is the Bcfg2 support for pkg."""
+
+import os
+import Bcfg2.Options
+import Bcfg2.Client.Tools
+
+
+class Pkgng(Bcfg2.Client.Tools.Tool):
+ """Support for pkgng packages on FreeBSD."""
+
+ options = Bcfg2.Client.Tools.Tool.options + [
+ Bcfg2.Options.PathOption(
+ cf=('Pkgng', 'path'),
+ default='/usr/sbin/pkg', dest='pkg_path',
+ help='Pkgng tool path')]
+
+ name = 'Pkgng'
+ __execs__ = []
+ __handles__ = [('Package', 'pkgng'), ('Path', 'ignore')]
+ __req__ = {'Package': ['name', 'version'], 'Path': ['type']}
+
+ def __init__(self, config):
+ Bcfg2.Client.Tools.Tool.__init__(self, config)
+
+ self.pkg = Bcfg2.Options.setup.pkg_path
+ self.__execs__ = [self.pkg]
+
+ self.pkgcmd = self.pkg + ' install -fy'
+ if not Bcfg2.Options.setup.debug:
+ self.pkgcmd += ' -q'
+ self.pkgcmd += ' %s'
+
+ self.ignores = [entry.get('name') for struct in config
+ for entry in struct
+ if entry.tag == 'Path' and
+ entry.get('type') == 'ignore']
+
+ self.__important__ = self.__important__ + \
+ [entry.get('name') for struct in config
+ for entry in struct
+ if (entry.tag == 'Path' and
+ entry.get('name').startswith('/etc/pkg/'))]
+ self.nonexistent = [entry.get('name') for struct in config
+ for entry in struct if entry.tag == 'Path'
+ and entry.get('type') == 'nonexistent']
+ self.actions = {}
+ self.pkg_cache = {}
+
+ try:
+ self._load_pkg_cache()
+ except OSError:
+ raise Bcfg2.Client.Tools.ToolInstantiationError
+
+ def _load_pkg_cache(self):
+ """Cache the version of all currently installed packages."""
+ self.pkg_cache = {}
+ output = self.cmd.run([self.pkg, 'query', '-a', '%n %v']).stdout
+ for line in output.splitlines():
+ parts = line.split(' ')
+ name = ' '.join(parts[:-1])
+ self.pkg_cache[name] = parts[-1]
+
+ def FindExtra(self):
+ """Find extra packages."""
+ packages = [entry.get('name') for entry in self.getSupportedEntries()]
+ extras = [(name, value) for (name, value) in self.pkg_cache.items()
+ if name not in packages]
+ return [Bcfg2.Client.XML.Element('Package', name=name,
+ type='pkgng', version=version)
+ for (name, version) in extras]
+
+ def VerifyChecksums(self, entry, modlist):
+ """Verify the checksum of the files, owned by a package."""
+ output = self.cmd.run([self.pkg, 'check', '-s',
+ entry.get('name')]).stdout.splitlines()
+ files = []
+ for item in output:
+ if "checksum mismatch" in item:
+ files.append(item.split()[-1])
+ elif "No such file or directory" in item:
+ continue
+ else:
+ self.logger.error("Got Unsupported pattern %s "
+ "from pkg check" % item)
+
+ files = list(set(files) - set(self.ignores))
+ # We check if there is file in the checksum to do
+ if files:
+ # if files are found there we try to be sure our modlist is sane
+ # with erroneous symlinks
+ modlist = [os.path.realpath(filename) for filename in modlist]
+ bad = [filename for filename in files if filename not in modlist]
+ if bad:
+ self.logger.debug("It is suggested that you either manage "
+ "these files, revert the changes, or ignore "
+ "false failures:")
+ self.logger.info("Package %s failed validation. Bad files "
+ "are:" % entry.get('name'))
+ self.logger.info(bad)
+ entry.set('qtext',
+ "Reinstall Package %s-%s to fix failing files? "
+ "(y/N) " % (entry.get('name'), entry.get('version')))
+ return False
+ return True
+
+ def _get_candidate_versions(self, name):
+ """
+ Get versions of the specified package name available for
+ installation from the configured remote repositories.
+ """
+ output = self.cmd.run([self.pkg, 'search', '-Qversion', '-q',
+ '-Sname', '-e', name]).stdout.splitlines()
+ versions = []
+ for line in output:
+ versions.append(line)
+
+ if len(versions) == 0:
+ return None
+
+ return sorted(versions)
+
+ def VerifyPackage(self, entry, modlist, checksums=True):
+ """Verify package for entry."""
+ if 'version' not in entry.attrib:
+ self.logger.info("Cannot verify unversioned package %s" %
+ (entry.attrib['name']))
+ return False
+
+ pkgname = entry.get('name')
+ if pkgname not in self.pkg_cache:
+ self.logger.info("Package %s not installed" % (entry.get('name')))
+ entry.set('current_exists', 'false')
+ return False
+
+ installed_version = self.pkg_cache[pkgname]
+ candidate_versions = self._get_candidate_versions(pkgname)
+ if candidate_versions is not None:
+ candidate_version = candidate_versions[0]
+ else:
+ self.logger.error("Package %s is installed but no candidate"
+ "version was found." % (entry.get('name')))
+ return False
+
+ if entry.get('version').startswith('auto'):
+ desired_version = candidate_version
+ entry.set('version', "auto: %s" % desired_version)
+ elif entry.get('version').startswith('any'):
+ desired_version = installed_version
+ entry.set('version', "any: %s" % desired_version)
+ else:
+ desired_version = entry.get('version')
+
+ if desired_version != installed_version:
+ entry.set('current_version', installed_version)
+ entry.set('qtext', "Modify Package %s (%s -> %s)? (y/N) " %
+ (entry.get('name'), entry.get('current_version'),
+ desired_version))
+ return False
+ else:
+ # version matches
+ if (not Bcfg2.Options.setup.quick and
+ entry.get('verify', 'true') == 'true'
+ and checksums):
+ pkgsums = self.VerifyChecksums(entry, modlist)
+ return pkgsums
+ return True
+
+ def Remove(self, packages):
+ """Deal with extra configuration detected."""
+ pkgnames = " ".join([pkg.get('name') for pkg in packages])
+ if len(packages) > 0:
+ self.logger.info('Removing packages:')
+ self.logger.info(pkgnames)
+ self.cmd.run([self.pkg, 'delete', '-y', pkgnames])
+ self._load_pkg_cache()
+ self.modified += packages
+ self.extra = self.FindExtra()
+
+ def Install(self, packages):
+ ipkgs = []
+ bad_pkgs = []
+ for pkg in packages:
+ versions = self._get_candidate_versions(pkg.get('name'))
+ if versions is None:
+ self.logger.error("pkg has no information about package %s" %
+ (pkg.get('name')))
+ continue
+
+ if pkg.get('version').startswith('auto') or \
+ pkg.get('version').startswith('any'):
+ ipkgs.append("%s-%s" % (pkg.get('name'), versions[0]))
+ continue
+
+ if pkg.get('version') in versions:
+ ipkgs.append("%s-%s" % (pkg.get('name'), pkg.get('version')))
+ continue
+ else:
+ self.logger.error("Package %s: desired version %s not in %s" %
+ (pkg.get('name'), pkg.get('version'),
+ versions))
+ bad_pkgs.append(pkg.get('name'))
+
+ if bad_pkgs:
+ self.logger.error("Cannot find correct versions of packages:")
+ self.logger.error(bad_pkgs)
+ if not ipkgs:
+ return
+ if not self.cmd.run(self.pkgcmd % (" ".join(ipkgs))):
+ self.logger.error("pkg command failed")
+ self._load_pkg_cache()
+ self.extra = self.FindExtra()
+ mark = []
+ states = dict()
+ for package in packages:
+ states[package] = self.VerifyPackage(package, [], checksums=False)
+ if states[package]:
+ self.modified.append(package)
+ if package.get('origin') == 'Packages':
+ mark.append(package.get('name'))
+ if mark:
+ self.cmd.run([self.pkg, 'set', '-A1', '-y'] + mark)
+ return states
+
+ def VerifyPath(self, _entry, _):
+ """Do nothing here since we only verify Path type=ignore."""
+ return True