"""Option parsing library for utilities.""" __revision__ = '$Revision$' import getopt import os import sys import shlex import Bcfg2.Client.Tools # Compatibility imports from Bcfg2.Bcfg2Py3k import ConfigParser def bool_cook(x): if x: return True else: return False class OptionFailure(Exception): pass DEFAULT_CONFIG_LOCATION = '/etc/bcfg2.conf' #/etc/bcfg2.conf DEFAULT_INSTALL_PREFIX = '/usr' #/usr class Option(object): cfpath = DEFAULT_CONFIG_LOCATION __cfp = False def getCFP(self): if not self.__cfp: self.__cfp = ConfigParser.ConfigParser() self.__cfp.readfp(open(self.cfpath)) return self.__cfp cfp = property(getCFP) def get_cooked_value(self, value): if self.boolean: return True if self.cook: return self.cook(value) else: return value def __init__(self, desc, default, cmd=False, odesc=False, env=False, cf=False, cook=False, long_arg=False): self.desc = desc self.default = default self.cmd = cmd self.long = long_arg if not self.long: if cmd and (cmd[0] != '-' or len(cmd) != 2): raise OptionFailure("Poorly formed command %s" % cmd) else: if cmd and (not cmd.startswith('--')): raise OptionFailure("Poorly formed command %s" % cmd) self.odesc = odesc self.env = env self.cf = cf self.boolean = False if not odesc and not cook: self.boolean = True self.cook = cook def buildHelpMessage(self): msg = '' if self.cmd: if not self.long: msg = self.cmd.ljust(3) else: msg = self.cmd if self.odesc: if self.long: msg = "%-28s" % ("%s=%s" % (self.cmd, self.odesc)) else: msg += '%-25s' % (self.odesc) else: msg += '%-25s' % ('') msg += "%s\n" % self.desc return msg def buildGetopt(self): gstr = '' if self.long: return gstr if self.cmd: gstr = self.cmd[1] if self.odesc: gstr += ':' return gstr def buildLongGetopt(self): if self.odesc: return self.cmd[2:]+'=' else: return self.cmd[2:] def parse(self, opts, rawopts): if self.cmd and opts: # Processing getopted data optinfo = [opt[1] for opt in opts if opt[0] == self.cmd] if optinfo: if optinfo[0]: self.value = self.get_cooked_value(optinfo[0]) else: self.value = True return if self.cmd and self.cmd in rawopts: data = rawopts[rawopts.index(self.cmd) + 1] self.value = self.get_cooked_value(data) return # No command line option found if self.env and self.env in os.environ: self.value = self.get_cooked_value(os.environ[self.env]) return if self.cf: # FIXME: This is potentially masking a lot of errors try: self.value = self.get_cooked_value(self.cfp.get(*self.cf)) return except: pass # Default value not cooked self.value = self.default class OptionSet(dict): def __init__(self, *args): dict.__init__(self, *args) self.hm = self.buildHelpMessage() def buildGetopt(self): return ''.join([opt.buildGetopt() for opt in list(self.values())]) def buildLongGetopt(self): return [opt.buildLongGetopt() for opt in list(self.values()) if opt.long] def buildHelpMessage(self): if hasattr(self, 'hm'): return self.hm return ' '.join([opt.buildHelpMessage() for opt in list(self.values())]) def helpExit(self, msg='', code=1): if msg: print(msg) print("Usage:\n %s" % self.buildHelpMessage()) raise SystemExit(code) def parse(self, argv, do_getopt=True): '''Parse options from command line.''' if do_getopt: try: opts, args = getopt.getopt(argv, self.buildGetopt(), self.buildLongGetopt()) except getopt.GetoptError: err = sys.exc_info()[1] self.helpExit(err) if '-h' in argv: self.helpExit('', 0) self['args'] = args for key in list(self.keys()): if key == 'args': continue option = self[key] if do_getopt: option.parse(opts, []) else: option.parse([], argv) if hasattr(option, 'value'): val = option.value self[key] = val list_split = lambda x:x.replace(' ','').split(',') flist_split = lambda x:list_split(x.replace(':', '').lower()) def colon_split(c_string): if c_string: return c_string.split(':') return [] def get_bool(s): # these values copied from ConfigParser.RawConfigParser.getboolean # with the addition of True and False truelist = ["1", "yes", "True", "true", "on"] falselist = ["0", "no", "False", "false", "off"] if s in truelist: return True elif s in falselist: return False else: raise ValueError # General options CFILE = Option('Specify configuration file', DEFAULT_CONFIG_LOCATION, cmd='-C', odesc='') LOCKFILE = Option('Specify lockfile', "/var/lock/bcfg2.run", cf=('components', 'lockfile'), odesc='') HELP = Option('Print this usage message', False, cmd='-h') DEBUG = Option("Enable debugging output", False, cmd='-d') VERBOSE = Option("Enable verbose output", False, cmd='-v') DAEMON = Option("Daemonize process, storing pid", False, cmd='-D', odesc="") INSTALL_PREFIX = Option('Installation location', cf=('server', 'prefix'), default=DEFAULT_INSTALL_PREFIX, odesc='') SENDMAIL_PATH = Option('Path to sendmail', cf=('reports', 'sendmailpath'), default='/usr/lib/sendmail') INTERACTIVE = Option('Run interactively, prompting the user for each change', default=False, cmd='-I', ) ENCODING = Option('Encoding of cfg files', default='UTF-8', cmd='-E', odesc='', cf=('components', 'encoding')) PARANOID_PATH = Option('Specify path for paranoid file backups', default='/var/cache/bcfg2', cf=('paranoid', 'path'), odesc='') PARANOID_MAX_COPIES = Option('Specify the number of paranoid copies you want', default=1, cf=('paranoid', 'max_copies'), odesc='') OMIT_LOCK_CHECK = Option('Omit lock check', default=False, cmd='-O') CORE_PROFILE = Option('profile', default=False, cmd='-p', ) FILES_ON_STDIN = Option('Operate on a list of files supplied on stdin', cmd='--stdin', default=False, long_arg=True) SCHEMA_PATH = Option('Path to XML Schema files', cmd='--schema', odesc='', default="%s/share/bcfg2/schemas" % DEFAULT_INSTALL_PREFIX, long_arg=True) REQUIRE_SCHEMA = Option("Require property files to have matching schema files", cmd="--require-schema", default=False, long_arg=True) # Metadata options MDATA_OWNER = Option('Default Path owner', default='root', cf=('mdata', 'owner'), odesc='owner permissions') MDATA_GROUP = Option('Default Path group', default='root', cf=('mdata', 'group'), odesc='group permissions') MDATA_IMPORTANT = Option('Default Path priority (importance)', default='False', cf=('mdata', 'important'), odesc='Important entries are installed first') MDATA_PERMS = Option('Default Path permissions', '644', cf=('mdata', 'perms'), odesc='octal permissions') MDATA_PARANOID = Option('Default Path paranoid setting', 'false', cf=('mdata', 'paranoid'), odesc='Path paranoid setting') MDATA_SENSITIVE = Option('Default Path sensitive setting', 'false', cf=('mdata', 'sensitive'), odesc='Path sensitive setting') # Server options SERVER_REPOSITORY = Option('Server repository path', '/var/lib/bcfg2', cf=('server', 'repository'), cmd='-Q', odesc='') SERVER_PLUGINS = Option('Server plugin list', cf=('server', 'plugins'), # default server plugins default=[ 'Bundler', 'Cfg', 'Metadata', 'Pkgmgr', 'Rules', 'SSHbase', ], cook=list_split) SERVER_MCONNECT = Option('Server Metadata Connector list', cook=list_split, cf=('server', 'connectors'), default=['Probes'], ) SERVER_FILEMONITOR = Option('Server file monitor', cf=('server', 'filemonitor'), default='default', odesc='File monitoring driver') SERVER_LISTEN_ALL = Option('Listen on all interfaces', cf=('server', 'listen_all'), cmd='--listen-all', default=False, long_arg=True, cook=get_bool, odesc='True|False') SERVER_LOCATION = Option('Server Location', cf=('components', 'bcfg2'), default='https://localhost:6789', cmd='-S', odesc='https://server:port') SERVER_STATIC = Option('Server runs on static port', cf=('components', 'bcfg2'), default=False, cook=bool_cook) SERVER_KEY = Option('Path to SSL key', cf=('communication', 'key'), default=False, cmd='--ssl-key', odesc='', long_arg=True) SERVER_CERT = Option('Path to SSL certificate', default='/etc/bcfg2.key', cf=('communication', 'certificate'), odesc='') SERVER_CA = Option('Path to SSL CA Cert', default=None, cf=('communication', 'ca'), odesc='') SERVER_PASSWORD = Option('Communication Password', cmd='-x', odesc='', cf=('communication', 'password'), default=False) SERVER_PROTOCOL = Option('Server Protocol', cf=('communication', 'procotol'), default='xmlrpc/ssl') # Client options CLIENT_KEY = Option('Path to SSL key', cf=('communication', 'key'), default=None, cmd="--ssl-key", odesc='', long_arg=True) CLIENT_CERT = Option('Path to SSL certificate', default=None, cmd="--ssl-cert", cf=('communication', 'certificate'), odesc='', long_arg=True) CLIENT_CA = Option('Path to SSL CA Cert', default=None, cmd="--ca-cert", cf=('communication', 'ca'), odesc='', long_arg=True) CLIENT_SCNS = Option('List of server commonNames', default=None, cmd="--ssl-cns", cf=('communication', 'serverCommonNames'), odesc='', cook=list_split, long_arg=True) CLIENT_PROFILE = Option('Assert the given profile for the host', default=False, cmd='-p', odesc="") CLIENT_RETRIES = Option('The number of times to retry network communication', default='3', cmd='-R', cf=('communication', 'retries'), odesc="") CLIENT_DRYRUN = Option('Do not actually change the system', default=False, cmd='-n', ) CLIENT_EXTRA_DISPLAY = Option('enable extra entry output', default=False, cmd='-e', ) CLIENT_PARANOID = Option('Make automatic backups of config files', default=False, cmd='-P', cook=get_bool, cf=('client', 'paranoid')) CLIENT_DRIVERS = Option('Specify tool driver set', cmd='-D', cf=('client', 'drivers'), odesc="", cook=list_split, default=Bcfg2.Client.Tools.default) CLIENT_CACHE = Option('Store the configuration in a file', default=False, cmd='-c', odesc="") CLIENT_REMOVE = Option('Force removal of additional configuration items', default=False, cmd='-r', odesc="") CLIENT_BUNDLE = Option('Only configure the given bundle(s)', default=[], cmd='-b', odesc='', cook=colon_split) CLIENT_BUNDLEQUICK = Option('only verify/configure the given bundle(s)', default=False, cmd='-Q') CLIENT_INDEP = Option('Only configure independent entries, ignore bundles', default=False, cmd='-z') CLIENT_KEVLAR = Option('Run in kevlar (bulletproof) mode', default=False, cmd='-k', ) CLIENT_DLIST = Option('Run client in server decision list mode', default='none', cf=('client', 'decision'), cmd='-l', odesc='') CLIENT_FILE = Option('Configure from a file rather than querying the server', default=False, cmd='-f', odesc='') CLIENT_QUICK = Option('Disable some checksum verification', default=False, cmd='-q', ) CLIENT_USER = Option('The user to provide for authentication', default='root', cmd='-u', cf=('communication', 'user'), odesc='') CLIENT_SERVICE_MODE = Option('Set client service mode', default='default', cmd='-s', odesc='') CLIENT_TIMEOUT = Option('Set the client XML-RPC timeout', default=90, cmd='-t', cf=('communication', 'timeout'), odesc='') # bcfg2-test options TEST_NOSEOPTS = Option('Options to pass to nosetests', default=[], cmd='--nose-options', cf=('bcfg2_test', 'nose_options'), odesc='', long_arg=True, cook=shlex.split) TEST_IGNORE = Option('Ignore these entries if they fail to build.', default=[], cmd='--ignore', cf=('bcfg2_test', 'ignore_entries'), long_arg=True, odesc=':,:', cook=list_split) # APT client tool options CLIENT_APT_TOOLS_INSTALL_PATH = Option('Apt tools install path', cf=('APT', 'install_path'), default='/usr') CLIENT_APT_TOOLS_VAR_PATH = Option('Apt tools var path', cf=('APT', 'var_path'), default='/var') CLIENT_SYSTEM_ETC_PATH = Option('System etc path', cf=('APT', 'etc_path'), default='/etc') # Logging options LOGGING_FILE_PATH = Option('Set path of file log', default=None, cmd='-o', odesc='', cf=('logging', 'path')) class OptionParser(OptionSet): """ OptionParser bootstraps option parsing, getting the value of the config file """ def __init__(self, args): self.Bootstrap = OptionSet([('configfile', CFILE)]) self.Bootstrap.parse(sys.argv[1:], do_getopt=False) if self.Bootstrap['configfile'] != Option.cfpath: Option.cfpath = self.Bootstrap['configfile'] Option.__cfp = False OptionSet.__init__(self, args) try: f = open(Option.cfpath, 'r') f.close() except IOError: e = sys.exc_info()[1] print("Warning! Unable to read specified configuration file: %s" % e)