mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Cleanup the salt
(salt.cli.SaltCMD
) binary parser.
* Created 2 mix-ins as option groups, the output options and the target options. This will allow adding some explanatory text besides separating these options from the parser's main options. * All options on the parser, including the grouped options are now merged to the loaded configuration which will latter get passed on. * Also created the timeout mix-in which will be used in other binaries.
This commit is contained in:
parent
503f4bdf08
commit
b582e40ff1
5 changed files with 395 additions and 312 deletions
|
@ -15,310 +15,73 @@ import salt.client
|
|||
import salt.output
|
||||
import salt.runner
|
||||
|
||||
#from salt.utils import parsers as optparse
|
||||
import optparse
|
||||
from salt.utils import parsers
|
||||
from salt.utils.verify import verify_env
|
||||
from salt.version import __version__ as VERSION
|
||||
from salt.exceptions import SaltInvocationError, SaltClientError, \
|
||||
SaltException
|
||||
from salt.exceptions import SaltInvocationError, SaltClientError, SaltException
|
||||
|
||||
|
||||
class SaltCMD(object):
|
||||
class SaltCMD(parsers.SaltCMDOptionParser):
|
||||
'''
|
||||
The execution of a salt command happens here
|
||||
'''
|
||||
def __init__(self):
|
||||
'''
|
||||
Create a SaltCMD object
|
||||
'''
|
||||
self.opts = self.__parse()
|
||||
|
||||
def __parse(self):
|
||||
'''
|
||||
Parse the command line
|
||||
'''
|
||||
usage = "%prog [options] '<target>' <function> [arguments]"
|
||||
parser = optparse.OptionParser(version="%%prog %s" % VERSION, usage=usage)
|
||||
|
||||
parser.add_option('-t',
|
||||
'--timeout',
|
||||
default=None,
|
||||
dest='timeout',
|
||||
help=('Set the return timeout for batch jobs; '
|
||||
'default=5 seconds'))
|
||||
parser.add_option('-s',
|
||||
'--static',
|
||||
default=False,
|
||||
dest='static',
|
||||
action='store_true',
|
||||
help=('Return the data from minions as a group after they '
|
||||
'all return.'))
|
||||
parser.add_option('-v',
|
||||
'--verbose',
|
||||
default=False,
|
||||
dest='verbose',
|
||||
action='store_true',
|
||||
help=('Turn on command verbosity, display jid and active job '
|
||||
'queries'))
|
||||
parser.add_option('-b',
|
||||
'--batch',
|
||||
'--batch-size',
|
||||
default='',
|
||||
dest='batch',
|
||||
help=('Execute the salt job in batch mode, pass either the '
|
||||
'number of minions to batch at a time, or the '
|
||||
'percentage of minions to have running'))
|
||||
parser.add_option('-E',
|
||||
'--pcre',
|
||||
default=False,
|
||||
dest='pcre',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'servers, use pcre regular expressions'))
|
||||
parser.add_option('-L',
|
||||
'--list',
|
||||
default=False,
|
||||
dest='list',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'servers, take a comma delimited list of servers.'))
|
||||
parser.add_option('-G',
|
||||
'--grain',
|
||||
default=False,
|
||||
dest='grain',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a grain value to identify targets, the syntax '
|
||||
'for the target is the grain key followed by a glob'
|
||||
'expression:\n"os:Arch*"'))
|
||||
parser.add_option('--grain-pcre',
|
||||
default=False,
|
||||
dest='grain_pcre',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a grain value to identify targets, the syntax '
|
||||
'for the target is the grain key followed by a pcre '
|
||||
'regular expression:\n"os:Arch.*"'))
|
||||
parser.add_option('-X',
|
||||
'--exsel',
|
||||
default=False,
|
||||
dest='exsel',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs use the return code '
|
||||
'of a function.'))
|
||||
parser.add_option('-I',
|
||||
'--pillar',
|
||||
default=False,
|
||||
dest='pillar',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a pillar value to identify targets, the syntax '
|
||||
'for the target is the pillar key followed by a glob'
|
||||
'expression:\n"role:production*"'))
|
||||
parser.add_option('-N',
|
||||
'--nodegroup',
|
||||
default=False,
|
||||
dest='nodegroup',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use one of the predefined nodegroups to identify a '
|
||||
'list of targets.'))
|
||||
parser.add_option('-R',
|
||||
'--range',
|
||||
default=False,
|
||||
dest='range',
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a range expression to identify targets. '
|
||||
'Range expressions look like %cluster'))
|
||||
parser.add_option('-C',
|
||||
'--compound',
|
||||
default=False,
|
||||
dest='compound',
|
||||
action='store_true',
|
||||
help=('The compound target option allows for multiple '
|
||||
'target types to be evaluated, allowing for greater '
|
||||
'granularity in target matching. The compound target '
|
||||
'is space delimited, targets other than globs are '
|
||||
'preceted with an identifyer matching the specific '
|
||||
'targets argument type: salt \'G@os:RedHat and '
|
||||
'webser* or E@database.*\''))
|
||||
parser.add_option('--return',
|
||||
default='',
|
||||
dest='return',
|
||||
metavar='RETURNER',
|
||||
help=('Set an alternative return method. By default salt will '
|
||||
'send the return data from the command back to the '
|
||||
'master, but the return data can be redirected into '
|
||||
'any number of systems, databases or applications.'))
|
||||
parser.add_option('-Q',
|
||||
'--query',
|
||||
dest='query',
|
||||
action='store_true',
|
||||
help=('This option is deprecated and will be removed in a '
|
||||
'future release, please use salt-run jobs instead\n'
|
||||
'Execute a salt command query, this can be used to find '
|
||||
'the results of a previous function call: -Q test.echo'))
|
||||
parser.add_option('-c',
|
||||
'--config',
|
||||
default='/etc/salt/master',
|
||||
dest='conf_file',
|
||||
help=('The location of the salt master configuration file, '
|
||||
'the salt master settings are required to know where '
|
||||
'the connections are; default=/etc/salt/master'))
|
||||
parser.add_option('--raw-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='raw_out',
|
||||
help=('Print the output from the salt command in raw python '
|
||||
'form, this is suitable for re-reading the output into '
|
||||
'an executing python script with eval.'))
|
||||
parser.add_option('--text-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='txt_out',
|
||||
help=('Print the output from the salt command in the same '
|
||||
'form the shell would.'))
|
||||
parser.add_option('--yaml-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='yaml_out',
|
||||
help='Print the output from the salt command in yaml.')
|
||||
parser.add_option('--json-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='json_out',
|
||||
help='Print the output from the salt command in json.')
|
||||
parser.add_option('--no-color',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='no_color',
|
||||
help='Disable all colored output')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
opts = {}
|
||||
|
||||
for k, v in options.__dict__.items():
|
||||
if v is not None:
|
||||
opts[k] = v
|
||||
|
||||
if not options.timeout is None:
|
||||
opts['timeout'] = int(options.timeout)
|
||||
|
||||
if options.query:
|
||||
opts['query'] = options.query
|
||||
if len(args) < 1:
|
||||
err = ('Please pass in a command to query the old salt '
|
||||
'calls for.')
|
||||
sys.stderr.write(err + '\n')
|
||||
sys.exit('2')
|
||||
opts['cmd'] = args[0]
|
||||
else:
|
||||
# Catch invalid invocations of salt such as: salt run
|
||||
if len(args) <= 1:
|
||||
parser.print_help()
|
||||
parser.exit(1)
|
||||
|
||||
if opts['list']:
|
||||
opts['tgt'] = args[0].split(',')
|
||||
else:
|
||||
opts['tgt'] = args[0]
|
||||
|
||||
# Detect compound command and set up the data for it
|
||||
if ',' in args[1]:
|
||||
opts['fun'] = args[1].split(',')
|
||||
opts['arg'] = []
|
||||
for comp in ' '.join(args[2:]).split(','):
|
||||
opts['arg'].append(comp.split())
|
||||
if len(opts['fun']) != len(opts['arg']):
|
||||
err = ('Cannot execute compound command without defining '
|
||||
'all arguments.')
|
||||
sys.stderr.write(err + '\n')
|
||||
sys.exit(42)
|
||||
else:
|
||||
opts['fun'] = args[1]
|
||||
opts['arg'] = args[2:]
|
||||
|
||||
return opts
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Execute the salt command line
|
||||
'''
|
||||
self.parse_args()
|
||||
|
||||
try:
|
||||
local = salt.client.LocalClient(self.opts['conf_file'])
|
||||
local = salt.client.LocalClient(self.get_config_file_path('master'))
|
||||
except SaltClientError as exc:
|
||||
sys.stderr.write('{0}\n'.format(exc))
|
||||
sys.exit(2)
|
||||
self.exit(2, '{0}\n'.format(exc))
|
||||
return
|
||||
|
||||
if 'query' in self.opts:
|
||||
ret = local.find_cmd(self.opts['cmd'])
|
||||
if self.options.query:
|
||||
ret = local.find_cmd(self.config['cmd'])
|
||||
for jid in ret:
|
||||
if isinstance(ret, list) or isinstance(ret, dict):
|
||||
# Determine the proper output method and run it
|
||||
get_outputter = salt.output.get_outputter
|
||||
if self.opts['raw_out']:
|
||||
printout = get_outputter('raw')
|
||||
elif self.opts['json_out']:
|
||||
printout = get_outputter('json')
|
||||
elif self.opts['txt_out']:
|
||||
printout = get_outputter('txt')
|
||||
elif self.opts['yaml_out']:
|
||||
printout = get_outputter('yaml')
|
||||
else:
|
||||
printout = get_outputter(None)
|
||||
printout = self.get_outputter()
|
||||
|
||||
print('Return data for job {0}:'.format(jid))
|
||||
printout(ret[jid])
|
||||
print('')
|
||||
elif self.opts['batch']:
|
||||
batch = salt.cli.batch.Batch(self.opts)
|
||||
elif self.options.batch:
|
||||
batch = salt.cli.batch.Batch(self.config)
|
||||
batch.run()
|
||||
else:
|
||||
if not 'timeout' in self.opts:
|
||||
self.opts['timeout'] = local.opts['timeout']
|
||||
args = [self.opts['tgt'],
|
||||
self.opts['fun'],
|
||||
self.opts['arg'],
|
||||
self.opts['timeout'],
|
||||
]
|
||||
if self.opts['pcre']:
|
||||
args.append('pcre')
|
||||
elif self.opts['list']:
|
||||
args.append('list')
|
||||
elif self.opts['grain']:
|
||||
args.append('grain')
|
||||
elif self.opts['grain_pcre']:
|
||||
args.append('grain_pcre')
|
||||
elif self.opts['exsel']:
|
||||
args.append('exsel')
|
||||
elif self.opts['pillar']:
|
||||
args.append('pillar')
|
||||
elif self.opts['nodegroup']:
|
||||
args.append('nodegroup')
|
||||
elif self.opts['range']:
|
||||
args.append('range')
|
||||
elif self.opts['compound']:
|
||||
args.append('compound')
|
||||
if self.options.timeout <= 0:
|
||||
self.options.timeout = local.opts['timeout']
|
||||
|
||||
args = [
|
||||
self.config['tgt'],
|
||||
self.config['fun'],
|
||||
self.config['arg'],
|
||||
self.options.timeout,
|
||||
]
|
||||
|
||||
if self.selected_target_option:
|
||||
args.append(self.selected_target_option)
|
||||
else:
|
||||
args.append('glob')
|
||||
|
||||
if self.opts['return']:
|
||||
args.append(self.opts['return'])
|
||||
if getattr(self.options, 'return'):
|
||||
args.append(getattr(self.options, 'return'))
|
||||
else:
|
||||
args.append('')
|
||||
try:
|
||||
# local will be None when there was an error
|
||||
if local:
|
||||
if self.opts['static']:
|
||||
if self.opts['verbose']:
|
||||
if self.options.static:
|
||||
if self.options.verbose:
|
||||
args.append(True)
|
||||
full_ret = local.cmd_full_return(*args)
|
||||
ret, out = self._format_ret(full_ret)
|
||||
self._output_ret(ret, out)
|
||||
elif self.opts['fun'] == 'sys.doc':
|
||||
elif self.config['fun'] == 'sys.doc':
|
||||
ret = {}
|
||||
out = ''
|
||||
for full_ret in local.cmd_cli(*args):
|
||||
|
@ -326,7 +89,7 @@ class SaltCMD(object):
|
|||
ret.update(ret_)
|
||||
self._output_ret(ret, out)
|
||||
else:
|
||||
if self.opts['verbose']:
|
||||
if self.options.verbose:
|
||||
args.append(True)
|
||||
for full_ret in local.cmd_cli(*args):
|
||||
ret, out = self._format_ret(full_ret)
|
||||
|
@ -340,29 +103,11 @@ class SaltCMD(object):
|
|||
Print the output from a single return to the terminal
|
||||
'''
|
||||
# Handle special case commands
|
||||
if self.opts['fun'] == 'sys.doc':
|
||||
if self.config['fun'] == 'sys.doc':
|
||||
self._print_docs(ret)
|
||||
else:
|
||||
# Determine the proper output method and run it
|
||||
get_outputter = salt.output.get_outputter
|
||||
if isinstance(ret, list) or isinstance(ret, dict):
|
||||
if self.opts['raw_out']:
|
||||
printout = get_outputter('raw')
|
||||
elif self.opts['json_out']:
|
||||
printout = get_outputter('json')
|
||||
elif self.opts['txt_out']:
|
||||
printout = get_outputter('txt')
|
||||
elif self.opts['yaml_out']:
|
||||
printout = get_outputter('yaml')
|
||||
elif out:
|
||||
printout = get_outputter(out)
|
||||
else:
|
||||
printout = get_outputter(None)
|
||||
# Pretty print any salt exceptions
|
||||
elif isinstance(ret, SaltException):
|
||||
printout = get_outputter("txt")
|
||||
color = not bool(self.opts['no_color'])
|
||||
printout(ret, color=color)
|
||||
salt.output.display_output(ret, out, self.config)
|
||||
|
||||
def _format_ret(self, full_ret):
|
||||
'''
|
||||
|
@ -382,7 +127,8 @@ class SaltCMD(object):
|
|||
'''
|
||||
docs = {}
|
||||
if not ret:
|
||||
sys.stderr.write('No minions found to gather docs from\n')
|
||||
self.exit(2, 'No minions found to gather docs from\n')
|
||||
|
||||
for host in ret:
|
||||
for fun in ret[host]:
|
||||
if fun not in docs:
|
||||
|
|
|
@ -21,32 +21,37 @@ import salt.utils
|
|||
from salt._compat import string_types
|
||||
from salt.exceptions import SaltException
|
||||
|
||||
__all__ = ('get_outputter',)
|
||||
__all__ = ('get_outputter', 'get_printout')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def get_printout(ret, out, opts):
|
||||
"""
|
||||
Return the proper printout
|
||||
"""
|
||||
if isinstance(ret, list) or isinstance(ret, dict):
|
||||
if opts['raw_out']:
|
||||
return get_outputter('raw')
|
||||
elif opts['json_out']:
|
||||
return get_outputter('json')
|
||||
elif opts.get('txt_out', False):
|
||||
return get_outputter('txt')
|
||||
elif opts['yaml_out']:
|
||||
return get_outputter('yaml')
|
||||
elif out:
|
||||
return get_outputter(out)
|
||||
else:
|
||||
return get_outputter(None)
|
||||
# Pretty print any salt exceptions
|
||||
elif isinstance(ret, SaltException):
|
||||
return get_outputter("txt")
|
||||
|
||||
def display_output(ret, out, opts):
|
||||
'''
|
||||
Display the output of a command in the terminal
|
||||
'''
|
||||
if isinstance(ret, list) or isinstance(ret, dict):
|
||||
if opts['raw_out']:
|
||||
printout = get_outputter('raw')
|
||||
elif opts['json_out']:
|
||||
printout = get_outputter('json')
|
||||
elif opts['txt_out']:
|
||||
printout = get_outputter('txt')
|
||||
elif opts['yaml_out']:
|
||||
printout = get_outputter('yaml')
|
||||
elif out:
|
||||
printout = get_outputter(out)
|
||||
else:
|
||||
printout = get_outputter(None)
|
||||
# Pretty print any salt exceptions
|
||||
elif isinstance(ret, SaltException):
|
||||
printout = get_outputter("txt")
|
||||
printout(ret)
|
||||
printout = get_printout(ret, out, opts)
|
||||
printout(ret, color=not bool(opts['no_color']))
|
||||
|
||||
|
||||
class Outputter(object):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
salt.utils.parser
|
||||
~~~~~~~~~~~~~~~~~
|
||||
salt.utils.parsers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: © 2012 UfSoft.org - :email:`Pedro Algarvio (pedro@algarvio.me)`
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
@ -10,8 +10,10 @@
|
|||
import os
|
||||
import sys
|
||||
import optparse
|
||||
from functools import partial
|
||||
from salt import config, log, version
|
||||
|
||||
|
||||
def _sorted(mixins_or_funcs):
|
||||
return sorted(
|
||||
mixins_or_funcs, key=lambda mf: getattr(mf, '_mixin_prio_', 1000)
|
||||
|
@ -106,7 +108,12 @@ class OptionParser(optparse.OptionParser):
|
|||
process_option_funcs.append(process_option_func)
|
||||
|
||||
for process_option_func in _sorted(process_option_funcs):
|
||||
process_option_func()
|
||||
try:
|
||||
process_option_func()
|
||||
except Exception, err:
|
||||
self.error("Error while processing {0}: {1}".format(
|
||||
process_option_func, err
|
||||
))
|
||||
|
||||
# Run the functions on self._mixin_after_parsed_funcs
|
||||
for mixin_after_parsed_func in self._mixin_after_parsed_funcs:
|
||||
|
@ -155,6 +162,7 @@ class ConfigDirMixIn(DeprecatedConfigMessage):
|
|||
)
|
||||
|
||||
def __merge_config_with_cli(self, *args):
|
||||
# Merge parser options
|
||||
for option in self.option_list:
|
||||
if not option.dest:
|
||||
# --version does not have dest attribute set for example.
|
||||
|
@ -163,9 +171,18 @@ class ConfigDirMixIn(DeprecatedConfigMessage):
|
|||
continue
|
||||
|
||||
value = getattr(self.options, option.dest, None)
|
||||
if value:
|
||||
if value is not None:
|
||||
self.config[option.dest] = value
|
||||
|
||||
# Merge parser group options if any
|
||||
for group in self.option_groups:
|
||||
for option in group.option_list:
|
||||
if not option.dest:
|
||||
continue
|
||||
value = getattr(self.options, option.dest, None)
|
||||
if value is not None:
|
||||
self.config[option.dest] = value
|
||||
|
||||
def process_config_dir(self):
|
||||
# XXX: Remove deprecation warning in next release
|
||||
if os.path.isfile(self.options.config_dir):
|
||||
|
@ -289,6 +306,231 @@ class PidfileMixin(object):
|
|||
set_pidfile(self.config['pidfile'])
|
||||
|
||||
|
||||
class TargetOptionsMixIn(object):
|
||||
|
||||
__metaclass__ = MixInMeta
|
||||
_mixin_prio_ = 20
|
||||
|
||||
selected_target_option = None
|
||||
|
||||
def _mixin_setup(self):
|
||||
group = self.target_options_group = optparse.OptionGroup(
|
||||
self, "Target Options", "Target Selection Options"
|
||||
)
|
||||
self.add_option_group(group)
|
||||
group.add_option(
|
||||
'-E', '--pcre',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'servers, use pcre regular expressions')
|
||||
)
|
||||
group.add_option(
|
||||
'-L', '--list',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'servers, take a comma delimited list of servers.')
|
||||
)
|
||||
group.add_option(
|
||||
'-G', '--grain',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a grain value to identify targets, the syntax '
|
||||
'for the target is the grain key followed by a glob'
|
||||
'expression:\n"os:Arch*"')
|
||||
)
|
||||
group.add_option(
|
||||
'--grain-pcre',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a grain value to identify targets, the syntax '
|
||||
'for the target is the grain key followed by a pcre '
|
||||
'regular expression:\n"os:Arch.*"')
|
||||
)
|
||||
group.add_option(
|
||||
'-N', '--nodegroup',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use one of the predefined nodegroups to identify a '
|
||||
'list of targets.')
|
||||
)
|
||||
group.add_option(
|
||||
'-R', '--range',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a range expression to identify targets. '
|
||||
'Range expressions look like %cluster')
|
||||
)
|
||||
|
||||
self._create_process_functions()
|
||||
|
||||
def _create_process_functions(self):
|
||||
for option in self.target_options_group.option_list:
|
||||
def process(opt):
|
||||
if getattr(self.options, opt.dest):
|
||||
self.selected_target_option = opt.dest
|
||||
|
||||
funcname = 'process_%s' % option.dest
|
||||
if not hasattr(self, funcname):
|
||||
setattr(self, funcname, partial(process, option))
|
||||
|
||||
def _mixin_after_parsed(self):
|
||||
group_options_selected = filter(
|
||||
lambda option: getattr(self.options, option.dest) is True,
|
||||
self.target_options_group.option_list
|
||||
)
|
||||
if len(group_options_selected) > 1:
|
||||
self.error(
|
||||
"The options {0} are mutually exclusive. Please only choose "
|
||||
"one of them".format('/'.join([
|
||||
option.get_opt_string() for option in group_options_selected
|
||||
]))
|
||||
)
|
||||
|
||||
|
||||
class ExtendedTargetOptionsMixIn(TargetOptionsMixIn):
|
||||
def _mixin_setup(self):
|
||||
TargetOptionsMixIn._mixin_setup(self)
|
||||
group = self.target_options_group
|
||||
group.add_option(
|
||||
'-C', '--compound',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('The compound target option allows for multiple target types '
|
||||
'to be evaluated, allowing for greater granularity in target '
|
||||
'matching. The compound target is space delimited, targets '
|
||||
'other than globs are preceded with an identifier matching '
|
||||
'the specific targets argument type: salt \'G@os:RedHat and '
|
||||
'webser* or E@database.*\'')
|
||||
)
|
||||
group.add_option(
|
||||
'-X', '--exsel',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs use the return code of '
|
||||
'a function.')
|
||||
)
|
||||
group.add_option(
|
||||
'-I', '--pillar',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Instead of using shell globs to evaluate the target '
|
||||
'use a pillar value to identify targets, the syntax '
|
||||
'for the target is the pillar key followed by a glob'
|
||||
'expression:\n"role:production*"')
|
||||
)
|
||||
|
||||
self._create_process_functions()
|
||||
|
||||
|
||||
class TimeoutMixIn(object):
|
||||
__metaclass__ = MixInMeta
|
||||
_mixin_prio_ = 10
|
||||
|
||||
def _mixin_setup(self):
|
||||
if not hasattr(self, 'default_timeout'):
|
||||
raise RuntimeError("You need to define the 'default_timeout' "
|
||||
"attribute on %s" % self.__class__.__name__)
|
||||
self.add_option(
|
||||
'-t', '--timeout',
|
||||
type=int,
|
||||
default=self.default_timeout,
|
||||
help=('Change the timeout, if applicable, for the running command; '
|
||||
'default=%default')
|
||||
)
|
||||
|
||||
|
||||
class OutputOptionsMixIn(object):
|
||||
|
||||
__metaclass__ = MixInMeta
|
||||
_mixin_prio_ = 40
|
||||
_include_text_out_ = False
|
||||
|
||||
def _mixin_setup(self):
|
||||
group = self.output_options_group = optparse.OptionGroup(
|
||||
self, "Output Options", "Configure your preferred output format"
|
||||
)
|
||||
self.add_option_group(group)
|
||||
group.add_option(
|
||||
'--raw-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Print the output from the salt-key command in raw python '
|
||||
'form, this is suitable for re-reading the output into an '
|
||||
'executing python script with eval.')
|
||||
)
|
||||
group.add_option(
|
||||
'--yaml-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Print the output from the salt-key command in yaml.'
|
||||
)
|
||||
group.add_option(
|
||||
'--json-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Print the output from the salt-key command in json.'
|
||||
)
|
||||
if self._include_text_out_:
|
||||
group.add_option(
|
||||
'--text-out',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Print the output from the salt command in the same '
|
||||
'form the shell would.')
|
||||
)
|
||||
group.add_option(
|
||||
'--no-color',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Disable all colored output'
|
||||
)
|
||||
|
||||
def _mixin_after_parsed(self):
|
||||
group_options_selected = filter(
|
||||
lambda option: getattr(self.options, option.dest) and
|
||||
option.dest.endswith('_out'),
|
||||
self.output_options_group.option_list
|
||||
)
|
||||
if len(group_options_selected) > 1:
|
||||
self.error(
|
||||
"The options {0} are mutually exclusive. Please only choose "
|
||||
"one of them".format('/'.join([
|
||||
option.get_opt_string() for option in group_options_selected
|
||||
]))
|
||||
)
|
||||
|
||||
# def get_printout(self, outputter=None):
|
||||
# # Determine the proper output method and run it
|
||||
# from salt.output import get_printout
|
||||
# return get_outputter(outputter)
|
||||
#
|
||||
# if outputter is not None:
|
||||
# return get_outputter(outputter)
|
||||
#
|
||||
# if self.options.raw_out:
|
||||
# outputter = 'raw'
|
||||
# elif self.options.json_out:
|
||||
# outputter = 'json'
|
||||
# elif self._include_text_out_ and self.options.text_out:
|
||||
# outputter = 'txt'
|
||||
# elif self.options.yaml_out:
|
||||
# outputter = 'yaml'
|
||||
#
|
||||
# if outputter is not None:
|
||||
# return get_outputter(outputter)
|
||||
#
|
||||
# return None
|
||||
|
||||
class OutputOptionsWithTextMixIn(OutputOptionsMixIn):
|
||||
_include_text_out_ = True
|
||||
|
||||
|
||||
class MasterOptionParser(OptionParser, ConfigDirMixIn, LogLevelMixIn,
|
||||
DeprecatedMasterMinionMixIn, RunUserMixin,
|
||||
DaemonMixIn, PidfileMixin):
|
||||
|
@ -343,3 +585,91 @@ class SyndicOptionParser(OptionParser, DeprecatedSyndicOptionsMixIn,
|
|||
opts['_master_conf_file'] = opts['conf_file']
|
||||
opts.pop('conf_file')
|
||||
return opts
|
||||
|
||||
|
||||
class SaltCMDOptionParser(OptionParser, ConfigDirMixIn, TimeoutMixIn,
|
||||
ExtendedTargetOptionsMixIn,
|
||||
OutputOptionsWithTextMixIn):
|
||||
|
||||
__metaclass__ = OptionParserMeta
|
||||
|
||||
default_timeout = 5
|
||||
|
||||
usage = "%prog [options] '<target>' <function> [arguments]"
|
||||
|
||||
def _mixin_setup(self):
|
||||
self.add_option(
|
||||
'-s', '--static',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Return the data from minions as a group after they '
|
||||
'all return.')
|
||||
)
|
||||
self.add_option(
|
||||
'-v', '--verbose',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=('Turn on command verbosity, display jid and active job '
|
||||
'queries')
|
||||
)
|
||||
self.add_option(
|
||||
'-b', '--batch',
|
||||
'--batch-size',
|
||||
default='',
|
||||
dest='batch',
|
||||
help=('Execute the salt job in batch mode, pass either the number '
|
||||
'of minions to batch at a time, or the percentage of '
|
||||
'minions to have running')
|
||||
)
|
||||
self.add_option(
|
||||
'--return',
|
||||
default='',
|
||||
metavar='RETURNER',
|
||||
help=('Set an alternative return method. By default salt will '
|
||||
'send the return data from the command back to the master, '
|
||||
'but the return data can be redirected into any number of '
|
||||
'systems, databases or applications.')
|
||||
)
|
||||
self.add_option(
|
||||
'-Q', '--query',
|
||||
action='store_true',
|
||||
help=('This option is deprecated and will be removed in a future '
|
||||
'release, please use salt-run jobs instead.\n'
|
||||
'Execute a salt command query, this can be used to find '
|
||||
'the results of a previous function call: -Q test.echo')
|
||||
)
|
||||
|
||||
def _mixin_after_parsed(self):
|
||||
if self.options.query:
|
||||
if len(self.args) < 1:
|
||||
self.error(
|
||||
'Please pass in a command to query the old salt calls for.'
|
||||
)
|
||||
self.config['cmd'] = self.args[0]
|
||||
else:
|
||||
# Catch invalid invocations of salt such as: salt run
|
||||
if len(self.args) <= 1:
|
||||
self.print_help()
|
||||
self.exit(1)
|
||||
|
||||
if self.options.list:
|
||||
self.config['tgt'] = self.args[0].split(',')
|
||||
else:
|
||||
self.config['tgt'] = self.args[0]
|
||||
|
||||
# Detect compound command and set up the data for it
|
||||
if ',' in self.args[1]:
|
||||
self.config['fun'] = self.args[1].split(',')
|
||||
self.config['arg'] = []
|
||||
for comp in ' '.join(self.args[2:]).split(','):
|
||||
self.config['arg'].append(comp.split())
|
||||
if len(self.config['fun']) != len(self.config['arg']):
|
||||
self.exit(42, 'Cannot execute compound command without '
|
||||
'defining all arguments.')
|
||||
else:
|
||||
self.config['fun'] = self.args[1]
|
||||
self.config['arg'] = self.args[2:]
|
||||
|
||||
|
||||
def setup_config(self):
|
||||
return config.master_config(self.get_config_file_path('master'))
|
|
@ -345,7 +345,7 @@ class ShellCase(TestCase):
|
|||
'''
|
||||
Execute salt
|
||||
'''
|
||||
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master')
|
||||
mconf = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf')
|
||||
arg_str = '-c {0} {1}'.format(mconf, arg_str)
|
||||
return self.run_script('salt', arg_str)
|
||||
|
||||
|
|
|
@ -7,10 +7,12 @@ import integration
|
|||
from integration import TestDaemon
|
||||
|
||||
|
||||
class MatchTest(integration.ShellCase):
|
||||
class MatchTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
|
||||
'''
|
||||
Test salt matchers
|
||||
'''
|
||||
_call_binary_ = 'salt'
|
||||
|
||||
def test_list(self):
|
||||
'''
|
||||
test salt -L matcher
|
||||
|
|
Loading…
Add table
Reference in a new issue