summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Client
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Client')
-rw-r--r--src/lib/Bcfg2/Client/Client.py337
-rw-r--r--src/lib/Bcfg2/Client/Frame.py561
-rw-r--r--src/lib/Bcfg2/Client/Proxy.py105
-rw-r--r--src/lib/Bcfg2/Client/Tools/APK.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/APT.py63
-rw-r--r--src/lib/Bcfg2/Client/Tools/Action.py25
-rw-r--r--src/lib/Bcfg2/Client/Tools/Chkconfig.py13
-rw-r--r--src/lib/Bcfg2/Client/Tools/MacPorts.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/File.py10
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/__init__.py43
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py34
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIXUsers.py44
-rw-r--r--src/lib/Bcfg2/Client/Tools/Pacman.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/Portage.py15
-rw-r--r--src/lib/Bcfg2/Client/Tools/RPM.py125
-rw-r--r--src/lib/Bcfg2/Client/Tools/SELinux.py3
-rw-r--r--src/lib/Bcfg2/Client/Tools/SYSV.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUM.py89
-rw-r--r--src/lib/Bcfg2/Client/Tools/__init__.py41
-rw-r--r--src/lib/Bcfg2/Client/__init__.py873
20 files changed, 1239 insertions, 1150 deletions
diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py
deleted file mode 100644
index 994ce7c84..000000000
--- a/src/lib/Bcfg2/Client/Client.py
+++ /dev/null
@@ -1,337 +0,0 @@
-""" The main Bcfg2 client class """
-
-import os
-import sys
-import stat
-import time
-import fcntl
-import socket
-import logging
-import tempfile
-import Bcfg2.Logger
-import Bcfg2.Options
-import Bcfg2.Client.XML
-import Bcfg2.Client.Proxy
-import Bcfg2.Client.Frame
-import Bcfg2.Client.Tools
-from Bcfg2.Utils import locked, Executor
-from Bcfg2.Compat import xmlrpclib
-from Bcfg2.version import __version__
-
-
-class Client(object):
- """ The main Bcfg2 client class """
-
- def __init__(self):
- self.toolset = None
- self.tools = None
- self.config = None
- self._proxy = None
- self.setup = Bcfg2.Options.get_option_parser()
-
- if self.setup['debug']:
- level = logging.DEBUG
- elif self.setup['verbose']:
- level = logging.INFO
- else:
- level = logging.WARNING
- Bcfg2.Logger.setup_logging('bcfg2',
- to_syslog=self.setup['syslog'],
- level=level,
- to_file=self.setup['logging'])
- self.logger = logging.getLogger('bcfg2')
- self.logger.debug(self.setup)
-
- self.cmd = Executor(self.setup['command_timeout'])
-
- if self.setup['bundle_quick']:
- if not self.setup['bundle'] and not self.setup['skipbundle']:
- self.logger.error("-Q option requires -b or -B")
- raise SystemExit(1)
- elif self.setup['remove']:
- self.logger.error("-Q option incompatible with -r")
- raise SystemExit(1)
- if 'drivers' in self.setup and self.setup['drivers'] == 'help':
- self.logger.info("The following drivers are available:")
- self.logger.info(Bcfg2.Client.Tools.__all__)
- raise SystemExit(0)
- if self.setup['remove'] and 'services' in self.setup['remove'].lower():
- self.logger.error("Service removal is nonsensical; "
- "removed services will only be disabled")
- if (self.setup['remove'] and
- self.setup['remove'].lower() not in ['all', 'services', 'packages',
- 'users']):
- self.logger.error("Got unknown argument %s for -r" %
- self.setup['remove'])
- if self.setup["file"] and self.setup["cache"]:
- print("cannot use -f and -c together")
- raise SystemExit(1)
- if not self.setup['server'].startswith('https://'):
- self.setup['server'] = 'https://' + self.setup['server']
-
- def _probe_failure(self, probename, msg):
- """ handle failure of a probe in the way the user wants us to
- (exit or continue) """
- message = "Failed to execute probe %s: %s" % (probename, msg)
- if self.setup['probe_exit']:
- self.fatal_error(message)
- else:
- self.logger.error(message)
-
- def run_probe(self, probe):
- """Execute probe."""
- name = probe.get('name')
- self.logger.info("Running probe %s" % name)
- ret = Bcfg2.Client.XML.Element("probe-data",
- name=name,
- source=probe.get('source'))
- try:
- scripthandle, scriptname = tempfile.mkstemp()
- script = os.fdopen(scripthandle, 'w')
- try:
- script.write("#!%s\n" %
- (probe.attrib.get('interpreter', '/bin/sh')))
- if sys.hexversion >= 0x03000000:
- script.write(probe.text)
- else:
- script.write(probe.text.encode('utf-8'))
- script.close()
- os.chmod(scriptname,
- stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
- stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
- stat.S_IWUSR) # 0755
- rv = self.cmd.run(scriptname, timeout=self.setup['timeout'])
- if rv.stderr:
- self.logger.warning("Probe %s has error output: %s" %
- (name, rv.stderr))
- if not rv.success:
- self._probe_failure(name, "Return value %s" % rv)
- self.logger.info("Probe %s has result:" % name)
- self.logger.info(rv.stdout)
- if sys.hexversion >= 0x03000000:
- ret.text = rv.stdout
- else:
- ret.text = rv.stdout.decode('utf-8')
- finally:
- os.unlink(scriptname)
- except SystemExit:
- raise
- except:
- self._probe_failure(name, sys.exc_info()[1])
- return ret
-
- def fatal_error(self, message):
- """Signal a fatal error."""
- self.logger.error("Fatal error: %s" % (message))
- raise SystemExit(1)
-
- @property
- def proxy(self):
- """ get an XML-RPC proxy to the server """
- if self._proxy is None:
- self._proxy = Bcfg2.Client.Proxy.ComponentProxy(
- self.setup['server'],
- self.setup['user'],
- self.setup['password'],
- key=self.setup['key'],
- cert=self.setup['certificate'],
- ca=self.setup['ca'],
- allowedServerCNs=self.setup['serverCN'],
- timeout=self.setup['timeout'],
- retries=int(self.setup['retries']),
- delay=int(self.setup['retry_delay']))
- return self._proxy
-
- def run_probes(self, times=None):
- """ run probes and upload probe data """
- if times is None:
- times = dict()
-
- try:
- probes = Bcfg2.Client.XML.XML(str(self.proxy.GetProbes()))
- except (Bcfg2.Client.Proxy.ProxyError,
- Bcfg2.Client.Proxy.CertificateError,
- socket.gaierror,
- socket.error):
- err = sys.exc_info()[1]
- self.fatal_error("Failed to download probes from bcfg2: %s" % err)
- except Bcfg2.Client.XML.ParseError:
- err = sys.exc_info()[1]
- self.fatal_error("Server returned invalid probe requests: %s" %
- err)
-
- times['probe_download'] = time.time()
-
- # execute probes
- probedata = Bcfg2.Client.XML.Element("ProbeData")
- for probe in probes.findall(".//probe"):
- probedata.append(self.run_probe(probe))
-
- if len(probes.findall(".//probe")) > 0:
- try:
- # upload probe responses
- self.proxy.RecvProbeData(
- Bcfg2.Client.XML.tostring(
- probedata,
- xml_declaration=False).decode('utf-8'))
- except Bcfg2.Client.Proxy.ProxyError:
- err = sys.exc_info()[1]
- self.fatal_error("Failed to upload probe data: %s" % err)
-
- times['probe_upload'] = time.time()
-
- def get_config(self, times=None):
- """ load the configuration, either from the cached
- configuration file (-f), or from the server """
- if times is None:
- times = dict()
-
- if self.setup['file']:
- # read config from file
- try:
- self.logger.debug("Reading cached configuration from %s" %
- self.setup['file'])
- return open(self.setup['file'], 'r').read()
- except IOError:
- self.fatal_error("Failed to read cached configuration from: %s"
- % (self.setup['file']))
- else:
- # retrieve config from server
- if self.setup['profile']:
- try:
- self.proxy.AssertProfile(self.setup['profile'])
- except Bcfg2.Client.Proxy.ProxyError:
- err = sys.exc_info()[1]
- self.fatal_error("Failed to set client profile: %s" % err)
-
- try:
- self.proxy.DeclareVersion(__version__)
- except xmlrpclib.Fault:
- err = sys.exc_info()[1]
- if (err.faultCode == xmlrpclib.METHOD_NOT_FOUND or
- (err.faultCode == 7 and
- err.faultString.startswith("Unknown method"))):
- self.logger.debug("Server does not support declaring "
- "client version")
- else:
- self.logger.error("Failed to declare version: %s" % err)
- except (Bcfg2.Client.Proxy.ProxyError,
- Bcfg2.Client.Proxy.CertificateError,
- socket.gaierror,
- socket.error):
- err = sys.exc_info()[1]
- self.logger.error("Failed to declare version: %s" % err)
-
- self.run_probes(times=times)
-
- if self.setup['decision'] in ['whitelist', 'blacklist']:
- try:
- self.setup['decision_list'] = \
- self.proxy.GetDecisionList(self.setup['decision'])
- self.logger.info("Got decision list from server:")
- self.logger.info(self.setup['decision_list'])
- except Bcfg2.Client.Proxy.ProxyError:
- err = sys.exc_info()[1]
- self.fatal_error("Failed to get decision list: %s" % err)
-
- try:
- rawconfig = self.proxy.GetConfig().encode('utf-8')
- except Bcfg2.Client.Proxy.ProxyError:
- err = sys.exc_info()[1]
- self.fatal_error("Failed to download configuration from "
- "Bcfg2: %s" % err)
-
- times['config_download'] = time.time()
- return rawconfig
-
- def run(self):
- """Perform client execution phase."""
- times = {}
-
- # begin configuration
- times['start'] = time.time()
-
- self.logger.info("Starting Bcfg2 client run at %s" % times['start'])
-
- rawconfig = self.get_config(times=times).decode('utf-8')
-
- if self.setup['cache']:
- try:
- open(self.setup['cache'], 'w').write(rawconfig)
- os.chmod(self.setup['cache'], 33152)
- except IOError:
- self.logger.warning("Failed to write config cache file %s" %
- (self.setup['cache']))
- times['caching'] = time.time()
-
- try:
- self.config = Bcfg2.Client.XML.XML(rawconfig)
- except Bcfg2.Client.XML.ParseError:
- syntax_error = sys.exc_info()[1]
- self.fatal_error("The configuration could not be parsed: %s" %
- syntax_error)
-
- times['config_parse'] = time.time()
-
- if self.config.tag == 'error':
- self.fatal_error("Server error: %s" % (self.config.text))
- return(1)
-
- if self.setup['bundle_quick']:
- newconfig = Bcfg2.Client.XML.XML('<Configuration/>')
- for bundle in self.config.getchildren():
- if (bundle.tag == 'Bundle' and
- ((self.setup['bundle'] and
- bundle.get('name') in self.setup['bundle']) or
- (self.setup['skipbundle'] and
- bundle.get('name') not in self.setup['skipbundle']))):
- newconfig.append(bundle)
- self.config = newconfig
-
- self.tools = Bcfg2.Client.Frame.Frame(self.config, times)
-
- if not self.setup['omit_lock_check']:
- #check lock here
- try:
- lockfile = open(self.setup['lockfile'], 'w')
- if locked(lockfile.fileno()):
- self.fatal_error("Another instance of Bcfg2 is running. "
- "If you want to bypass the check, run "
- "with the %s option" %
- Bcfg2.Options.OMIT_LOCK_CHECK.cmd)
- except SystemExit:
- raise
- except:
- lockfile = None
- self.logger.error("Failed to open lockfile %s: %s" %
- (self.setup['lockfile'], sys.exc_info()[1]))
-
- # execute the configuration
- self.tools.Execute()
-
- if not self.setup['omit_lock_check']:
- # unlock here
- if lockfile:
- try:
- fcntl.lockf(lockfile.fileno(), fcntl.LOCK_UN)
- os.remove(self.setup['lockfile'])
- except OSError:
- self.logger.error("Failed to unlock lockfile %s" %
- lockfile.name)
-
- if not self.setup['file'] and not self.setup['bundle_quick']:
- # upload statistics
- feedback = self.tools.GenerateStats()
-
- try:
- self.proxy.RecvStats(
- Bcfg2.Client.XML.tostring(
- feedback,
- xml_declaration=False).decode('utf-8'))
- except Bcfg2.Client.Proxy.ProxyError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to upload configuration statistics: "
- "%s" % err)
- raise SystemExit(2)
-
- self.logger.info("Finished Bcfg2 client run at %s" % time.time())
diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py
deleted file mode 100644
index 4fece79b8..000000000
--- a/src/lib/Bcfg2/Client/Frame.py
+++ /dev/null
@@ -1,561 +0,0 @@
-""" Frame is the Client Framework that verifies and installs entries,
-and generates statistics. """
-
-import copy
-import time
-import fnmatch
-import logging
-import Bcfg2.Client.Tools
-from Bcfg2.Client import prompt
-from Bcfg2.Options import get_option_parser
-from Bcfg2.Compat import any, all, cmp # pylint: disable=W0622
-
-
-def cmpent(ent1, ent2):
- """Sort entries."""
- if ent1.tag != ent2.tag:
- return cmp(ent1.tag, ent2.tag)
- else:
- return cmp(ent1.get('name'), ent2.get('name'))
-
-
-def matches_entry(entryspec, entry):
- """ Determine if the Decisions-style entry specification matches
- the entry. Both are tuples of (tag, name). The entryspec can
- handle the wildcard * in either position. """
- if entryspec == entry:
- return True
- return all(fnmatch.fnmatch(entry[i], entryspec[i]) for i in [0, 1])
-
-
-def matches_white_list(entry, whitelist):
- """ Return True if (<entry tag>, <entry name>) is in the given
- whitelist. """
- return any(matches_entry(we, (entry.tag, entry.get('name')))
- for we in whitelist)
-
-
-def passes_black_list(entry, blacklist):
- """ Return True if (<entry tag>, <entry name>) is not in the given
- blacklist. """
- return not any(matches_entry(be, (entry.tag, entry.get('name')))
- for be in blacklist)
-
-
-# pylint: disable=W0702
-# in frame we frequently want to catch all exceptions, regardless of
-# type, so disable the pylint rule that catches that.
-
-
-class Frame(object):
- """Frame is the container for all Tool objects and state information."""
-
- def __init__(self, config, times):
- self.setup = get_option_parser()
- self.config = config
- self.times = times
- self.dryrun = self.setup['dryrun']
- self.times['initialization'] = time.time()
- self.tools = []
-
- #: A dict of the state of each entry. Keys are the entries.
- #: Values are boolean: True means that the entry is good,
- #: False means that the entry is bad.
- self.states = {}
- self.whitelist = []
- self.blacklist = []
- self.removal = []
- self.logger = logging.getLogger(__name__)
- drivers = self.setup['drivers']
- for driver in drivers[:]:
- if (driver not in Bcfg2.Client.Tools.__all__ and
- isinstance(driver, str)):
- self.logger.error("Tool driver %s is not available" % driver)
- drivers.remove(driver)
-
- tclass = {}
- for tool in drivers:
- if not isinstance(tool, str):
- tclass[time.time()] = tool
- tool_class = "Bcfg2.Client.Tools.%s" % tool
- try:
- tclass[tool] = getattr(__import__(tool_class, globals(),
- locals(), ['*']),
- tool)
- except ImportError:
- continue
- except:
- self.logger.error("Tool %s unexpectedly failed to load" % tool,
- exc_info=1)
-
- for tool in list(tclass.values()):
- try:
- self.tools.append(tool(config))
- except Bcfg2.Client.Tools.ToolInstantiationError:
- continue
- except:
- self.logger.error("Failed to instantiate tool %s" % tool,
- exc_info=1)
-
- for tool in self.tools[:]:
- for conflict in getattr(tool, 'conflicts', []):
- for item in self.tools:
- if item.name == conflict:
- self.tools.remove(item)
-
- self.logger.info("Loaded tool drivers:")
- self.logger.info([tool.name for tool in self.tools])
-
- deprecated = [tool.name for tool in self.tools if tool.deprecated]
- if deprecated:
- self.logger.warning("Loaded deprecated tool drivers:")
- self.logger.warning(deprecated)
- experimental = [tool.name for tool in self.tools if tool.experimental]
- if experimental:
- self.logger.info("Loaded experimental tool drivers:")
- self.logger.info(experimental)
-
- # find entries not handled by any tools
- self.unhandled = [entry for struct in config
- for entry in struct
- if entry not in self.handled]
-
- if self.unhandled:
- self.logger.error("The following entries are not handled by any "
- "tool:")
- for entry in self.unhandled:
- self.logger.error("%s:%s:%s" % (entry.tag, entry.get('type'),
- entry.get('name')))
-
- self.find_dups(config)
-
- pkgs = [(entry.get('name'), entry.get('origin'))
- for struct in config
- for entry in struct
- if entry.tag == 'Package']
- if pkgs:
- self.logger.debug("The following packages are specified in bcfg2:")
- self.logger.debug([pkg[0] for pkg in pkgs if pkg[1] is None])
- self.logger.debug("The following packages are prereqs added by "
- "Packages:")
- self.logger.debug([pkg[0] for pkg in pkgs if pkg[1] == 'Packages'])
-
- def find_dups(self, config):
- """ Find duplicate entries and warn about them """
- entries = dict()
- for struct in config:
- for entry in struct:
- for tool in self.tools:
- if tool.handlesEntry(entry):
- pkey = tool.primarykey(entry)
- if pkey in entries:
- entries[pkey] += 1
- else:
- entries[pkey] = 1
- multi = [e for e, c in entries.items() if c > 1]
- if multi:
- self.logger.debug("The following entries are included multiple "
- "times:")
- for entry in multi:
- self.logger.debug(entry)
-
- def promptFilter(self, msg, entries):
- """Filter a supplied list based on user input."""
- ret = []
- entries.sort(key=lambda e: e.tag + ":" + e.get('name'))
- for entry in entries[:]:
- if entry in self.unhandled:
- # don't prompt for entries that can't be installed
- continue
- if 'qtext' in entry.attrib:
- iprompt = entry.get('qtext')
- else:
- iprompt = msg % (entry.tag, entry.get('name'))
- if prompt(iprompt):
- ret.append(entry)
- return ret
-
- def __getattr__(self, name):
- if name in ['extra', 'handled', 'modified', '__important__']:
- ret = []
- for tool in self.tools:
- ret += getattr(tool, name)
- return ret
- elif name in self.__dict__:
- return self.__dict__[name]
- raise AttributeError(name)
-
- def InstallImportant(self):
- """Install important entries
-
- We also process the decision mode stuff here because we want to prevent
- non-whitelisted/blacklisted 'important' entries from being installed
- prior to determining the decision mode on the client.
- """
- # Need to process decision stuff early so that dryrun mode
- # works with it
- self.whitelist = [entry for entry in self.states
- if not self.states[entry]]
- if not self.setup['file']:
- if self.setup['decision'] == 'whitelist':
- dwl = self.setup['decision_list']
- w_to_rem = [e for e in self.whitelist
- if not matches_white_list(e, dwl)]
- if w_to_rem:
- self.logger.info("In whitelist mode: "
- "suppressing installation of:")
- self.logger.info(["%s:%s" % (e.tag, e.get('name'))
- for e in w_to_rem])
- self.whitelist = [x for x in self.whitelist
- if x not in w_to_rem]
- elif self.setup['decision'] == 'blacklist':
- b_to_rem = \
- [e for e in self.whitelist
- if not passes_black_list(e, self.setup['decision_list'])]
- if b_to_rem:
- self.logger.info("In blacklist mode: "
- "suppressing installation of:")
- self.logger.info(["%s:%s" % (e.tag, e.get('name'))
- for e in b_to_rem])
- self.whitelist = [x for x in self.whitelist
- if x not in b_to_rem]
-
- # take care of important entries first
- if not self.dryrun:
- parent_map = dict((c, p)
- for p in self.config.getiterator()
- for c in p)
- for cfile in self.config.findall(".//Path"):
- if (cfile.get('name') not in self.__important__ or
- cfile.get('type') != 'file' or
- cfile not in self.whitelist):
- continue
- parent = parent_map[cfile]
- if ((parent.tag == "Bundle" and
- ((self.setup['bundle'] and
- parent.get("name") not in self.setup['bundle']) or
- (self.setup['skipbundle'] and
- parent.get("name") in self.setup['skipbundle']))) or
- (parent.tag == "Independent" and
- (self.setup['bundle'] or self.setup['skipindep']))):
- continue
- tools = [t for t in self.tools
- if t.handlesEntry(cfile) and t.canVerify(cfile)]
- if tools:
- if (self.setup['interactive'] and not
- self.promptFilter("Install %s: %s? (y/N):", [cfile])):
- self.whitelist.remove(cfile)
- continue
- try:
- self.states[cfile] = tools[0].InstallPath(cfile)
- if self.states[cfile]:
- tools[0].modified.append(cfile)
- except:
- self.logger.error("Unexpected tool failure",
- exc_info=1)
- cfile.set('qtext', '')
- if tools[0].VerifyPath(cfile, []):
- self.whitelist.remove(cfile)
-
- 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:
- self.states.update(tool.Inventory())
- except:
- self.logger.error("%s.Inventory() call failed:" % tool.name,
- exc_info=1)
-
- def Decide(self): # pylint: disable=R0912
- """Set self.whitelist based on user interaction."""
- iprompt = "Install %s: %s? (y/N): "
- rprompt = "Remove %s: %s? (y/N): "
- if self.setup['remove']:
- if self.setup['remove'] == 'all':
- self.removal = self.extra
- elif self.setup['remove'].lower() == 'services':
- self.removal = [entry for entry in self.extra
- if entry.tag == 'Service']
- elif self.setup['remove'].lower() == 'packages':
- self.removal = [entry for entry in self.extra
- if entry.tag == 'Package']
- elif self.setup['remove'].lower() == 'users':
- self.removal = [entry for entry in self.extra
- if entry.tag in ['POSIXUser', 'POSIXGroup']]
-
- candidates = [entry for entry in self.states
- if not self.states[entry]]
-
- if self.dryrun:
- if self.whitelist:
- self.logger.info("In dryrun mode: "
- "suppressing entry installation for:")
- self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
- for entry in self.whitelist])
- self.whitelist = []
- if self.removal:
- self.logger.info("In dryrun mode: "
- "suppressing entry removal for:")
- self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
- for entry in self.removal])
- self.removal = []
-
- # Here is where most of the work goes
- # first perform bundle filtering
- all_bundle_names = [b.get('name')
- for b in self.config.findall('./Bundle')]
- bundles = self.config.getchildren()
- if self.setup['bundle']:
- # warn if non-existent bundle given
- for bundle in self.setup['bundle']:
- if bundle not in all_bundle_names:
- self.logger.info("Warning: Bundle %s not found" % bundle)
- bundles = [b for b in bundles
- if b.get('name') in self.setup['bundle']]
- elif self.setup['indep']:
- bundles = [b for b in bundles if b.tag != 'Bundle']
- if self.setup['skipbundle']:
- # warn if non-existent bundle given
- if not self.setup['bundle_quick']:
- for bundle in self.setup['skipbundle']:
- if bundle not in all_bundle_names:
- self.logger.info("Warning: Bundle %s not found" %
- bundle)
- bundles = [b for b in bundles
- if b.get('name') not in self.setup['skipbundle']]
- if self.setup['skipindep']:
- bundles = [b for b in bundles if b.tag == 'Bundle']
-
- self.whitelist = [e for e in self.whitelist
- if any(e in b for b in bundles)]
-
- # first process prereq actions
- for bundle in bundles[:]:
- if bundle.tag == 'Bundle':
- bmodified = any(item in self.whitelist for item in bundle)
- else:
- bmodified = False
- actions = [a for a in bundle.findall('./Action')
- if (a.get('timing') in ['pre', 'both'] and
- (bmodified or a.get('when') == 'always'))]
- # now we process all "always actions"
- if self.setup['interactive']:
- self.promptFilter(iprompt, actions)
- self.DispatchInstallCalls(actions)
-
- if bundle.tag != 'Bundle':
- continue
-
- # need to test to fail entries in whitelist
- if not all(self.states[a] for a in actions):
- # then display bundles forced off with entries
- self.logger.info("%s %s failed prerequisite action" %
- (bundle.tag, bundle.get('name')))
- bundles.remove(bundle)
- b_to_remv = [ent for ent in self.whitelist if ent in bundle]
- if b_to_remv:
- self.logger.info("Not installing entries from %s %s" %
- (bundle.tag, bundle.get('name')))
- self.logger.info(["%s:%s" % (e.tag, e.get('name'))
- for e in b_to_remv])
- for ent in b_to_remv:
- self.whitelist.remove(ent)
-
- self.logger.debug("Installing entries in the following bundle(s):")
- self.logger.debug(" %s" % ", ".join(b.get("name") for b in bundles
- if b.get("name")))
-
- if self.setup['interactive']:
- self.whitelist = self.promptFilter(iprompt, self.whitelist)
- self.removal = self.promptFilter(rprompt, self.removal)
-
- for entry in candidates:
- if entry not in self.whitelist:
- self.blacklist.append(entry)
-
- 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:
- self.states.update(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)
- mods = self.modified
- mbundles = [struct for struct in self.config.findall('Bundle')
- if any(True for mod in mods if mod in struct)]
-
- if self.modified:
- # Handle Bundle interdeps
- if mbundles:
- self.logger.info("The Following Bundles have been modified:")
- self.logger.info([mbun.get('name') for mbun in mbundles])
- tbm = [(t, b) for t in self.tools for b in mbundles]
- for tool, bundle in tbm:
- try:
- self.states.update(tool.Inventory(structures=[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] and
- entry not in self.blacklist)]
- if clobbered:
- self.logger.debug("Found clobbered entries:")
- self.logger.debug(["%s:%s" % (entry.tag, entry.get('name'))
- for entry in clobbered])
- if not self.setup['interactive']:
- self.DispatchInstallCalls(clobbered)
-
- for bundle in self.config.findall('.//Bundle'):
- if (self.setup['bundle'] and
- bundle.get('name') not in self.setup['bundle']):
- # prune out unspecified bundles when running with -b
- continue
- if bundle in mbundles:
- self.logger.debug("Bundle %s was modified" %
- bundle.get('name'))
- func = "BundleUpdated"
- else:
- self.logger.debug("Bundle %s was not modified" %
- bundle.get('name'))
- func = "BundleNotUpdated"
- for tool in self.tools:
- try:
- self.states.update(getattr(tool, func)(bundle))
- except:
- self.logger.error("%s.%s(%s:%s) call failed:" %
- (tool.name, func, bundle.tag,
- bundle.get("name")), exc_info=1)
-
- for indep in self.config.findall('.//Independent'):
- for tool in self.tools:
- try:
- self.states.update(tool.BundleNotUpdated(indep))
- except:
- self.logger.error("%s.BundleNotUpdated(%s:%s) call failed:"
- % (tool.name, indep.tag,
- indep.get("name")), exc_info=1)
-
- def Remove(self):
- """Remove extra entries."""
- for tool in self.tools:
- extras = [entry for entry in self.removal
- if tool.handlesEntry(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('Phase: %s' % phase)
- self.logger.info('Correct entries: %d' %
- list(self.states.values()).count(True))
- self.logger.info('Incorrect entries: %d' %
- list(self.states.values()).count(False))
- if phase == 'final' and list(self.states.values()).count(False):
- for entry in sorted(self.states.keys(), key=lambda e: e.tag + ":" +
- e.get('name')):
- if not self.states[entry]:
- etype = entry.get('type')
- if etype:
- self.logger.info("%s:%s:%s" % (entry.tag, etype,
- entry.get('name')))
- else:
- self.logger.info("%s:%s" % (entry.tag,
- entry.get('name')))
- self.logger.info('Total managed entries: %d' %
- len(list(self.states.values())))
- self.logger.info('Unmanaged entries: %d' % len(self.extra))
- if phase == 'final' and self.setup['extra']:
- for entry in sorted(self.extra, key=lambda e: e.tag + ":" +
- e.get('name')):
- etype = entry.get('type')
- if etype:
- self.logger.info("%s:%s:%s" % (entry.tag, etype,
- entry.get('name')))
- else:
- self.logger.info("%s:%s" % (entry.tag,
- entry.get('name')))
-
- if ((list(self.states.values()).count(False) == 0) and not self.extra):
- self.logger.info('All entries correct.')
-
- def ReInventory(self):
- """Recheck everything."""
- if not self.dryrun and self.setup['kevlar']:
- self.logger.info("Rechecking system inventory")
- self.Inventory()
-
- def Execute(self):
- """Run all methods."""
- self.Inventory()
- self.times['inventory'] = time.time()
- self.CondDisplayState('initial')
- self.InstallImportant()
- self.Decide()
- self.Install()
- self.times['install'] = time.time()
- self.Remove()
- self.times['remove'] = time.time()
- if self.modified:
- self.ReInventory()
- self.times['reinventory'] = time.time()
- 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)),
- version='2.0',
- revision=self.config.get('revision', '-1'))
- good_entries = [key for key, val in list(self.states.items()) if val]
- good = len(good_entries)
- stats.set('good', str(good))
- if any(not val for val in list(self.states.values())):
- stats.set('state', 'dirty')
- else:
- stats.set('state', 'clean')
-
- # List bad elements of the configuration
- for (data, ename) in [(self.modified, 'Modified'),
- (self.extra, "Extra"),
- (good_entries, "Good"),
- ([entry for entry in self.states
- if not self.states[entry]], "Bad")]:
- container = Bcfg2.Client.XML.SubElement(stats, ename)
- for item in data:
- item.set('qtext', '')
- container.append(copy.deepcopy(item))
- item.text = None
-
- timeinfo = Bcfg2.Client.XML.Element("OpStamps")
- feedback.append(stats)
- for (event, timestamp) in list(self.times.items()):
- timeinfo.set(event, str(timestamp))
- stats.append(timeinfo)
- return feedback
diff --git a/src/lib/Bcfg2/Client/Proxy.py b/src/lib/Bcfg2/Client/Proxy.py
index fbf114de6..98d081b10 100644
--- a/src/lib/Bcfg2/Client/Proxy.py
+++ b/src/lib/Bcfg2/Client/Proxy.py
@@ -1,6 +1,10 @@
import re
+import sys
+import time
import socket
import logging
+import Bcfg2.Options
+from Bcfg2.Compat import httplib, xmlrpclib, urlparse, quote_plus
# The ssl module is provided by either Python 2.6 or a separate ssl
# package that works on older versions of Python (see
@@ -16,11 +20,6 @@ except ImportError:
SSL_LIB = 'm2crypto'
SSL_ERROR = SSL.SSLError
-import sys
-import time
-
-# Compatibility imports
-from Bcfg2.Compat import httplib, xmlrpclib, urlparse, quote_plus
version = sys.version_info[:2]
has_py26 = version >= (2, 6)
@@ -64,6 +63,7 @@ class CertificateError(Exception):
_orig_Method = xmlrpclib._Method
+
class RetryMethod(xmlrpclib._Method):
"""Method with error handling and retries built in."""
log = logging.getLogger('xmlrpc')
@@ -104,7 +104,6 @@ class RetryMethod(xmlrpclib._Method):
err = sys.exc_info()[1]
msg = err
except:
- raise
etype, err = sys.exc_info()[:2]
msg = "Unknown failure: %s (%s)" % (err, etype.__name__)
if msg:
@@ -218,12 +217,15 @@ class SSLHTTPConnection(httplib.HTTPConnection):
other_side_required = ssl.CERT_REQUIRED
else:
other_side_required = ssl.CERT_NONE
- self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.")
+ self.logger.warning("No ca is specified. Cannot authenticate the "
+ "server with SSL.")
if self.cert and not self.key:
- self.logger.warning("SSL cert specfied, but no key. Cannot authenticate this client with SSL.")
+ self.logger.warning("SSL cert specfied, but no key. Cannot "
+ "authenticate this client with SSL.")
self.cert = None
if self.key and not self.cert:
- self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.")
+ self.logger.warning("SSL key specfied, but no cert. Cannot "
+ "authenticate this client with SSL.")
self.key = None
rawsock.settimeout(self.timeout)
@@ -234,7 +236,8 @@ class SSLHTTPConnection(httplib.HTTPConnection):
self.sock.connect((self.host, self.port))
peer_cert = self.sock.getpeercert()
if peer_cert and self.scns:
- scn = [x[0][1] for x in peer_cert['subject'] if x[0][0] == 'commonName'][0]
+ scn = [x[0][1] for x in peer_cert['subject']
+ if x[0][0] == 'commonName'][0]
if scn not in self.scns:
raise CertificateError(scn)
self.sock.closeSocket = True
@@ -253,20 +256,24 @@ class SSLHTTPConnection(httplib.HTTPConnection):
if self.ca:
# Use the certificate authority to validate the cert
# presented by the server
- ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
+ depth=9)
if ctx.load_verify_locations(self.ca) != 1:
raise Exception('No CA certs')
else:
- self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.")
+ self.logger.warning("No ca is specified. Cannot authenticate the "
+ "server with SSL.")
if self.cert and self.key:
# A cert/key is defined, use them to support client
# authentication to the server
ctx.load_cert(self.cert, self.key)
elif self.cert:
- self.logger.warning("SSL cert specfied, but no key. Cannot authenticate this client with SSL.")
+ self.logger.warning("SSL cert specfied, but no key. Cannot "
+ "authenticate this client with SSL.")
elif self.key:
- self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.")
+ self.logger.warning("SSL key specfied, but no cert. Cannot "
+ "authenticate this client with SSL.")
self.sock = SSL.Connection(ctx)
if re.match('\\d+\\.\\d+\\.\\d+\\.\\d+', self.host):
@@ -343,26 +350,50 @@ class XMLRPCTransport(xmlrpclib.Transport):
# pylint: enable=E1101
-def ComponentProxy(url, user=None, password=None, key=None, cert=None, ca=None,
- allowedServerCNs=None, timeout=90, retries=3, delay=1):
-
- """Constructs proxies to components.
-
- Arguments:
- component_name -- name of the component to connect to
-
- Additional arguments are passed to the ServerProxy constructor.
-
- """
- xmlrpclib._Method.max_retries = retries
- xmlrpclib._Method.retry_delay = delay
-
- if user and password:
- method, path = urlparse(url)[:2]
- newurl = "%s://%s:%s@%s" % (method, quote_plus(user, ''),
- quote_plus(password, ''), path)
- else:
- newurl = url
- ssl_trans = XMLRPCTransport(key, cert, ca,
- allowedServerCNs, timeout=float(timeout))
- return xmlrpclib.ServerProxy(newurl, allow_none=True, transport=ssl_trans)
+class ComponentProxy(xmlrpclib.ServerProxy):
+ """Constructs proxies to components. """
+
+ options = [
+ Bcfg2.Options.Common.location, Bcfg2.Options.Common.ssl_key,
+ Bcfg2.Options.Common.ssl_cert, Bcfg2.Options.Common.ssl_ca,
+ Bcfg2.Options.Common.password,
+ Bcfg2.Options.Option(
+ "-u", "--user", default="root", cf=('communication', 'user'),
+ help='The user to provide for authentication'),
+ Bcfg2.Options.Option(
+ "-R", "--retries", type=int, default=3,
+ cf=('communication', 'retries'),
+ help='The number of times to retry network communication'),
+ Bcfg2.Options.Option(
+ "-y", "--retry-delay", type=int, default=1,
+ cf=('communication', 'retry_delay'),
+ help='The time in seconds to wait between retries'),
+ Bcfg2.Options.Option(
+ '--ssl-cns', cf=('communication', 'serverCommonNames'),
+ type=Bcfg2.Options.Types.colon_list,
+ help='List of server commonNames'),
+ Bcfg2.Options.Option(
+ "-t", "--timeout", type=float, default=90.0,
+ cf=('communication', 'timeout'),
+ help='Set the client XML-RPC timeout')]
+
+ def __init__(self):
+ RetryMethod.max_retries = Bcfg2.Options.setup.retries
+ RetryMethod.retry_delay = Bcfg2.Options.setup.retry_delay
+
+ if Bcfg2.Options.setup.user and Bcfg2.Options.setup.password:
+ method, path = urlparse(Bcfg2.Options.setup.server)[:2]
+ url = "%s://%s:%s@%s" % (
+ method,
+ quote_plus(Bcfg2.Options.setup.user, ''),
+ quote_plus(Bcfg2.Options.setup.password, ''),
+ path)
+ else:
+ url = Bcfg2.Options.setup.server
+ ssl_trans = XMLRPCTransport(Bcfg2.Options.setup.key,
+ Bcfg2.Options.setup.cert,
+ Bcfg2.Options.setup.ca,
+ Bcfg2.Options.setup.ssl_cns,
+ Bcfg2.Options.setup.timeout)
+ xmlrpclib.ServerProxy.__init__(self, url,
+ allow_none=True, transport=ssl_trans)
diff --git a/src/lib/Bcfg2/Client/Tools/APK.py b/src/lib/Bcfg2/Client/Tools/APK.py
index 46f46bb1c..457197c28 100644
--- a/src/lib/Bcfg2/Client/Tools/APK.py
+++ b/src/lib/Bcfg2/Client/Tools/APK.py
@@ -33,8 +33,6 @@ class APK(Bcfg2.Client.Tools.PkgTool):
if entry.attrib['name'] in self.installed:
if entry.attrib['version'] in \
['auto', self.installed[entry.attrib['name']]]:
- #if not self.setup['quick'] and \
- # entry.get('verify', 'true') == 'true':
#FIXME: Does APK have any sort of verification mechanism?
return True
else:
diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py
index f449557aa..5f14b43ed 100644
--- a/src/lib/Bcfg2/Client/Tools/APT.py
+++ b/src/lib/Bcfg2/Client/Tools/APT.py
@@ -4,16 +4,27 @@
import warnings
warnings.filterwarnings("ignore", "apt API not stable yet",
FutureWarning)
-import apt.cache
import os
+import apt.cache
+import Bcfg2.Options
import Bcfg2.Client.Tools
+
class APT(Bcfg2.Client.Tools.Tool):
- """The Debian toolset implements package and service operations and inherits
- the rest from Toolset.Toolset.
+ """The Debian toolset implements package and service operations
+ and inherits the rest from Tools.Tool. """
+
+ options = Bcfg2.Client.Tools.Tool.options + [
+ Bcfg2.Options.PathOption(
+ cf=('APT', 'install_path'), default='/usr', dest='apt_install_path',
+ help='Apt tools install path'),
+ Bcfg2.Options.PathOption(
+ cf=('APT', 'var_path'), default='/var', dest='apt_var_path',
+ help='Apt tools var path'),
+ Bcfg2.Options.PathOption(
+ cf=('APT', 'etc_path'), default='/etc', dest='apt_etc_path',
+ help='System etc path')]
- """
- name = 'APT'
__execs__ = []
__handles__ = [('Package', 'deb'), ('Path', 'ignore')]
__req__ = {'Package': ['name', 'version'], 'Path': ['type']}
@@ -21,12 +32,9 @@ class APT(Bcfg2.Client.Tools.Tool):
def __init__(self, config):
Bcfg2.Client.Tools.Tool.__init__(self, config)
- self.install_path = self.setup.get('apt_install_path', '/usr')
- self.var_path = self.setup.get('apt_var_path', '/var')
- self.etc_path = self.setup.get('apt_etc_path', '/etc')
- self.debsums = '%s/bin/debsums' % self.install_path
- self.aptget = '%s/bin/apt-get' % self.install_path
- self.dpkg = '%s/bin/dpkg' % self.install_path
+ self.debsums = '%s/bin/debsums' % Bcfg2.Options.setup.apt_install_path
+ self.aptget = '%s/bin/apt-get' % Bcfg2.Options.setup.apt_install_path
+ self.dpkg = '%s/bin/dpkg' % Bcfg2.Options.setup.apt_install_path
self.__execs__ = [self.debsums, self.aptget, self.dpkg]
path_entries = os.environ['PATH'].split(':')
@@ -38,7 +46,7 @@ class APT(Bcfg2.Client.Tools.Tool):
'-o DPkg::Options::=--force-confmiss ' + \
'--reinstall ' + \
'--force-yes '
- if not self.setup['debug']:
+ if not Bcfg2.Options.setup.debug:
self.pkgcmd += '-q=2 '
self.pkgcmd += '-y install %s'
self.ignores = [entry.get('name') for struct in config \
@@ -46,19 +54,23 @@ class APT(Bcfg2.Client.Tools.Tool):
if entry.tag == 'Path' and \
entry.get('type') == 'ignore']
self.__important__ = self.__important__ + \
- ["%s/cache/debconf/config.dat" % self.var_path,
- "%s/cache/debconf/templates.dat" % self.var_path,
- '/etc/passwd', '/etc/group',
- '%s/apt/apt.conf' % self.etc_path,
- '%s/dpkg/dpkg.cfg' % self.etc_path] + \
- [entry.get('name') for struct in config for entry in struct \
- if entry.tag == 'Path' and \
- entry.get('name').startswith('%s/apt/sources.list' % self.etc_path)]
- self.nonexistent = [entry.get('name') for struct in config for entry in struct \
- if entry.tag == 'Path' and entry.get('type') == 'nonexistent']
+ [
+ "%s/cache/debconf/config.dat" % Bcfg2.Options.setup.apt_var_path,
+ "%s/cache/debconf/templates.dat" % Bcfg2.Options.setup.apt_var_path,
+ '/etc/passwd', '/etc/group',
+ '%s/apt/apt.conf' % Bcfg2.Options.setup.apt_etc_path,
+ '%s/dpkg/dpkg.cfg' % Bcfg2.Options.setup.apt_etc_path] + \
+ [entry.get('name') for struct in config
+ for entry in struct
+ if (entry.tag == 'Path' and
+ entry.get('name').startswith(
+ '%s/apt/sources.list' % Bcfg2.Options.setup.apt_etc_path))]
+ self.nonexistent = [
+ entry.get('name') for struct in config for entry in struct
+ if entry.tag == 'Path' and entry.get('type') == 'nonexistent']
os.environ["DEBIAN_FRONTEND"] = 'noninteractive'
self.actions = {}
- if self.setup['kevlar'] and not self.setup['dryrun']:
+ if Bcfg2.Options.setup.kevlar and not Bcfg2.Options.setup.dry_run:
self.cmd.run("%s --force-confold --configure --pending" %
self.dpkg)
self.cmd.run("%s clean" % self.aptget)
@@ -184,8 +196,9 @@ class APT(Bcfg2.Client.Tools.Tool):
return False
else:
# version matches
- if not self.setup['quick'] and entry.get('verify', 'true') == 'true' \
- and checksums:
+ if (not Bcfg2.Options.setup.quick and
+ entry.get('verify', 'true') == 'true'
+ and checksums):
pkgsums = self.VerifyDebsums(entry, modlist)
return pkgsums
return True
diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py
index 05e35befc..921d5723e 100644
--- a/src/lib/Bcfg2/Client/Tools/Action.py
+++ b/src/lib/Bcfg2/Client/Tools/Action.py
@@ -2,10 +2,9 @@
import os
import sys
-import select
import Bcfg2.Client.Tools
-from Bcfg2.Client.Frame import matches_white_list, passes_black_list
-from Bcfg2.Compat import input # pylint: disable=W0622
+from Bcfg2.Utils import safe_input
+from Bcfg2.Client import matches_white_list, passes_black_list
class Action(Bcfg2.Client.Tools.Tool):
@@ -17,13 +16,13 @@ class Action(Bcfg2.Client.Tools.Tool):
def _action_allowed(self, action):
""" Return true if the given action is allowed to be run by
the whitelist or blacklist """
- if self.setup['decision'] == 'whitelist' and \
- not matches_white_list(action, self.setup['decision_list']):
+ if (Bcfg2.Options.setup.decision == 'whitelist' and
+ not matches_white_list(action, Bcfg2.Options.setup.decision_list)):
self.logger.info("In whitelist mode: suppressing Action: %s" %
action.get('name'))
return False
- if self.setup['decision'] == 'blacklist' and \
- not passes_black_list(action, self.setup['decision_list']):
+ if (Bcfg2.Options.setup.decision == 'blacklist' and
+ not passes_black_list(action, Bcfg2.Options.setup.decision_list)):
self.logger.info("In blacklist mode: suppressing Action: %s" %
action.get('name'))
return False
@@ -37,19 +36,15 @@ class Action(Bcfg2.Client.Tools.Tool):
shell = True
shell_string = '(in shell) '
- if not self.setup['dryrun']:
- if self.setup['interactive']:
+ if not Bcfg2.Options.setup.dryrun:
+ if Bcfg2.Options.setup.interactive:
prompt = ('Run Action %s%s, %s: (y/N): ' %
(shell_string, entry.get('name'),
entry.get('command')))
- # flush input buffer
- while len(select.select([sys.stdin.fileno()], [], [],
- 0.0)[0]) > 0:
- os.read(sys.stdin.fileno(), 4096)
- ans = input(prompt)
+ ans = safe_input(prompt)
if ans not in ['y', 'Y']:
return False
- if self.setup['servicemode'] == 'build':
+ if Bcfg2.Options.setup.service_mode == 'build':
if entry.get('build', 'true') == 'false':
self.logger.debug("Action: Deferring execution of %s due "
"to build mode" % entry.get('command'))
diff --git a/src/lib/Bcfg2/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
index 4833f3f68..c2c7e21c1 100644
--- a/src/lib/Bcfg2/Client/Tools/Chkconfig.py
+++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
@@ -3,7 +3,6 @@
"""This is chkconfig support."""
import os
-
import Bcfg2.Client.Tools
import Bcfg2.Client.XML
@@ -96,15 +95,15 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
bootcmd = '/sbin/chkconfig %s %s' % (entry.get('name'),
bootstatus)
bootcmdrv = self.cmd.run(bootcmd).success
- if self.setup['servicemode'] == 'disabled':
+ if Bcfg2.Options.setup.servicemode == 'disabled':
# 'disabled' means we don't attempt to modify running svcs
return bootcmdrv
- buildmode = self.setup['servicemode'] == 'build'
- if (entry.get('status') == 'on' and not buildmode) and \
- entry.get('current_status') == 'off':
+ buildmode = Bcfg2.Options.setup.servicemode == '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':
+ elif ((entry.get('status') == 'off' or buildmode) and
+ entry.get('current_status') == 'on'):
svccmdrv = self.stop_service(entry)
else:
svccmdrv = True # ignore status attribute
diff --git a/src/lib/Bcfg2/Client/Tools/MacPorts.py b/src/lib/Bcfg2/Client/Tools/MacPorts.py
index dcf58cfec..c28f8c743 100644
--- a/src/lib/Bcfg2/Client/Tools/MacPorts.py
+++ b/src/lib/Bcfg2/Client/Tools/MacPorts.py
@@ -39,8 +39,6 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool):
if entry.attrib['name'] in self.installed:
if (self.installed[entry.attrib['name']] == entry.attrib['version']
or entry.attrib['version'] == 'any'):
- #if not self.setup['quick'] and \
- # entry.get('verify', 'true') == 'true':
#FIXME: We should be able to check this once
# http://trac.macports.org/ticket/15709 is implemented
return True
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/File.py b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
index 9f47fb53a..482320e0d 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/File.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
@@ -6,6 +6,7 @@ import stat
import time
import difflib
import tempfile
+import Bcfg2.Options
from Bcfg2.Client.Tools.POSIX.base import POSIXTool
from Bcfg2.Compat import unicode, b64encode, b64decode # pylint: disable=W0622
@@ -43,7 +44,7 @@ class POSIXFile(POSIXTool):
tempdata = entry.text
if isinstance(tempdata, unicode) and unicode != str:
try:
- tempdata = tempdata.encode(self.setup['encoding'])
+ tempdata = tempdata.encode(Bcfg2.Options.setup.encoding)
except UnicodeEncodeError:
err = sys.exc_info()[1]
self.logger.error("POSIX: Error encoding file %s: %s" %
@@ -82,7 +83,7 @@ class POSIXFile(POSIXTool):
self.logger.debug("POSIX: %s has incorrect contents" %
entry.get("name"))
self._get_diffs(
- entry, interactive=self.setup['interactive'],
+ entry, interactive=Bcfg2.Options.setup.interactive,
sensitive=entry.get('sensitive', 'false').lower() == 'true',
is_binary=is_binary, content=content)
return POSIXTool.verify(self, entry, modlist) and not different
@@ -170,7 +171,8 @@ class POSIXFile(POSIXTool):
(entry.get("name"), sys.exc_info()[1]))
return False
if not is_binary:
- is_binary |= not self._is_string(content, self.setup['encoding'])
+ is_binary |= not self._is_string(content,
+ Bcfg2.Options.setup.encoding)
if is_binary:
# don't compute diffs if the file is binary
prompt.append('Binary file, no printable diff')
@@ -183,7 +185,7 @@ class POSIXFile(POSIXTool):
if diff:
udiff = '\n'.join(l.rstrip('\n') for l in diff)
if hasattr(udiff, "decode"):
- udiff = udiff.decode(self.setup['encoding'])
+ udiff = udiff.decode(Bcfg2.Options.setup.encoding)
try:
prompt.append(udiff)
except UnicodeEncodeError:
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
index 4f1f8e5aa..db0fa96ab 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
@@ -4,20 +4,31 @@ import os
import re
import sys
import shutil
-from datetime import datetime
+import Bcfg2.Options
import Bcfg2.Client.Tools
+from datetime import datetime
from Bcfg2.Compat import walk_packages
from Bcfg2.Client.Tools.POSIX.base import POSIXTool
class POSIX(Bcfg2.Client.Tools.Tool):
"""POSIX File support code."""
- name = 'POSIX'
+
+ options = Bcfg2.Client.Tools.Tool.options + [
+ Bcfg2.Options.PathOption(
+ cf=('paranoid', 'path'), default='/var/cache/bcfg2',
+ dest='paranoid_path',
+ help='Specify path for paranoid file backups'),
+ Bcfg2.Options.Option(
+ cf=('paranoid', 'max_copies'), default=1, type=int,
+ dest='paranoid_copies',
+ help='Specify the number of paranoid copies you want'),
+ Bcfg2.Options.BooleanOption(
+ '-P', '--paranoid', cf=('client', 'paranoid'),
+ help='Make automatic backups of config files')]
def __init__(self, config):
Bcfg2.Client.Tools.Tool.__init__(self, config)
- self.ppath = self.setup['ppath']
- self.max_copies = self.setup['max_copies']
self._handlers = self._load_handlers()
self.logger.debug("POSIX: Handlers loaded: %s" %
(", ".join(self._handlers.keys())))
@@ -89,7 +100,7 @@ class POSIX(Bcfg2.Client.Tools.Tool):
self.logger.debug("POSIX: Verifying entry %s:%s:%s" %
(entry.tag, entry.get("type"), entry.get("name")))
ret = self._handlers[entry.get("type")].verify(entry, modlist)
- if self.setup['interactive'] and not ret:
+ if Bcfg2.Options.setup.interactive and not ret:
entry.set('qtext',
'%s\nInstall %s %s: (y/N) ' %
(entry.get('qtext', ''),
@@ -103,35 +114,39 @@ class POSIX(Bcfg2.Client.Tools.Tool):
bkupnam + r'_\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}$')
# current list of backups for this file
try:
- bkuplist = [f for f in os.listdir(self.ppath) if
- bkup_re.match(f)]
+ bkuplist = [f
+ for f in os.listdir(Bcfg2.Options.setup.paranoid_path)
+ if bkup_re.match(f)]
except OSError:
err = sys.exc_info()[1]
self.logger.error("POSIX: Failed to create backup list in %s: %s" %
- (self.ppath, err))
+ (Bcfg2.Options.setup.paranoid_path, err))
return
bkuplist.sort()
- while len(bkuplist) >= int(self.max_copies):
+ while len(bkuplist) >= int(Bcfg2.Options.setup.paranoid_copies):
# remove the oldest backup available
oldest = bkuplist.pop(0)
self.logger.info("POSIX: Removing old backup %s" % oldest)
try:
- os.remove(os.path.join(self.ppath, oldest))
+ os.remove(os.path.join(Bcfg2.Options.setup.paranoid_path,
+ oldest))
except OSError:
err = sys.exc_info()[1]
- self.logger.error("POSIX: Failed to remove old backup %s: %s" %
- (os.path.join(self.ppath, oldest), err))
+ self.logger.error(
+ "POSIX: Failed to remove old backup %s: %s" %
+ (os.path.join(Bcfg2.Options.setup.paranoid_path, oldest),
+ err))
def _paranoid_backup(self, entry):
""" Take a backup of the specified entry for paranoid mode """
if (entry.get("paranoid", 'false').lower() == 'true' and
- self.setup.get("paranoid", False) and
+ Bcfg2.Options.setup.paranoid and
entry.get('current_exists', 'true') == 'true' and
not os.path.isdir(entry.get("name"))):
self._prune_old_backups(entry)
bkupnam = "%s_%s" % (entry.get('name').replace('/', '_'),
datetime.isoformat(datetime.now()))
- bfile = os.path.join(self.ppath, bkupnam)
+ bfile = os.path.join(Bcfg2.Options.setup.paranoid_path, bkupnam)
try:
shutil.copy(entry.get('name'), bfile)
self.logger.info("POSIX: Backup of %s saved to %s" %
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
index fb5d06e54..c9164cb88 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/base.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -105,23 +105,23 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
path = entry.get("name")
rv = True
- if entry.get("owner") and entry.get("group"):
- try:
- self.logger.debug("POSIX: Setting ownership of %s to %s:%s" %
- (path,
- self._norm_entry_uid(entry),
- self._norm_entry_gid(entry)))
- os.chown(path, self._norm_entry_uid(entry),
- self._norm_entry_gid(entry))
- except KeyError:
- self.logger.error('POSIX: Failed to change ownership of %s' %
- path)
- rv = False
- os.chown(path, 0, 0)
- except OSError:
- self.logger.error('POSIX: Failed to change ownership of %s' %
- path)
- rv = False
+ if os.geteuid() == 0:
+ if entry.get("owner") and entry.get("group"):
+ try:
+ self.logger.debug("POSIX: Setting ownership of %s to %s:%s"
+ % (path,
+ self._norm_entry_uid(entry),
+ self._norm_entry_gid(entry)))
+ os.chown(path, self._norm_entry_uid(entry),
+ self._norm_entry_gid(entry))
+ except (OSError, KeyError):
+ self.logger.error('POSIX: Failed to change ownership of %s'
+ % path)
+ rv = False
+ if sys.exc_info()[0] == KeyError:
+ os.chown(path, 0, 0)
+ else:
+ self.logger.debug("POSIX: Run as non-root, not setting ownership")
if entry.get("mode"):
wanted_mode = int(entry.get('mode'), 8)
diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
index 9d7441b5c..19657f12a 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
@@ -8,9 +8,31 @@ import Bcfg2.Client.Tools
from Bcfg2.Utils import PackedDigitRange
+def uid_range_type(val):
+ return PackedDigitRange(*Bcfg2.Options.Types.comma_list(val))
+
+
class POSIXUsers(Bcfg2.Client.Tools.Tool):
""" A tool to handle creating users and groups with
useradd/mod/del and groupadd/mod/del """
+ options = Bcfg2.Client.Tools.Tool.options + [
+ Bcfg2.Options.Option(
+ cf=('POSIXUsers', 'uid_whitelist'), default=[],
+ type=uid_range_type,
+ help="UID ranges the POSIXUsers tool will manage"),
+ Bcfg2.Options.Option(
+ cf=('POSIXUsers', 'gid_whitelist'), default=[],
+ type=uid_range_type,
+ help="GID ranges the POSIXUsers tool will manage"),
+ Bcfg2.Options.Option(
+ cf=('POSIXUsers', 'uid_blacklist'), default=[],
+ type=uid_range_type,
+ help="UID ranges the POSIXUsers tool will not manage"),
+ Bcfg2.Options.Option(
+ cf=('POSIXUsers', 'gid_blacklist'), default=[],
+ type=uid_range_type,
+ help="GID ranges the POSIXUsers tool will not manage")]
+
__execs__ = ['/usr/sbin/useradd', '/usr/sbin/usermod', '/usr/sbin/userdel',
'/usr/sbin/groupadd', '/usr/sbin/groupmod',
'/usr/sbin/groupdel']
@@ -34,20 +56,10 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool):
self.set_defaults = dict(POSIXUser=self.populate_user_entry,
POSIXGroup=lambda g: g)
self._existing = None
- self._whitelist = dict(POSIXUser=None, POSIXGroup=None)
- self._blacklist = dict(POSIXUser=None, POSIXGroup=None)
- if self.setup['posix_uid_whitelist']:
- self._whitelist['POSIXUser'] = \
- PackedDigitRange(*self.setup['posix_uid_whitelist'])
- else:
- self._blacklist['POSIXUser'] = \
- PackedDigitRange(*self.setup['posix_uid_blacklist'])
- if self.setup['posix_gid_whitelist']:
- self._whitelist['POSIXGroup'] = \
- PackedDigitRange(*self.setup['posix_gid_whitelist'])
- else:
- self._blacklist['POSIXGroup'] = \
- PackedDigitRange(*self.setup['posix_gid_blacklist'])
+ self._whitelist = dict(POSIXUser=Bcfg2.Options.setup.uid_whitelist,
+ POSIXGroup=Bcfg2.Options.setup.gid_whitelist)
+ self._blacklist = dict(POSIXUser=Bcfg2.Options.setup.uid_blacklist,
+ POSIXGroup=Bcfg2.Options.setup.gid_blacklist)
@property
def existing(self):
@@ -164,7 +176,7 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool):
% (entry.tag, entry.get("name"),
actual, expected)]))
rv = False
- if self.setup['interactive'] and not rv:
+ if Bcfg2.Options.setup.interactive and not rv:
entry.set('qtext',
'%s\nInstall %s %s: (y/N) ' %
(entry.get('qtext', ''), entry.tag, entry.get('name')))
@@ -173,7 +185,7 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool):
def VerifyPOSIXGroup(self, entry, _):
""" Verify a POSIXGroup entry """
rv = self._verify(entry)
- if self.setup['interactive'] and not rv:
+ if Bcfg2.Options.setup.interactive and not rv:
entry.set('qtext',
'%s\nInstall %s %s: (y/N) ' %
(entry.get('qtext', ''), entry.tag, entry.get('name')))
diff --git a/src/lib/Bcfg2/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py
index d7d60a66d..2ab9b7403 100644
--- a/src/lib/Bcfg2/Client/Tools/Pacman.py
+++ b/src/lib/Bcfg2/Client/Tools/Pacman.py
@@ -38,8 +38,6 @@ class Pacman(Bcfg2.Client.Tools.PkgTool):
return True
elif self.installed[entry.attrib['name']] == \
entry.attrib['version']:
- #if not self.setup['quick'] and \
- # entry.get('verify', 'true') == 'true':
#FIXME: need to figure out if pacman
# allows you to verify packages
return True
diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py
index e52da081b..a877b564f 100644
--- a/src/lib/Bcfg2/Client/Tools/Portage.py
+++ b/src/lib/Bcfg2/Client/Tools/Portage.py
@@ -5,9 +5,13 @@ import Bcfg2.Client.Tools
class Portage(Bcfg2.Client.Tools.PkgTool):
- """The Gentoo toolset implements package and service operations and
- inherits the rest from Toolset.Toolset."""
- name = 'Portage'
+ """The Gentoo toolset implements package and service operations
+ and inherits the rest from Tools.Tool."""
+
+ options = Bcfg2.Client.Tools.PkgTool.options + [
+ Bcfg2.Options.BooleanOption(
+ cf=('Portage', 'binpkgonly'), help='Portage binary packages only')]
+
__execs__ = ['/usr/bin/emerge', '/usr/bin/equery']
__handles__ = [('Package', 'ebuild')]
__req__ = {'Package': ['name', 'version']}
@@ -25,8 +29,7 @@ class Portage(Bcfg2.Client.Tools.PkgTool):
self._pkg_pattern = re.compile(r'(.*)-(\d.*)')
self._ebuild_pattern = re.compile('(ebuild|binary)')
self.installed = {}
- self._binpkgonly = self.setup.get('portage_binpkgonly', False)
- if self._binpkgonly:
+ if Bcfg2.Options.setup.binpkgonly:
self.pkgtool = self._binpkgtool
self.RefreshPackages()
@@ -61,7 +64,7 @@ class Portage(Bcfg2.Client.Tools.PkgTool):
version = self.installed[entry.get('name')]
entry.set('current_version', version)
- if not self.setup['quick']:
+ if not Bcfg2.Options.setup.quick:
if ('verify' not in entry.attrib or
entry.get('verify').lower() == 'true'):
diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py
index be5ad01e2..1ebc61c93 100644
--- a/src/lib/Bcfg2/Client/Tools/RPM.py
+++ b/src/lib/Bcfg2/Client/Tools/RPM.py
@@ -1075,6 +1075,42 @@ if __name__ == "__main__":
class RPM(Bcfg2.Client.Tools.PkgTool):
"""Support for RPM packages."""
+ options = Bcfg2.Client.Tools.PkgTool.options + [
+ Bcfg2.Options.Option(
+ cf=('RPM', 'installonlypackages'), dest="rpm_installonly",
+ type=Bcfg2.Options.Types.comma_list,
+ default=['kernel', 'kernel-bigmem', 'kernel-enterprise',
+ 'kernel-smp', 'kernel-modules', 'kernel-debug',
+ 'kernel-unsupported', 'kernel-devel', 'kernel-source',
+ 'kernel-default', 'kernel-largesmp-devel',
+ 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'],
+ help='RPM install-only packages'),
+ Bcfg2.Options.BooleanOption(
+ cf=('RPM', 'pkg_checks'), default=True, dest="rpm_pkg_checks",
+ help="Perform RPM package checks"),
+ Bcfg2.Options.BooleanOption(
+ cf=('RPM', 'pkg_verify'), default=True, dest="rpm_pkg_verify",
+ help="Perform RPM package verify"),
+ Bcfg2.Options.BooleanOption(
+ cf=('RPM', 'install_missing'), default=True,
+ dest="rpm_install_missing",
+ help="Install missing packages"),
+ Bcfg2.Options.Option(
+ cf=('RPM', 'erase_flags'), default=["allmatches"],
+ dest="rpm_erase_flags",
+ help="RPM erase flags"),
+ Bcfg2.Options.BooleanOption(
+ cf=('RPM', 'fix_version'), default=True,
+ dest="rpm_fix_version",
+ help="Fix (upgrade or downgrade) packages with the wrong version"),
+ Bcfg2.Options.BooleanOption(
+ cf=('RPM', 'reinstall_broken'), default=True,
+ dest="rpm_reinstall_broken",
+ help="Reinstall packages that fail to verify"),
+ Bcfg2.Options.Option(
+ cf=('RPM', 'verify_flags'), default=[], dest="rpm_verify_flags",
+ help="RPM verify flags")]
+
__execs__ = ['/bin/rpm', '/var/lib/rpm']
__handles__ = [('Package', 'rpm')]
@@ -1109,43 +1145,36 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
self.modlists = {}
self.gpg_keyids = self.getinstalledgpg()
- opt_prefix = self.name.lower()
- self.installOnlyPkgs = self.setup["%s_installonly" % opt_prefix]
+ self.installOnlyPkgs = Bcfg2.Options.setup.rpm_installonly
if 'gpg-pubkey' not in self.installOnlyPkgs:
self.installOnlyPkgs.append('gpg-pubkey')
- self.erase_flags = self.setup['%s_erase_flags' % opt_prefix]
- self.pkg_checks = self.setup['%s_pkg_checks' % opt_prefix]
- self.pkg_verify = self.setup['%s_pkg_verify' % opt_prefix]
- self.installed_action = self.setup['%s_installed_action' % opt_prefix]
- self.version_fail_action = self.setup['%s_version_fail_action' %
- opt_prefix]
- self.verify_fail_action = self.setup['%s_verify_fail_action' %
- opt_prefix]
- self.verify_flags = self.setup['%s_verify_flags' % opt_prefix]
+ self.verify_flags = Bcfg2.Options.setup.rpm_verify_flags
if '' in self.verify_flags:
self.verify_flags.remove('')
self.logger.debug('%s: installOnlyPackages = %s' %
(self.name, self.installOnlyPkgs))
self.logger.debug('%s: erase_flags = %s' %
- (self.name, self.erase_flags))
+ (self.name, Bcfg2.Options.setup.rpm_erase_flags))
self.logger.debug('%s: pkg_checks = %s' %
- (self.name, self.pkg_checks))
+ (self.name, Bcfg2.Options.setup.rpm_pkg_checks))
self.logger.debug('%s: pkg_verify = %s' %
- (self.name, self.pkg_verify))
- self.logger.debug('%s: installed_action = %s' %
- (self.name, self.installed_action))
- self.logger.debug('%s: version_fail_action = %s' %
- (self.name, self.version_fail_action))
- self.logger.debug('%s: verify_fail_action = %s' %
- (self.name, self.verify_fail_action))
+ (self.name, Bcfg2.Options.setup.rpm_pkg_verify))
+ self.logger.debug('%s: install_missing = %s' %
+ (self.name, Bcfg2.Options.setup.install_missing))
+ self.logger.debug('%s: fix_version = %s' %
+ (self.name, Bcfg2.Options.setup.rpm_fix_version))
+ self.logger.debug('%s: reinstall_broken = %s' %
+ (self.name,
+ Bcfg2.Options.setup.rpm_reinstall_broken))
self.logger.debug('%s: verify_flags = %s' %
(self.name, self.verify_flags))
# Force a re- prelink of all packages if prelink exists.
# Many, if not most package verifies can be caused by out of
# date prelinking.
- if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']:
+ if (os.path.isfile('/usr/sbin/prelink') and
+ not Bcfg2.Options.setup.dry_run):
rv = self.cmd.run('/usr/sbin/prelink -a -mR')
if rv.success:
self.logger.debug('Pre-emptive prelink succeeded')
@@ -1176,7 +1205,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
refresh_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES)
for nevra in rpmpackagelist(refresh_ts):
self.installed.setdefault(nevra['name'], []).append(nevra)
- if self.setup['debug']:
+ if Bcfg2.Options.setup.debug:
print("The following package instances are installed:")
for name, instances in list(self.installed.items()):
self.logger.debug(" " + name)
@@ -1217,7 +1246,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
instance = Bcfg2.Client.XML.SubElement(entry, 'Package')
for attrib in list(entry.attrib.keys()):
instance.attrib[attrib] = entry.attrib[attrib]
- if (self.pkg_checks and
+ if (Bcfg2.Options.setup.rpm_pkg_checks and
entry.get('pkg_checks', 'true').lower() == 'true'):
if 'any' in [entry.get('version'), pinned_version]:
version, release = 'any', 'any'
@@ -1240,7 +1269,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
if entry.get('name') in self.installed:
# There is at least one instance installed.
- if (self.pkg_checks and
+ if (Bcfg2.Options.setup.rpm_pkg_checks and
entry.get('pkg_checks', 'true').lower() == 'true'):
rpmTs = rpm.TransactionSet()
rpmHeader = None
@@ -1269,7 +1298,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
self.logger.debug(" %s" % self.str_evra(inst))
self.instance_status[inst]['installed'] = True
- if (self.pkg_verify and
+ if (Bcfg2.Options.setup.rpm_pkg_verify and
inst.get('pkg_verify', 'true').lower() == 'true'):
flags = inst.get('verify_flags', '').split(',') + self.verify_flags
if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
@@ -1280,7 +1309,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
pkg.get('gpgkeyid', '')))
self.logger.debug(' Disabling signature check.')
- if self.setup.get('quick', False):
+ if Bcfg2.Options.setup.quick:
if prelink_exists:
flags += ['nomd5', 'nosize']
else:
@@ -1328,7 +1357,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
self.logger.debug(" %s" % self.str_evra(inst))
self.instance_status[inst]['installed'] = True
- if (self.pkg_verify and
+ if (Bcfg2.Options.setup.rpm_pkg_verify and
inst.get('pkg_verify', 'true').lower() == 'true'):
flags = inst.get('verify_flags', '').split(',') + self.verify_flags
if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
@@ -1339,7 +1368,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
pkg.get('gpgkeyid', '')))
self.logger.info(' Disabling signature check.')
- if self.setup.get('quick', False):
+ if Bcfg2.Options.setup.quick:
if prelink_exists:
flags += ['nomd5', 'nosize']
else:
@@ -1374,7 +1403,8 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
instance_fail = False
# Dump the rpm verify results.
#****Write something to format this nicely.*****
- if self.setup['debug'] and self.instance_status[inst].get('verify', None):
+ if (Bcfg2.Options.setup.debug and
+ self.instance_status[inst].get('verify', None)):
self.logger.debug(self.instance_status[inst]['verify'])
self.instance_status[inst]['verify_fail'] = False
@@ -1502,7 +1532,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
self.logger.info(" This package will be deleted in a future version of the RPM driver.")
#pkgspec_list.append(pkg_spec)
- erase_results = rpm_erase(pkgspec_list, self.erase_flags)
+ erase_results = rpm_erase(pkgspec_list, Bcfg2.Options.setup.rpm_erase_flags)
if erase_results == []:
self.modified += packages
for pkg in pkgspec_list:
@@ -1530,7 +1560,9 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
% (pkgspec.get('name'), self.str_evra(pkgspec)))
self.logger.info(" This package will be deleted in a future version of the RPM driver.")
continue # Don't delete the gpg-pubkey packages for now.
- erase_results = rpm_erase([pkgspec], self.erase_flags)
+ erase_results = rpm_erase(
+ [pkgspec],
+ Bcfg2.Options.setup.rpm_erase_flags)
if erase_results == []:
pkg_modified = True
self.logger.info("Deleted %s %s" % \
@@ -1555,28 +1587,27 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
"""
fix = False
- if inst_status.get('installed', False) == False:
- if instance.get('installed_action', 'install') == "install" and \
- self.installed_action == "install":
+ if not inst_status.get('installed', False):
+ if (instance.get('install_missing', 'true').lower() == "true" and
+ Bcfg2.Options.setup.rpm_install_missing):
fix = True
else:
self.logger.debug('Installed Action for %s %s is to not install' % \
(inst_status.get('pkg').get('name'),
self.str_evra(instance)))
- elif inst_status.get('version_fail', False) == True:
- if instance.get('version_fail_action', 'upgrade') == "upgrade" and \
- self.version_fail_action == "upgrade":
+ elif inst_status.get('version_fail', False):
+ if (instance.get('fix_version', 'true').lower() == "true" and
+ Bcfg2.Options.setup.rpm_fix_version):
fix = True
else:
self.logger.debug('Version Fail Action for %s %s is to not upgrade' % \
(inst_status.get('pkg').get('name'),
self.str_evra(instance)))
- elif inst_status.get('verify_fail', False) == True and self.name == "RPM":
- # yum can't reinstall packages so only do this for rpm.
- if instance.get('verify_fail_action', 'reinstall') == "reinstall" and \
- self.verify_fail_action == "reinstall":
+ elif inst_status.get('verify_fail', False):
+ if (instance.get('reinstall_broken', 'true').lower() == "true" and
+ Bcfg2.Options.setup.rpm_reinstall_broken):
for inst in inst_status.get('verify'):
# This needs to be a for loop rather than a straight get()
# because the underlying routines handle multiple packages
@@ -1633,9 +1664,8 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
# Remove extra instances.
# Can not reverify because we don't have a package entry.
if len(self.extra_instances) > 0:
- if (self.setup.get('remove') == 'all' or \
- self.setup.get('remove') == 'packages') and\
- not self.setup.get('dryrun'):
+ if (Bcfg2.Options.setup.remove in ['all', 'packages'] and
+ not Bcfg2.Options.setup.dry_run):
self.Remove(self.extra_instances)
else:
self.logger.info("The following extra package instances will be removed by the '-r' option:")
@@ -1744,7 +1774,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
for inst in upgrade_pkgs])
self.RefreshPackages()
- if not self.setup['kevlar']:
+ if not Bcfg2.Options.setup.kevlar:
for pkg_entry in packages:
self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name')))
states[pkg_entry] = self.VerifyPackage(pkg_entry, \
@@ -1847,7 +1877,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
return False
# We don't want to do any checks so we don't care what the entry has in it.
- if (not self.pkg_checks or
+ if (not Bcfg2.Options.setup.rpm_pkg_checks or
entry.get('pkg_checks', 'true').lower() == 'false'):
return True
@@ -1914,7 +1944,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
if name not in packages:
extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype)
for installed_inst in instances:
- if self.setup['extra']:
+ if Bcfg2.Options.setup.extra:
self.logger.info("Extra Package %s %s." % \
(name, self.str_evra(installed_inst)))
tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \
@@ -1927,7 +1957,6 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
extras.append(extra_entry)
return extras
-
def FindExtraInstances(self, pkg_entry, installed_entry):
"""
Check for installed instances that are not in the config.
diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py
index 92572ef1d..ef89ef46d 100644
--- a/src/lib/Bcfg2/Client/Tools/SELinux.py
+++ b/src/lib/Bcfg2/Client/Tools/SELinux.py
@@ -141,7 +141,7 @@ class SELinux(Bcfg2.Client.Tools.Tool):
def GenericSEVerify(self, entry, _):
"""Dispatch verify to the proper method according to entry tag"""
rv = self.handlers[entry.tag].Verify(entry)
- if entry.get('qtext') and self.setup['interactive']:
+ if entry.get('qtext') and Bcfg2.Options.setup.interactive:
entry.set('qtext',
'%s\nInstall %s: (y/N) ' %
(entry.get('qtext'),
@@ -174,7 +174,6 @@ class SELinuxEntryHandler(object):
def __init__(self, tool, config):
self.tool = tool
self.logger = logging.getLogger(self.__class__.__name__)
- self.setup = tool.setup
self.config = config
self._records = None
self._all = None
diff --git a/src/lib/Bcfg2/Client/Tools/SYSV.py b/src/lib/Bcfg2/Client/Tools/SYSV.py
index 7be7b6fa3..f149be7af 100644
--- a/src/lib/Bcfg2/Client/Tools/SYSV.py
+++ b/src/lib/Bcfg2/Client/Tools/SYSV.py
@@ -80,7 +80,7 @@ class SYSV(Bcfg2.Client.Tools.PkgTool):
self.logger.debug("Package %s not installed" %
entry.get("name"))
else:
- if (self.setup['quick'] or
+ if (Bcfg2.Options.setup.quick or
entry.attrib.get('verify', 'true') == 'false'):
return True
rv = self.cmd.run("/usr/sbin/pkgchk -n %s" % entry.get('name'))
diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py
index 147615f47..ae238174b 100644
--- a/src/lib/Bcfg2/Client/Tools/YUM.py
+++ b/src/lib/Bcfg2/Client/Tools/YUM.py
@@ -119,6 +119,34 @@ class YumDisplay(yum.callbacks.ProcessTransBaseCallback):
class YUM(Bcfg2.Client.Tools.PkgTool):
"""Support for Yum packages."""
+
+ options = Bcfg2.Client.Tools.PkgTool.options + [
+ Bcfg2.Options.BooleanOption(
+ cf=('YUM', 'pkg_checks'), default=True, dest="yum_pkg_checks",
+ help="Perform YUM package checks"),
+ Bcfg2.Options.BooleanOption(
+ cf=('YUM', 'pkg_verify'), default=True, dest="yum_pkg_verify",
+ help="Perform YUM package verify"),
+ Bcfg2.Options.BooleanOption(
+ cf=('YUM', 'install_missing'), default=True,
+ dest="yum_install_missing",
+ help="Install missing packages"),
+ Bcfg2.Options.Option(
+ cf=('YUM', 'erase_flags'), default=["allmatches"],
+ dest="yum_erase_flags",
+ help="YUM erase flags"),
+ Bcfg2.Options.BooleanOption(
+ cf=('YUM', 'fix_version'), default=True,
+ dest="yum_fix_version",
+ help="Fix (upgrade or downgrade) packages with the wrong version"),
+ Bcfg2.Options.BooleanOption(
+ cf=('YUM', 'reinstall_broken'), default=True,
+ dest="yum_reinstall_broken",
+ help="Reinstall packages that fail to verify"),
+ Bcfg2.Options.Option(
+ cf=('YUM', 'verify_flags'), default=[], dest="yum_verify_flags",
+ help="YUM verify flags")]
+
pkgtype = 'yum'
__execs__ = []
__handles__ = [('Package', 'yum'),
@@ -173,26 +201,23 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
else:
dest[pname] = dict(data)
- # Process the Yum section from the config file. These are all
- # boolean flags, either we do stuff or we don't
- self.pkg_checks = self.setup["yum_pkg_checks"]
- self.pkg_verify = self.setup["yum_pkg_verify"]
- self.do_install = self.setup["yum_installed_action"] == "install"
- self.do_upgrade = self.setup["yum_version_fail_action"] == "upgrade"
- self.do_reinst = self.setup["yum_verify_fail_action"] == "reinstall"
- self.verify_flags = self.setup["yum_verify_flags"]
-
self.installonlypkgs = self.yumbase.conf.installonlypkgs
if 'gpg-pubkey' not in self.installonlypkgs:
self.installonlypkgs.append('gpg-pubkey')
- self.logger.debug("Yum: Install missing: %s" % self.do_install)
- self.logger.debug("Yum: pkg_checks: %s" % self.pkg_checks)
- self.logger.debug("Yum: pkg_verify: %s" % self.pkg_verify)
- self.logger.debug("Yum: Upgrade on version fail: %s" % self.do_upgrade)
- self.logger.debug("Yum: Reinstall on verify fail: %s" % self.do_reinst)
+ self.logger.debug("Yum: Install missing: %s" %
+ Bcfg2.Options.setup.yum_install_missing)
+ self.logger.debug("Yum: pkg_checks: %s" %
+ Bcfg2.Options.setup.yum_pkg_checks)
+ self.logger.debug("Yum: pkg_verify: %s" %
+ Bcfg2.Options.setup.yum_pkg_verify)
+ self.logger.debug("Yum: Upgrade on version fail: %s" %
+ Bcfg2.Options.setup.yum_fix_version)
+ self.logger.debug("Yum: Reinstall on verify fail: %s" %
+ Bcfg2.Options.setup.yum_reinstall_broken)
self.logger.debug("Yum: installonlypkgs: %s" % self.installonlypkgs)
- self.logger.debug("Yum: verify_flags: %s" % self.verify_flags)
+ self.logger.debug("Yum: verify_flags: %s" %
+ Bcfg2.Options.setup.yum_verify_flags)
def _loadYumBase(self):
''' this may be called before PkgTool.__init__() is called on
@@ -203,18 +228,14 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
packages. '''
rv = yum.YumBase() # pylint: disable=C0103
- if hasattr(self, "setup"):
- setup = self.setup
- else:
- setup = Bcfg2.Options.get_option_parser()
if hasattr(self, "logger"):
logger = self.logger
else:
logger = logging.getLogger(self.name)
- if setup['debug']:
+ if Bcfg2.Options.setup.debug:
debuglevel = 3
- elif setup['verbose']:
+ elif Bcfg2.Options.setup.verbose:
debuglevel = 2
else:
debuglevel = 0
@@ -314,7 +335,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
using. Disabling file checksums is a new feature yum
3.2.17-ish """
try:
- return pkg.verify(fast=self.setup.get('quick', False))
+ return pkg.verify(fast=Bcfg2.Options.setup.quick)
except TypeError:
# Older Yum API
return pkg.verify()
@@ -439,9 +460,9 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
package_fail = False
qtext_versions = []
virt_pkg = False
- pkg_checks = (self.pkg_checks and
+ pkg_checks = (Bcfg2.Options.setup.yum_pkg_checks and
entry.get('pkg_checks', 'true').lower() == 'true')
- pkg_verify = (self.pkg_verify and
+ pkg_verify = (Bcfg2.Options.setup.yum_pkg_verify and
entry.get('pkg_verify', 'true').lower() == 'true')
yum_group = False
@@ -534,7 +555,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
inst.get('verify_flags').lower().replace(' ',
',').split(',')
else:
- verify_flags = self.verify_flags
+ verify_flags = Bcfg2.Options.setup.yum_verify_flags
if 'arch' in nevra:
# If arch is specified use it to select the package
@@ -622,7 +643,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
qtext_versions.append("U(%s)" % str(all_pkg_objs[0]))
continue
- if self.setup.get('quick', False):
+ if Bcfg2.Options.setup.quick:
# Passed -q on the command line
continue
if not (pkg_verify and
@@ -696,7 +717,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
install_only = False
if virt_pkg or \
- (install_only and not self.setup['kevlar']) or \
+ (install_only and not Bcfg2.Options.setup.kevlar) or \
yum_group:
# virtual capability supplied, we are probably dealing
# with multiple packages of different names. This check
@@ -904,8 +925,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
# Remove extra instances.
# Can not reverify because we don't have a package entry.
if self.extra_instances is not None and len(self.extra_instances) > 0:
- if (self.setup.get('remove') == 'all' or
- self.setup.get('remove') == 'packages'):
+ if Bcfg2.Options.setup.remove in ['all', 'packages']:
self.Remove(self.extra_instances)
else:
self.logger.info("The following extra package instances will "
@@ -930,11 +950,14 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
nevra2string(build_yname(pkg.get('name'), inst)))
continue
status = self.instance_status[inst]
- if not status.get('installed', False) and self.do_install:
+ if (not status.get('installed', False) and
+ Bcfg2.Options.setup.yum_install_missing):
queue_pkg(pkg, inst, install_pkgs)
- elif status.get('version_fail', False) and self.do_upgrade:
+ elif (status.get('version_fail', False) and
+ Bcfg2.Options.setup.yum_fix_version):
queue_pkg(pkg, inst, upgrade_pkgs)
- elif status.get('verify_fail', False) and self.do_reinst:
+ elif (status.get('verify_fail', False) and
+ Bcfg2.Options.setup.yum_reinstall_broken):
queue_pkg(pkg, inst, reinstall_pkgs)
else:
# Either there was no Install/Version/Verify
@@ -1010,7 +1033,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
self._runYumTransaction()
- if not self.setup['kevlar']:
+ if not Bcfg2.Options.setup.kevlar:
for pkg_entry in [p for p in packages if self.canVerify(p)]:
self.logger.debug("Reverifying Failed Package %s" %
pkg_entry.get('name'))
diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py
index 885e22761..ce75005fe 100644
--- a/src/lib/Bcfg2/Client/Tools/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/__init__.py
@@ -4,11 +4,11 @@ import os
import sys
import stat
import logging
+import Bcfg2.Options
import Bcfg2.Client
import Bcfg2.Client.XML
from Bcfg2.Utils import Executor, ClassName
from Bcfg2.Compat import walk_packages # pylint: disable=W0622
-import Bcfg2.Options
__all__ = [m[1] for m in walk_packages(path=__path__)]
@@ -28,6 +28,12 @@ class Tool(object):
.. autoattribute:: Bcfg2.Client.Tools.Tool.__important__
"""
+ options = [
+ Bcfg2.Options.Option(
+ cf=('client', 'command_timeout'),
+ help="Timeout when running external commands other than probes",
+ type=Bcfg2.Options.Types.timeout)]
+
#: The name of the tool. By default this uses
#: :class:`Bcfg2.Client.Tools.ClassName` to ensure that it is the
#: same as the name of the class.
@@ -77,10 +83,6 @@ class Tool(object):
:type config: lxml.etree._Element
:raises: :exc:`Bcfg2.Client.Tools.ToolInstantiationError`
"""
- #: A :class:`Bcfg2.Options.OptionParser` object describing the
- #: option set Bcfg2 was invoked with
- self.setup = Bcfg2.Options.get_option_parser()
-
#: A :class:`logging.Logger` object that will be used by this
#: tool for logging
self.logger = logging.getLogger(self.name)
@@ -90,7 +92,7 @@ class Tool(object):
#: An :class:`Bcfg2.Utils.Executor` object for
#: running external commands.
- self.cmd = Executor(timeout=self.setup['command_timeout'])
+ self.cmd = Executor(timeout=Bcfg2.Options.setup.command_timeout)
#: A list of entries that have been modified by this tool
self.modified = []
@@ -136,7 +138,7 @@ class Tool(object):
:param bundle: The bundle that has been updated
:type bundle: lxml.etree._Element
:returns: dict - A dict of the state of entries suitable for
- updating :attr:`Bcfg2.Client.Frame.Frame.states`
+ updating :attr:`Bcfg2.Client.Client.states`
"""
return dict()
@@ -146,7 +148,7 @@ class Tool(object):
:param bundle: The bundle that has been updated
:type bundle: lxml.etree._Element
:returns: dict - A dict of the state of entries suitable for
- updating :attr:`Bcfg2.Client.Frame.Frame.states`
+ updating :attr:`Bcfg2.Client.Client.states`
"""
return dict()
@@ -172,7 +174,7 @@ class Tool(object):
be used.
:type structures: list of lxml.etree._Element
:returns: dict - A dict of the state of entries suitable for
- updating :attr:`Bcfg2.Client.Frame.Frame.states`
+ updating :attr:`Bcfg2.Client.Client.states`
"""
if not structures:
structures = self.config.getchildren()
@@ -210,7 +212,7 @@ class Tool(object):
:param entries: The entries to install
:type entries: list of lxml.etree._Element
:returns: dict - A dict of the state of entries suitable for
- updating :attr:`Bcfg2.Client.Frame.Frame.states`
+ updating :attr:`Bcfg2.Client.Client.states`
"""
states = dict()
for entry in entries:
@@ -435,7 +437,7 @@ class PkgTool(Tool):
:param entries: The entries to install
:type entries: list of lxml.etree._Element
:returns: dict - A dict of the state of entries suitable for
- updating :attr:`Bcfg2.Client.Frame.Frame.states`
+ updating :attr:`Bcfg2.Client.Client.states`
"""
self.logger.info("Trying single pass package install for pkgtype %s" %
self.pkgtype)
@@ -493,6 +495,12 @@ class PkgTool(Tool):
class SvcTool(Tool):
""" Base class for tools that handle Service entries """
+ options = Tool.options + [
+ Bcfg2.Options.Option(
+ '-s', '--service-mode', default='default',
+ choices=['default', 'disabled', 'build'],
+ help='Set client service mode')]
+
def __init__(self, config):
Tool.__init__(self, config)
#: List of services that have been restarted
@@ -571,14 +579,14 @@ class SvcTool(Tool):
return bool(self.cmd.run(self.get_svc_command(service, 'status')))
def Remove(self, services):
- if self.setup['servicemode'] != 'disabled':
+ if Bcfg2.Options.setup.service_mode != 'disabled':
for entry in services:
entry.set("status", "off")
self.InstallService(entry)
Remove.__doc__ = Tool.Remove.__doc__
def BundleUpdated(self, bundle):
- if self.setup['servicemode'] == 'disabled':
+ if Bcfg2.Options.setup.service_mode == 'disabled':
return
for entry in bundle:
@@ -587,15 +595,16 @@ class SvcTool(Tool):
restart = entry.get("restart", "true").lower()
if (restart == "false" or
- (restart == "interactive" and not self.setup['interactive'])):
+ (restart == "interactive" and
+ not Bcfg2.Options.setup.interactive)):
continue
success = False
if entry.get('status') == 'on':
- if self.setup['servicemode'] == 'build':
+ if Bcfg2.Options.setup.service_mode == 'build':
success = self.stop_service(entry)
elif entry.get('name') not in self.restarted:
- if self.setup['interactive']:
+ if Bcfg2.Options.setup.interactive:
if not Bcfg2.Client.prompt('Restart service %s? (y/N) '
% entry.get('name')):
continue
diff --git a/src/lib/Bcfg2/Client/__init__.py b/src/lib/Bcfg2/Client/__init__.py
index 6d1cb9d40..2761fcddb 100644
--- a/src/lib/Bcfg2/Client/__init__.py
+++ b/src/lib/Bcfg2/Client/__init__.py
@@ -2,8 +2,55 @@
import os
import sys
-import select
-from Bcfg2.Compat import input # pylint: disable=W0622
+import stat
+import time
+import fcntl
+import socket
+import fnmatch
+import logging
+import argparse
+import tempfile
+import Bcfg2.Logger
+import Bcfg2.Options
+import XML # pylint: disable=W0403
+import Proxy # pylint: disable=W0403
+import Tools # pylint: disable=W0403
+from Bcfg2.Utils import locked, Executor, safe_input
+from Bcfg2.version import __version__
+# pylint: disable=W0622
+from Bcfg2.Compat import xmlrpclib, walk_packages, any, all, cmp
+# pylint: enable=W0622
+
+
+def cmpent(ent1, ent2):
+ """Sort entries."""
+ if ent1.tag != ent2.tag:
+ return cmp(ent1.tag, ent2.tag)
+ else:
+ return cmp(ent1.get('name'), ent2.get('name'))
+
+
+def matches_entry(entryspec, entry):
+ """ Determine if the Decisions-style entry specification matches
+ the entry. Both are tuples of (tag, name). The entryspec can
+ handle the wildcard * in either position. """
+ if entryspec == entry:
+ return True
+ return all(fnmatch.fnmatch(entry[i], entryspec[i]) for i in [0, 1])
+
+
+def matches_white_list(entry, whitelist):
+ """ Return True if (<entry tag>, <entry name>) is in the given
+ whitelist. """
+ return any(matches_entry(we, (entry.tag, entry.get('name')))
+ for we in whitelist)
+
+
+def passes_black_list(entry, blacklist):
+ """ Return True if (<entry tag>, <entry name>) is not in the given
+ blacklist. """
+ return not any(matches_entry(be, (entry.tag, entry.get('name')))
+ for be in blacklist)
def prompt(msg):
@@ -16,10 +63,8 @@ def prompt(msg):
contain "[y/N]" if desired, etc.
:type msg: string
:returns: bool - True if yes, False if no """
- while len(select.select([sys.stdin.fileno()], [], [], 0.0)[0]) > 0:
- os.read(sys.stdin.fileno(), 4096)
try:
- ans = input(msg)
+ ans = safe_input(msg)
return ans in ['y', 'Y']
except UnicodeEncodeError:
ans = input(msg.encode('utf-8'))
@@ -27,3 +72,821 @@ def prompt(msg):
except EOFError:
# handle ^C on rhel-based platforms
raise SystemExit(1)
+ except:
+ print("Error while reading input: %s" % sys.exc_info()[1])
+ return False
+
+
+class ClientDriverAction(Bcfg2.Options.ComponentAction):
+ """ Action to load client drivers """
+ bases = ['Bcfg2.Client.Tools']
+ fail_silently = True
+
+
+class Client(object):
+ """ The main Bcfg2 client class """
+
+ options = Proxy.ComponentProxy.options + [
+ Bcfg2.Options.Common.syslog,
+ Bcfg2.Options.Common.location,
+ Bcfg2.Options.Common.interactive,
+ Bcfg2.Options.BooleanOption(
+ "-q", "--quick", help="Disable some checksum verification"),
+ Bcfg2.Options.Option(
+ cf=('client', 'probe_timeout'),
+ type=Bcfg2.Options.Types.timeout,
+ help="Timeout when running client probes"),
+ Bcfg2.Options.Option(
+ "-b", "--only-bundles", default=[],
+ type=Bcfg2.Options.Types.colon_list,
+ help='Only configure the given bundle(s)'),
+ Bcfg2.Options.Option(
+ "-B", "--except-bundles", default=[],
+ type=Bcfg2.Options.Types.colon_list,
+ help='Configure everything except the given bundle(s)'),
+ Bcfg2.Options.ExclusiveOptionGroup(
+ Bcfg2.Options.BooleanOption(
+ "-Q", "--bundle-quick",
+ help='Only verify the given bundle(s)'),
+ Bcfg2.Options.Option(
+ '-r', '--remove',
+ choices=['all', 'services', 'packages', 'users'],
+ help='Force removal of additional configuration items')),
+ Bcfg2.Options.ExclusiveOptionGroup(
+ Bcfg2.Options.PathOption(
+ '-f', '--file', type=argparse.FileType('r'),
+ help='Configure from a file rather than querying the server'),
+ Bcfg2.Options.PathOption(
+ '-c', '--cache', type=argparse.FileType('w'),
+ help='Store the configuration in a file')),
+ Bcfg2.Options.BooleanOption(
+ '--exit-on-probe-failure', default=True,
+ cf=('client', 'exit_on_probe_failure'),
+ help="The client should exit if a probe fails"),
+ Bcfg2.Options.Option(
+ '-p', '--profile', cf=('client', 'profile'),
+ help='Assert the given profile for the host'),
+ Bcfg2.Options.Option(
+ '-l', '--decision', cf=('client', 'decision'),
+ choices=['whitelist', 'blacklist', 'none'],
+ help='Run client in server decision list mode'),
+ Bcfg2.Options.BooleanOption(
+ "-O", "--no-lock", help='Omit lock check'),
+ Bcfg2.Options.PathOption(
+ cf=('components', 'lockfile'), default='/var/lock/bcfg2.run',
+ help='Client lock file'),
+ Bcfg2.Options.BooleanOption(
+ "-n", "--dry-run", help='Do not actually change the system'),
+ Bcfg2.Options.Option(
+ "-D", "--drivers", cf=('client', 'drivers'),
+ type=Bcfg2.Options.Types.comma_list,
+ default=[m[1] for m in walk_packages(path=Tools.__path__)],
+ action=ClientDriverAction, help='Client drivers'),
+ Bcfg2.Options.BooleanOption(
+ "-e", "--show-extra", help='Enable extra entry output'),
+ Bcfg2.Options.BooleanOption(
+ "-k", "--kevlar", help='Run in bulletproof mode')]
+
+ def __init__(self):
+ self.config = None
+ self._proxy = None
+ self.logger = logging.getLogger('bcfg2')
+ self.cmd = Executor(Bcfg2.Options.setup.probe_timeout)
+ self.tools = []
+ self.times = dict()
+ self.times['initialization'] = time.time()
+
+ if Bcfg2.Options.setup.bundle_quick:
+ if (not Bcfg2.Options.setup.only_bundles and
+ not Bcfg2.Options.setup.except_bundles):
+ self.logger.error("-Q option requires -b or -B")
+ raise SystemExit(1)
+ if Bcfg2.Options.setup.remove == 'services':
+ self.logger.error("Service removal is nonsensical; "
+ "removed services will only be disabled")
+ if not Bcfg2.Options.setup.server.startswith('https://'):
+ Bcfg2.Options.setup.server = \
+ 'https://' + Bcfg2.Options.setup.server
+
+ #: A dict of the state of each entry. Keys are the entries.
+ #: Values are boolean: True means that the entry is good,
+ #: False means that the entry is bad.
+ self.states = {}
+ self.whitelist = []
+ self.blacklist = []
+ self.removal = []
+ self.unhandled = []
+ self.logger = logging.getLogger(__name__)
+
+ def _probe_failure(self, probename, msg):
+ """ handle failure of a probe in the way the user wants us to
+ (exit or continue) """
+ message = "Failed to execute probe %s: %s" % (probename, msg)
+ if Bcfg2.Options.setup.exit_on_probe_failure:
+ self.fatal_error(message)
+ else:
+ self.logger.error(message)
+
+ def run_probe(self, probe):
+ """Execute probe."""
+ name = probe.get('name')
+ self.logger.info("Running probe %s" % name)
+ ret = XML.Element("probe-data", name=name, source=probe.get('source'))
+ try:
+ scripthandle, scriptname = tempfile.mkstemp()
+ script = os.fdopen(scripthandle, 'w')
+ try:
+ script.write("#!%s\n" %
+ (probe.attrib.get('interpreter', '/bin/sh')))
+ if sys.hexversion >= 0x03000000:
+ script.write(probe.text)
+ else:
+ script.write(probe.text.encode('utf-8'))
+ script.close()
+ os.chmod(scriptname,
+ stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
+ stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
+ stat.S_IWUSR) # 0755
+ rv = self.cmd.run(scriptname)
+ if rv.stderr:
+ self.logger.warning("Probe %s has error output: %s" %
+ (name, rv.stderr))
+ if not rv.success:
+ self._probe_failure(name, "Return value %s" % rv.retval)
+ self.logger.info("Probe %s has result:" % name)
+ self.logger.info(rv.stdout)
+ if sys.hexversion >= 0x03000000:
+ ret.text = rv.stdout
+ else:
+ ret.text = rv.stdout.decode('utf-8')
+ finally:
+ os.unlink(scriptname)
+ except SystemExit:
+ raise
+ except:
+ self._probe_failure(name, sys.exc_info()[1])
+ return ret
+
+ def fatal_error(self, message):
+ """Signal a fatal error."""
+ self.logger.error("Fatal error: %s" % (message))
+ raise SystemExit(1)
+
+ @property
+ def proxy(self):
+ """ get an XML-RPC proxy to the server """
+ if self._proxy is None:
+ self._proxy = Proxy.ComponentProxy()
+ return self._proxy
+
+ def run_probes(self):
+ """ run probes and upload probe data """
+ try:
+ probes = XML.XML(str(self.proxy.GetProbes()))
+ except (Proxy.ProxyError,
+ Proxy.CertificateError,
+ socket.gaierror,
+ socket.error):
+ err = sys.exc_info()[1]
+ self.fatal_error("Failed to download probes from bcfg2: %s" % err)
+ except XML.ParseError:
+ err = sys.exc_info()[1]
+ self.fatal_error("Server returned invalid probe requests: %s" %
+ err)
+
+ self.times['probe_download'] = time.time()
+
+ # execute probes
+ probedata = XML.Element("ProbeData")
+ for probe in probes.findall(".//probe"):
+ probedata.append(self.run_probe(probe))
+
+ if len(probes.findall(".//probe")) > 0:
+ try:
+ # upload probe responses
+ self.proxy.RecvProbeData(
+ XML.tostring(probedata,
+ xml_declaration=False).decode('utf-8'))
+ except Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ self.fatal_error("Failed to upload probe data: %s" % err)
+
+ self.times['probe_upload'] = time.time()
+
+ def get_config(self):
+ """ load the configuration, either from the cached
+ configuration file (-f), or from the server """
+ if Bcfg2.Options.setup.file:
+ # read config from file
+ try:
+ self.logger.debug("Reading cached configuration from %s" %
+ Bcfg2.Options.setup.file.name)
+ return Bcfg2.Options.setup.file.read()
+ except IOError:
+ self.fatal_error("Failed to read cached configuration from: %s"
+ % Bcfg2.Options.setup.file.name)
+ else:
+ # retrieve config from server
+ if Bcfg2.Options.setup.profile:
+ try:
+ self.proxy.AssertProfile(Bcfg2.Options.setup.profile)
+ except Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ self.fatal_error("Failed to set client profile: %s" % err)
+
+ try:
+ self.proxy.DeclareVersion(__version__)
+ except xmlrpclib.Fault:
+ err = sys.exc_info()[1]
+ if (err.faultCode == xmlrpclib.METHOD_NOT_FOUND or
+ (err.faultCode == 7 and
+ err.faultString.startswith("Unknown method"))):
+ self.logger.debug("Server does not support declaring "
+ "client version")
+ else:
+ self.logger.error("Failed to declare version: %s" % err)
+ except (Proxy.ProxyError,
+ Proxy.CertificateError,
+ socket.gaierror,
+ socket.error):
+ err = sys.exc_info()[1]
+ self.logger.error("Failed to declare version: %s" % err)
+
+ self.run_probes()
+
+ if Bcfg2.Options.setup.decision in ['whitelist', 'blacklist']:
+ try:
+ # TODO: read decision list from --decision-list
+ Bcfg2.Options.setup.decision_list = \
+ self.proxy.GetDecisionList(
+ Bcfg2.Options.setup.decision)
+ self.logger.info("Got decision list from server:")
+ self.logger.info(Bcfg2.Options.setup.decision_list)
+ except Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ self.fatal_error("Failed to get decision list: %s" % err)
+
+ try:
+ rawconfig = self.proxy.GetConfig().encode('utf-8')
+ except Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ self.fatal_error("Failed to download configuration from "
+ "Bcfg2: %s" % err)
+
+ self.times['config_download'] = time.time()
+
+ if Bcfg2.Options.setup.cache:
+ try:
+ Bcfg2.Options.setup.cache.write(rawconfig)
+ os.chmod(Bcfg2.Options.setup.cache, 384) # 0600
+ except IOError:
+ self.logger.warning("Failed to write config cache file %s" %
+ (Bcfg2.Options.setup.cache))
+ self.times['caching'] = time.time()
+
+ return rawconfig
+
+ def parse_config(self, rawconfig):
+ """ Parse the XML configuration received from the Bcfg2 server """
+ try:
+ self.config = XML.XML(rawconfig)
+ except XML.ParseError:
+ syntax_error = sys.exc_info()[1]
+ self.fatal_error("The configuration could not be parsed: %s" %
+ syntax_error)
+
+ self.load_tools()
+
+ # find entries not handled by any tools
+ self.unhandled = [entry for struct in self.config
+ for entry in struct
+ if entry not in self.handled]
+
+ if self.unhandled:
+ self.logger.error("The following entries are not handled by any "
+ "tool:")
+ for entry in self.unhandled:
+ self.logger.error("%s:%s:%s" % (entry.tag, entry.get('type'),
+ entry.get('name')))
+
+ # find duplicates
+ self.find_dups(self.config)
+
+ pkgs = [(entry.get('name'), entry.get('origin'))
+ for struct in self.config
+ for entry in struct
+ if entry.tag == 'Package']
+ if pkgs:
+ self.logger.debug("The following packages are specified in bcfg2:")
+ self.logger.debug([pkg[0] for pkg in pkgs if pkg[1] is None])
+ self.logger.debug("The following packages are prereqs added by "
+ "Packages:")
+ self.logger.debug([pkg[0] for pkg in pkgs if pkg[1] == 'Packages'])
+
+ self.times['config_parse'] = time.time()
+
+ def run(self):
+ """Perform client execution phase."""
+ # begin configuration
+ self.times['start'] = time.time()
+
+ self.logger.info("Starting Bcfg2 client run at %s" %
+ self.times['start'])
+
+ self.parse_config(self.get_config().decode('utf-8'))
+
+ if self.config.tag == 'error':
+ self.fatal_error("Server error: %s" % (self.config.text))
+
+ if Bcfg2.Options.setup.bundle_quick:
+ newconfig = XML.XML('<Configuration/>')
+ for bundle in self.config.getchildren():
+ name = bundle.get("name")
+ if (name and (name in Bcfg2.Options.setup.only_bundles or
+ name not in Bcfg2.Options.setup.except_bundles)):
+ newconfig.append(bundle)
+ self.config = newconfig
+
+ if not Bcfg2.Options.setup.no_lock:
+ #check lock here
+ try:
+ lockfile = open(Bcfg2.Options.setup.lockfile, 'w')
+ if locked(lockfile.fileno()):
+ self.fatal_error("Another instance of Bcfg2 is running. "
+ "If you want to bypass the check, run "
+ "with the -O/--no-lock option")
+ except SystemExit:
+ raise
+ except:
+ lockfile = None
+ self.logger.error("Failed to open lockfile %s: %s" %
+ (Bcfg2.Options.setup.lockfile,
+ sys.exc_info()[1]))
+
+ # execute the configuration
+ self.Execute()
+
+ if not Bcfg2.Options.setup.no_lock:
+ # unlock here
+ if lockfile:
+ try:
+ fcntl.lockf(lockfile.fileno(), fcntl.LOCK_UN)
+ os.remove(Bcfg2.Options.setup.lockfile)
+ except OSError:
+ self.logger.error("Failed to unlock lockfile %s" %
+ lockfile.name)
+
+ if (not Bcfg2.Options.setup.file and
+ not Bcfg2.Options.setup.bundle_quick):
+ # upload statistics
+ feedback = self.GenerateStats()
+
+ try:
+ self.proxy.RecvStats(
+ XML.tostring(feedback,
+ xml_declaration=False).decode('utf-8'))
+ except Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ self.logger.error("Failed to upload configuration statistics: "
+ "%s" % err)
+ raise SystemExit(2)
+
+ self.logger.info("Finished Bcfg2 client run at %s" % time.time())
+
+ def load_tools(self):
+ """ Load all applicable client tools """
+ for tool in Bcfg2.Options.setup.drivers:
+ try:
+ self.tools.append(tool(self.config))
+ except Tools.ToolInstantiationError:
+ continue
+ except:
+ self.logger.error("Failed to instantiate tool %s" % tool,
+ exc_info=1)
+
+ for tool in self.tools[:]:
+ for conflict in getattr(tool, 'conflicts', []):
+ for item in self.tools:
+ if item.name == conflict:
+ self.tools.remove(item)
+
+ self.logger.info("Loaded tool drivers:")
+ self.logger.info([tool.name for tool in self.tools])
+
+ deprecated = [tool.name for tool in self.tools if tool.deprecated]
+ if deprecated:
+ self.logger.warning("Loaded deprecated tool drivers:")
+ self.logger.warning(deprecated)
+ experimental = [tool.name for tool in self.tools if tool.experimental]
+ if experimental:
+ self.logger.warning("Loaded experimental tool drivers:")
+ self.logger.warning(experimental)
+
+ def find_dups(self, config):
+ """ Find duplicate entries and warn about them """
+ entries = dict()
+ for struct in config:
+ for entry in struct:
+ for tool in self.tools:
+ if tool.handlesEntry(entry):
+ pkey = tool.primarykey(entry)
+ if pkey in entries:
+ entries[pkey] += 1
+ else:
+ entries[pkey] = 1
+ multi = [e for e, c in entries.items() if c > 1]
+ if multi:
+ self.logger.debug("The following entries are included multiple "
+ "times:")
+ for entry in multi:
+ self.logger.debug(entry)
+
+ def promptFilter(self, msg, entries):
+ """Filter a supplied list based on user input."""
+ ret = []
+ entries.sort(key=lambda e: e.tag + ":" + e.get('name'))
+ for entry in entries[:]:
+ if entry in self.unhandled:
+ # don't prompt for entries that can't be installed
+ continue
+ if 'qtext' in entry.attrib:
+ iprompt = entry.get('qtext')
+ else:
+ iprompt = msg % (entry.tag, entry.get('name'))
+ if prompt(iprompt):
+ ret.append(entry)
+ return ret
+
+ def __getattr__(self, name):
+ if name in ['extra', 'handled', 'modified', '__important__']:
+ ret = []
+ for tool in self.tools:
+ ret += getattr(tool, name)
+ return ret
+ elif name in self.__dict__:
+ return self.__dict__[name]
+ raise AttributeError(name)
+
+ def InstallImportant(self):
+ """Install important entries
+
+ We also process the decision mode stuff here because we want to prevent
+ non-whitelisted/blacklisted 'important' entries from being installed
+ prior to determining the decision mode on the client.
+ """
+ # Need to process decision stuff early so that dryrun mode
+ # works with it
+ self.whitelist = [entry for entry in self.states
+ if not self.states[entry]]
+ if not Bcfg2.Options.setup.file:
+ if Bcfg2.Options.setup.decision == 'whitelist':
+ dwl = Bcfg2.Options.setup.decision_list
+ w_to_rem = [e for e in self.whitelist
+ if not matches_white_list(e, dwl)]
+ if w_to_rem:
+ self.logger.info("In whitelist mode: "
+ "suppressing installation of:")
+ self.logger.info(["%s:%s" % (e.tag, e.get('name'))
+ for e in w_to_rem])
+ self.whitelist = [x for x in self.whitelist
+ if x not in w_to_rem]
+ elif Bcfg2.Options.setup.decision == 'blacklist':
+ b_to_rem = \
+ [e for e in self.whitelist
+ if not
+ passes_black_list(e, Bcfg2.Options.setup.decision_list)]
+ if b_to_rem:
+ self.logger.info("In blacklist mode: "
+ "suppressing installation of:")
+ self.logger.info(["%s:%s" % (e.tag, e.get('name'))
+ for e in b_to_rem])
+ self.whitelist = [x for x in self.whitelist
+ if x not in b_to_rem]
+
+ # take care of important entries first
+ if not Bcfg2.Options.setup.dry_run:
+ for parent in self.config.findall(".//Path/.."):
+ name = parent.get("name")
+ if (name and (name in Bcfg2.Options.setup.only_bundles or
+ name not in Bcfg2.Options.setup.except_bundles)):
+ continue
+ for cfile in parent.findall("./Path"):
+ if (cfile.get('name') not in self.__important__ or
+ cfile.get('type') != 'file' or
+ cfile not in self.whitelist):
+ continue
+ tools = [t for t in self.tools
+ if t.handlesEntry(cfile) and t.canVerify(cfile)]
+ if not tools:
+ continue
+ if (Bcfg2.Options.setup.interactive and not
+ self.promptFilter("Install %s: %s? (y/N):", [cfile])):
+ self.whitelist.remove(cfile)
+ continue
+ try:
+ self.states[cfile] = tools[0].InstallPath(cfile)
+ if self.states[cfile]:
+ tools[0].modified.append(cfile)
+ except: # pylint: disable=W0702
+ self.logger.error("Unexpected tool failure",
+ exc_info=1)
+ cfile.set('qtext', '')
+ if tools[0].VerifyPath(cfile, []):
+ self.whitelist.remove(cfile)
+
+ 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:
+ self.states.update(tool.Inventory())
+ except: # pylint: disable=W0702
+ self.logger.error("%s.Inventory() call failed:" % tool.name,
+ exc_info=1)
+
+ def Decide(self): # pylint: disable=R0912
+ """Set self.whitelist based on user interaction."""
+ iprompt = "Install %s: %s? (y/N): "
+ rprompt = "Remove %s: %s? (y/N): "
+ if Bcfg2.Options.setup.remove:
+ if Bcfg2.Options.setup.remove == 'all':
+ self.removal = self.extra
+ elif Bcfg2.Options.setup.remove.lower() == 'services':
+ self.removal = [entry for entry in self.extra
+ if entry.tag == 'Service']
+ elif Bcfg2.Options.setup.remove.lower() == 'packages':
+ self.removal = [entry for entry in self.extra
+ if entry.tag == 'Package']
+ elif Bcfg2.Options.setup.remove.lower() == 'users':
+ self.removal = [entry for entry in self.extra
+ if entry.tag in ['POSIXUser', 'POSIXGroup']]
+
+ candidates = [entry for entry in self.states
+ if not self.states[entry]]
+
+ if Bcfg2.Options.setup.dry_run:
+ if self.whitelist:
+ self.logger.info("In dryrun mode: "
+ "suppressing entry installation for:")
+ self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
+ for entry in self.whitelist])
+ self.whitelist = []
+ if self.removal:
+ self.logger.info("In dryrun mode: "
+ "suppressing entry removal for:")
+ self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
+ for entry in self.removal])
+ self.removal = []
+
+ # Here is where most of the work goes
+ # first perform bundle filtering
+ all_bundle_names = [b.get('name')
+ for b in self.config.findall('./Bundle')]
+ bundles = self.config.getchildren()
+ if Bcfg2.Options.setup.only_bundles:
+ # warn if non-existent bundle given
+ for bundle in Bcfg2.Options.setup.only_bundles:
+ if bundle not in all_bundle_names:
+ self.logger.info("Warning: Bundle %s not found" % bundle)
+ bundles = [b for b in bundles
+ if b.get('name') in Bcfg2.Options.setup.only_bundles]
+ if Bcfg2.Options.setup.except_bundles:
+ # warn if non-existent bundle given
+ if not Bcfg2.Options.setup.bundle_quick:
+ for bundle in Bcfg2.Options.setup.except_bundles:
+ if bundle not in all_bundle_names:
+ self.logger.info("Warning: Bundle %s not found" %
+ bundle)
+ bundles = [
+ b for b in bundles
+ if b.get('name') not in Bcfg2.Options.setup.except_bundles]
+ self.whitelist = [e for e in self.whitelist
+ if any(e in b for b in bundles)]
+
+ # first process prereq actions
+ for bundle in bundles[:]:
+ if bundle.tag == 'Bundle':
+ bmodified = any(item in self.whitelist for item in bundle)
+ else:
+ bmodified = False
+ actions = [a for a in bundle.findall('./Action')
+ if (a.get('timing') in ['pre', 'both'] and
+ (bmodified or a.get('when') == 'always'))]
+ # now we process all "always actions"
+ if Bcfg2.Options.setup.interactive:
+ self.promptFilter(iprompt, actions)
+ self.DispatchInstallCalls(actions)
+
+ if bundle.tag != 'Bundle':
+ continue
+
+ # need to test to fail entries in whitelist
+ if not all(self.states[a] for a in actions):
+ # then display bundles forced off with entries
+ self.logger.info("%s %s failed prerequisite action" %
+ (bundle.tag, bundle.get('name')))
+ bundles.remove(bundle)
+ b_to_remv = [ent for ent in self.whitelist if ent in bundle]
+ if b_to_remv:
+ self.logger.info("Not installing entries from %s %s" %
+ (bundle.tag, bundle.get('name')))
+ self.logger.info(["%s:%s" % (e.tag, e.get('name'))
+ for e in b_to_remv])
+ for ent in b_to_remv:
+ self.whitelist.remove(ent)
+
+ self.logger.debug("Installing entries in the following bundle(s):")
+ self.logger.debug(" %s" % ", ".join(b.get("name") for b in bundles
+ if b.get("name")))
+
+ if Bcfg2.Options.setup.interactive:
+ self.whitelist = self.promptFilter(iprompt, self.whitelist)
+ self.removal = self.promptFilter(rprompt, self.removal)
+
+ for entry in candidates:
+ if entry not in self.whitelist:
+ self.blacklist.append(entry)
+
+ 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:
+ self.states.update(tool.Install(handled))
+ except: # pylint: disable=W0702
+ self.logger.error("%s.Install() call failed:" % tool.name,
+ exc_info=1)
+
+ def Install(self):
+ """Install all entries."""
+ self.DispatchInstallCalls(self.whitelist)
+ mods = self.modified
+ mbundles = [struct for struct in self.config.findall('Bundle')
+ if any(True for mod in mods if mod in struct)]
+
+ if self.modified:
+ # Handle Bundle interdeps
+ if mbundles:
+ self.logger.info("The Following Bundles have been modified:")
+ self.logger.info([mbun.get('name') for mbun in mbundles])
+ tbm = [(t, b) for t in self.tools for b in mbundles]
+ for tool, bundle in tbm:
+ try:
+ self.states.update(tool.Inventory(structures=[bundle]))
+ except: # pylint: disable=W0702
+ 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] and
+ entry not in self.blacklist)]
+ if clobbered:
+ self.logger.debug("Found clobbered entries:")
+ self.logger.debug(["%s:%s" % (entry.tag, entry.get('name'))
+ for entry in clobbered])
+ if not Bcfg2.Options.setup.interactive:
+ self.DispatchInstallCalls(clobbered)
+
+ for bundle in self.config.findall('.//Bundle'):
+ if (Bcfg2.Options.setup.only_bundles and
+ bundle.get('name') not in Bcfg2.Options.setup.only_bundles):
+ # prune out unspecified bundles when running with -b
+ continue
+ if bundle in mbundles:
+ self.logger.debug("Bundle %s was modified" %
+ bundle.get('name'))
+ func = "BundleUpdated"
+ else:
+ self.logger.debug("Bundle %s was not modified" %
+ bundle.get('name'))
+ func = "BundleNotUpdated"
+ for tool in self.tools:
+ try:
+ self.states.update(getattr(tool, func)(bundle))
+ except: # pylint: disable=W0702
+ self.logger.error("%s.%s(%s:%s) call failed:" %
+ (tool.name, func, bundle.tag,
+ bundle.get("name")), exc_info=1)
+
+ for indep in self.config.findall('.//Independent'):
+ for tool in self.tools:
+ try:
+ self.states.update(tool.BundleNotUpdated(indep))
+ except: # pylint: disable=W0702
+ self.logger.error("%s.BundleNotUpdated(%s:%s) call failed:"
+ % (tool.name, indep.tag,
+ indep.get("name")), exc_info=1)
+
+ def Remove(self):
+ """Remove extra entries."""
+ for tool in self.tools:
+ extras = [entry for entry in self.removal
+ if tool.handlesEntry(entry)]
+ if extras:
+ try:
+ tool.Remove(extras)
+ except: # pylint: disable=W0702
+ self.logger.error("%s.Remove() failed" % tool.name,
+ exc_info=1)
+
+ def CondDisplayState(self, phase):
+ """Conditionally print tracing information."""
+ self.logger.info('Phase: %s' % phase)
+ self.logger.info('Correct entries: %d' %
+ list(self.states.values()).count(True))
+ self.logger.info('Incorrect entries: %d' %
+ list(self.states.values()).count(False))
+ if phase == 'final' and list(self.states.values()).count(False):
+ for entry in sorted(self.states.keys(), key=lambda e: e.tag + ":" +
+ e.get('name')):
+ if not self.states[entry]:
+ etype = entry.get('type')
+ if etype:
+ self.logger.info("%s:%s:%s" % (entry.tag, etype,
+ entry.get('name')))
+ else:
+ self.logger.info("%s:%s" % (entry.tag,
+ entry.get('name')))
+ self.logger.info('Total managed entries: %d' %
+ len(list(self.states.values())))
+ self.logger.info('Unmanaged entries: %d' % len(self.extra))
+ if phase == 'final' and Bcfg2.Options.setup.show_extra:
+ for entry in sorted(self.extra,
+ key=lambda e: e.tag + ":" + e.get('name')):
+ etype = entry.get('type')
+ if etype:
+ self.logger.info("%s:%s:%s" % (entry.tag, etype,
+ entry.get('name')))
+ else:
+ self.logger.info("%s:%s" % (entry.tag,
+ entry.get('name')))
+
+ if ((list(self.states.values()).count(False) == 0) and not self.extra):
+ self.logger.info('All entries correct.')
+
+ def ReInventory(self):
+ """Recheck everything."""
+ if not Bcfg2.Options.setup.dry_run and Bcfg2.Options.setup.kevlar:
+ self.logger.info("Rechecking system inventory")
+ self.Inventory()
+
+ def Execute(self):
+ """Run all methods."""
+ self.Inventory()
+ self.times['inventory'] = time.time()
+ self.CondDisplayState('initial')
+ self.InstallImportant()
+ self.Decide()
+ self.Install()
+ self.times['install'] = time.time()
+ self.Remove()
+ self.times['remove'] = time.time()
+ if self.modified:
+ self.ReInventory()
+ self.times['reinventory'] = time.time()
+ self.times['finished'] = time.time()
+ self.CondDisplayState('final')
+
+ def GenerateStats(self):
+ """Generate XML summary of execution statistics."""
+ feedback = XML.Element("upload-statistics")
+ stats = XML.SubElement(feedback,
+ 'Statistics', total=str(len(self.states)),
+ version='2.0',
+ revision=self.config.get('revision', '-1'))
+ good_entries = [key for key, val in list(self.states.items()) if val]
+ good = len(good_entries)
+ stats.set('good', str(good))
+ if any(not val for val in list(self.states.values())):
+ stats.set('state', 'dirty')
+ else:
+ stats.set('state', 'clean')
+
+ # List bad elements of the configuration
+ for (data, ename) in [(self.modified, 'Modified'),
+ (self.extra, "Extra"),
+ (good_entries, "Good"),
+ ([entry for entry in self.states
+ if not self.states[entry]], "Bad")]:
+ container = XML.SubElement(stats, ename)
+ for item in data:
+ item.set('qtext', '')
+ container.append(item)
+ item.text = None
+
+ timeinfo = XML.Element("OpStamps")
+ feedback.append(stats)
+ for (event, timestamp) in list(self.times.items()):
+ timeinfo.set(event, str(timestamp))
+ stats.append(timeinfo)
+ return feedback