summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-17 11:21:27 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-17 11:21:41 -0400
commit7c3368f78e7c5042932a4fb58cea4b2ac3130358 (patch)
tree39ea7d392fe118b91df702cce6bf0a0e3963c11e
parent6683f2e8d48b1cbf8e4c1ad096c6b0c98eefe527 (diff)
downloadbcfg2-7c3368f78e7c5042932a4fb58cea4b2ac3130358.tar.gz
bcfg2-7c3368f78e7c5042932a4fb58cea4b2ac3130358.tar.bz2
bcfg2-7c3368f78e7c5042932a4fb58cea4b2ac3130358.zip
added bcfg2_local.py, a tool to run bcfg2 against a local specification
-rw-r--r--src/lib/Bcfg2/Client/Client.py327
-rw-r--r--src/lib/Bcfg2/Client/__init__.py2
-rwxr-xr-xsrc/sbin/bcfg2344
-rwxr-xr-xsrc/sbin/bcfg2-info2
-rw-r--r--tools/README4
-rwxr-xr-xtools/bcfg2_local.py77
6 files changed, 422 insertions, 334 deletions
diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py
new file mode 100644
index 000000000..ed1333fb8
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Client.py
@@ -0,0 +1,327 @@
+import os
+import sys
+import stat
+import time
+import fcntl
+import socket
+import logging
+import tempfile
+import Bcfg2.Proxy
+import Bcfg2.Logger
+import Bcfg2.Options
+import Bcfg2.Client.XML
+import Bcfg2.Client.Frame
+import Bcfg2.Client.Tools
+from Bcfg2.Compat import xmlrpclib
+from Bcfg2.version import __version__
+from subprocess import Popen, PIPE
+
+
+class Client(object):
+ """The main bcfg2 client class"""
+
+ def __init__(self, setup):
+ self.toolset = None
+ self.tools = None
+ self.config = None
+ self._proxy = None
+ self.setup = setup
+
+ 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)
+ 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.drivers)
+ 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']):
+ 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')))
+ script.write(probe.text)
+ 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
+ proc = Popen(scriptname, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ ret.text, err = proc.communicate()
+ rv = proc.wait()
+ if err:
+ self.logger.warning("Probe %s has error output: %s" %
+ (name, err))
+ if rv:
+ self._probe_failure(name, "Return value %s" % rv)
+ self.logger.info("Probe %s has result:" % name)
+ self.logger.info(ret.text)
+ finally:
+ os.unlink(scriptname)
+ except: # pylint: disable=W0702
+ 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.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.Proxy.ProxyError,
+ Bcfg2.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.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.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.Proxy.ProxyError,
+ Bcfg2.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.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.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)
+
+ 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))
+ return(1)
+
+ 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,
+ self.setup,
+ times, self.setup['drivers'],
+ self.setup['dryrun'])
+
+ if not self.setup['omit_lock_check']:
+ #check lock here
+ try:
+ lockfile = open(self.setup['lockfile'], 'w')
+ try:
+ fcntl.lockf(lockfile.fileno(),
+ fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except IOError:
+ # otherwise exit and give a warning to the user
+ self.fatal_error("An other instance of Bcfg2 is running. "
+ "If you what to bypass the check, run "
+ "with %s option" %
+ Bcfg2.Options.OMIT_LOCK_CHECK.cmd)
+ except: # pylint: disable=W0702
+ lockfile = None
+ self.logger.error("Failed to open lockfile")
+ # execute the said 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.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/__init__.py b/src/lib/Bcfg2/Client/__init__.py
index 6ed37b257..c03021f14 100644
--- a/src/lib/Bcfg2/Client/__init__.py
+++ b/src/lib/Bcfg2/Client/__init__.py
@@ -1,3 +1,3 @@
"""This contains all Bcfg2 Client modules"""
-__all__ = ["Frame", "Tools", "XML"]
+__all__ = ["Frame", "Tools", "XML", "Client"]
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2
index f7c36cff2..91d09b959 100755
--- a/src/sbin/bcfg2
+++ b/src/sbin/bcfg2
@@ -1,25 +1,10 @@
#!/usr/bin/env python
-
"""Bcfg2 Client"""
-import fcntl
-import logging
-import os
-import signal
-import socket
-import stat
import sys
-import tempfile
-import time
-import Bcfg2.Proxy
-import Bcfg2.Logger
+import signal
import Bcfg2.Options
-import Bcfg2.Client.XML
-import Bcfg2.Client.Frame
-import Bcfg2.Client.Tools
-from Bcfg2.Compat import xmlrpclib
-from Bcfg2.version import __version__
-from subprocess import Popen, PIPE
+from Bcfg2.Client import Client
def cb_sigint_handler(signum, frame):
@@ -27,323 +12,18 @@ def cb_sigint_handler(signum, frame):
raise SystemExit(1)
-class Client(object):
- """The main bcfg2 client class"""
-
- def __init__(self):
- self.toolset = None
- self.tools = None
- self.config = None
- self._proxy = None
-
- optinfo = Bcfg2.Options.CLIENT_COMMON_OPTIONS
- self.setup = Bcfg2.Options.OptionParser(optinfo)
- self.setup.parse(sys.argv[1:])
-
- if self.setup['args']:
- print("Bcfg2 takes no arguments, only options")
- print(self.setup.buildHelpMessage())
- raise SystemExit(1)
- 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)
- 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.drivers)
- 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']):
- 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 main():
+ optinfo = Bcfg2.Options.CLIENT_COMMON_OPTIONS
+ setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.parse(sys.argv[1:])
- 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')))
- script.write(probe.text)
- 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
- proc = Popen(scriptname, stdin=PIPE, stdout=PIPE, stderr=PIPE)
- ret.text, err = proc.communicate()
- rv = proc.wait()
- if err:
- self.logger.warning("Probe %s has error output: %s" %
- (name, err))
- if rv:
- self._probe_failure(name, "Return value %s" % rv)
- self.logger.info("Probe %s has result:" % name)
- self.logger.info(ret.text)
- finally:
- os.unlink(scriptname)
- except: # pylint: disable=W0702
- 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))
+ if setup['args']:
+ print("Bcfg2 takes no arguments, only options")
+ print(setup.buildHelpMessage())
raise SystemExit(1)
- @property
- def proxy(self):
- """ get an XML-RPC proxy to the server """
- if self._proxy is None:
- self._proxy = Bcfg2.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.Proxy.ProxyError,
- Bcfg2.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.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.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.Proxy.ProxyError,
- Bcfg2.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.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.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)
-
- 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))
- return(1)
-
- 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,
- self.setup,
- times, self.setup['drivers'],
- self.setup['dryrun'])
-
- if not self.setup['omit_lock_check']:
- #check lock here
- try:
- lockfile = open(self.setup['lockfile'], 'w')
- try:
- fcntl.lockf(lockfile.fileno(),
- fcntl.LOCK_EX | fcntl.LOCK_NB)
- except IOError:
- # otherwise exit and give a warning to the user
- self.fatal_error("An other instance of Bcfg2 is running. "
- "If you what to bypass the check, run "
- "with %s option" %
- Bcfg2.Options.OMIT_LOCK_CHECK.cmd)
- except: # pylint: disable=W0702
- lockfile = None
- self.logger.error("Failed to open lockfile")
- # execute the said 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.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())
-
+ signal.signal(signal.SIGINT, cb_sigint_handler)
+ return Client(setup).run()
if __name__ == '__main__':
- signal.signal(signal.SIGINT, cb_sigint_handler)
- Client().run()
+ sys.exit(main())
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index d6d529dec..4c5d8f785 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -733,7 +733,7 @@ Bcfg2 client itself.""")
def _run(self):
pass
-
+
def _block(self):
pass
diff --git a/tools/README b/tools/README
index 7cae4409d..5b1fc0baf 100644
--- a/tools/README
+++ b/tools/README
@@ -20,6 +20,10 @@ bcfg2-import-config
- Create tarball of changed files on a client for import into the
specification
+bcfg2_local.py
+ - Perform a full Bcfg2 run against a local repository instead of
+ against a remote server
+
bcfg2-profile-templates.py [<template>]
- Benchmark template rendering times
diff --git a/tools/bcfg2_local.py b/tools/bcfg2_local.py
new file mode 100755
index 000000000..3ff623505
--- /dev/null
+++ b/tools/bcfg2_local.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+""" This tool performs a full Bcfg2 run entirely against a local
+repository, i.e., without a server. It starts up a local instance of
+the server core, then uses that to get probes, run them, and so on."""
+
+import sys
+import socket
+import Bcfg2.Options
+from Bcfg2.Client.Client import Client
+from Bcfg2.Server.Core import BaseCore
+
+
+class LocalCore(BaseCore):
+ """ Local server core similar to the one started by bcfg2-info """
+
+ def __init__(self, setup):
+ saved = (setup['syslog'], setup['logging'])
+ setup['syslog'] = False
+ setup['logging'] = None
+ Bcfg2.Server.Core.BaseCore.__init__(self, setup=setup)
+ setup['syslog'], setup['logging'] = saved
+ self.fam.handle_events_in_interval(4)
+
+ def _daemonize(self):
+ pass
+
+ def _run(self):
+ pass
+
+ def _block(self):
+ pass
+
+
+class LocalProxy(object):
+ """ A local proxy (as opposed to XML-RPC) that proxies from the
+ Client object to the LocalCore object, adding a client address
+ pair to the argument list of each proxied call """
+
+ def __init__(self, core):
+ self.core = core
+ self.hostname = socket.gethostname()
+ self.ipaddr = socket.gethostbyname(self.hostname)
+
+ def __getattr__(self, attr):
+ if hasattr(self.core, attr):
+ func = getattr(self.core, attr)
+ if func.exposed:
+ def inner(*args, **kwargs):
+ args = ((self.ipaddr, self.hostname), ) + args
+ return func(*args, **kwargs)
+ return inner
+ raise AttributeError(attr)
+
+
+class LocalClient(Client):
+ """ A version of the Client class that uses LocalProxy instead of
+ an XML-RPC proxy to make its calls """
+
+ def __init__(self, setup, proxy):
+ Client.__init__(self, setup)
+ self._proxy = proxy
+
+
+def main():
+ optinfo = Bcfg2.Options.CLIENT_COMMON_OPTIONS
+ optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS)
+ setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.parse(sys.argv[1:])
+
+ core = LocalCore(setup)
+ try:
+ LocalClient(setup, LocalProxy(core)).run()
+ finally:
+ core.shutdown()
+
+if __name__ == '__main__':
+ sys.exit(main())