def _parse_function_definition(fn_def, modulename, ret): args = [] match = re.match(r'def\s+(.*?)\((.*)\):$', fn_def) if match is None: return fn_name = match.group(1) if fn_name.startswith('_'): return if fn_name.endswith('_'): fn_name = fn_name[0:-1] fn_name = fn_name.strip('"') fn_name = fn_name.strip("'") try: raw_args = match.group(2) raw_args = re.sub(r'(.*)\(.*\)(.*)', r'\1\2', raw_args) raw_args = re.sub(r'(.*)\'.*\'(.*)', r'\1\2', raw_args) individual_args = raw_args.split(',') for a in individual_args: if '*' in a: continue args.append(a.split('=')[0].strip()) except AttributeError: pass key = '{}.{}'.format(modulename, fn_name) if key in ret: ret[key].extend(args) else: ret[key] = args ret[key] = list(set(ret[key]))
def parse_args_and_kwargs(self, cmdline): """ cmdline: list returns tuple of: args (list), kwargs (dict) """ # Parse args and kwargs args = [] kwargs = {} if len(cmdline) > 1: for item in cmdline[1:]: if "=" in item: (key, value) = item.split("=", 1) kwargs[key] = value else: args.append(item) return (args, kwargs)
def __gather_minions(self): ''' Return a list of minions to use for the batch run ''' args = [ self.opts['tgt'], 'test.ping', [], self.opts.get('batch_presence_ping_timeout', self.opts['timeout']), ] selected_target_option = self.opts.get('selected_target_option', None) if selected_target_option is not None: args.append(selected_target_option) else: args.append(self.opts.get('tgt_type', 'glob')) self.pub_kwargs['yield_pub_data'] = True ping_gen = self.local.cmd_iter( *args, gather_job_timeout=self.opts.get( 'batch_presence_ping_gather_job_timeout', self.opts['gather_job_timeout']), **self.pub_kwargs) # Broadcast to targets fret = set() nret = set() for ret in ping_gen: if ('minions' and 'jid') in ret: for minion in ret['minions']: nret.add(minion) continue else: try: m = next(six.iterkeys(ret)) except StopIteration: if not self.quiet: salt.utils.stringutils.print_cli( 'No minions matched the target.') break if m is not None: fret.add(m) return (list(fret), ping_gen, nret.difference(fret))
def _get_args_kwargs(self, fun, args=None): argspec = salt.utils.args.get_function_argspec(fun) if args is None: args = [] if argspec.args: # Iterate in reverse order to ensure we get the correct default # value for the positional argument. for arg, default in zip_longest( reversed(argspec.args), reversed(argspec.defaults or ())): args.append(self.opts.get(arg, default)) # Reverse the args so that they are in the correct order args = args[::-1] if argspec.keywords is None: kwargs = {} else: args, kwargs = salt.minion.load_args_and_kwargs(fun, args) return args, kwargs
def __gather_minions(self): """ Return a list of minions to use for the batch run """ args = [ self.opts["tgt"], "test.ping", [], self.opts["timeout"], ] selected_target_option = self.opts.get("selected_target_option", None) if selected_target_option is not None: args.append(selected_target_option) else: args.append(self.opts.get("tgt_type", "glob")) self.pub_kwargs["yield_pub_data"] = True ping_gen = self.local.cmd_iter( *args, gather_job_timeout=self.opts["gather_job_timeout"], **self.pub_kwargs) # Broadcast to targets fret = set() nret = set() for ret in ping_gen: if ("minions" and "jid") in ret: for minion in ret["minions"]: nret.add(minion) continue else: try: m = next(six.iterkeys(ret)) except StopIteration: if not self.quiet: salt.utils.stringutils.print_cli( "No minions matched the target.") break if m is not None: fret.add(m) return (list(fret), ping_gen, nret.difference(fret))
def __arg_comps(self): ''' Return the function name and the arg list ''' fun = self.argv[0] if self.argv else '' args = [] kws = {} for arg in self.argv[1:]: # FIXME - there is a bug here that will steal a non-keyword argument. # example: # # .. code-block:: bash # # salt-ssh '*' cmd.run_all 'n=$((RANDOM%8)); exit $n' # # The 'n=' appears to be a keyword argument, but it is # simply the argument! if re.match(r'\w+=', arg): (key, val) = arg.split('=', 1) kws[key] = val else: args.append(arg) return fun, args, kws
def _run(name, **kwargs): ''' .. deprecated:: 2017.7.0 Function name stays the same, behaviour will change. Run a single module function ``name`` The module function to execute ``returner`` Specify the returner to send the return of the module execution to ``kwargs`` Pass any arguments needed to execute the function ''' ret = {'name': name, 'changes': {}, 'comment': '', 'result': None} if name not in __salt__: ret['comment'] = 'Module function {0} is not available'.format(name) ret['result'] = False return ret if __opts__['test']: ret['comment'] = 'Module function {0} is set to execute'.format(name) return ret aspec = salt.utils.args.get_function_argspec(__salt__[name]) args = [] defaults = {} arglen = 0 deflen = 0 if isinstance(aspec.args, list): arglen = len(aspec.args) if isinstance(aspec.defaults, tuple): deflen = len(aspec.defaults) # Match up the defaults with the respective args for ind in range(arglen - 1, -1, -1): minus = arglen - ind if deflen - minus > -1: defaults[aspec.args[ind]] = aspec.defaults[-minus] # overwrite passed default kwargs for arg in defaults: if arg == 'name': if 'm_name' in kwargs: defaults[arg] = kwargs.pop('m_name') elif arg == 'fun': if 'm_fun' in kwargs: defaults[arg] = kwargs.pop('m_fun') elif arg == 'state': if 'm_state' in kwargs: defaults[arg] = kwargs.pop('m_state') elif arg == 'saltenv': if 'm_saltenv' in kwargs: defaults[arg] = kwargs.pop('m_saltenv') if arg in kwargs: defaults[arg] = kwargs.pop(arg) missing = set() for arg in aspec.args: if arg == 'name': rarg = 'm_name' elif arg == 'fun': rarg = 'm_fun' elif arg == 'names': rarg = 'm_names' elif arg == 'state': rarg = 'm_state' elif arg == 'saltenv': rarg = 'm_saltenv' else: rarg = arg if rarg not in kwargs and arg not in defaults: missing.add(rarg) continue if arg in defaults: args.append(defaults[arg]) else: args.append(kwargs.pop(rarg)) if missing: comment = 'The following arguments are missing:' for arg in missing: comment += ' {0}'.format(arg) ret['comment'] = comment ret['result'] = False return ret if aspec.varargs: if aspec.varargs == 'name': rarg = 'm_name' elif aspec.varargs == 'fun': rarg = 'm_fun' elif aspec.varargs == 'names': rarg = 'm_names' elif aspec.varargs == 'state': rarg = 'm_state' elif aspec.varargs == 'saltenv': rarg = 'm_saltenv' else: rarg = aspec.varargs if rarg in kwargs: varargs = kwargs.pop(rarg) if not isinstance(varargs, list): msg = "'{0}' must be a list." ret['comment'] = msg.format(aspec.varargs) ret['result'] = False return ret args.extend(varargs) nkwargs = {} if aspec.keywords and aspec.keywords in kwargs: nkwargs = kwargs.pop(aspec.keywords) if not isinstance(nkwargs, dict): msg = "'{0}' must be a dict." ret['comment'] = msg.format(aspec.keywords) ret['result'] = False return ret try: if aspec.keywords: mret = __salt__[name](*args, **nkwargs) else: mret = __salt__[name](*args) except Exception as e: ret['comment'] = 'Module function {0} threw an exception. Exception: {1}'.format( name, e) ret['result'] = False return ret else: if mret is not None or mret is not {}: ret['changes']['ret'] = mret if 'returner' in kwargs: ret_ret = { 'id': __opts__['id'], 'ret': mret, 'fun': name, 'jid': salt.utils.jid.gen_jid(__opts__) } returners = salt.loader.returners(__opts__, __salt__) if kwargs['returner'] in returners: returners[kwargs['returner']](ret_ret) ret['comment'] = 'Module function {0} executed'.format(name) ret['result'] = _get_result(mret, ret['changes']) return ret
def start(token, room='salt', aliases=None, valid_users=None, valid_commands=None, control=False, trigger="!", tag='salt/engines/hipchat/incoming', api_key=None, api_url=None, max_rooms=None, wait_time=None, output_type='file', outputter='nested'): ''' Listen to Hipchat messages and forward them to Salt. token The HipChat API key. It requires a key for global usgae, assigned per user, rather than room. room The HipChat room name. aliases Define custom aliases. valid_users Restrict access only to certain users. valid_commands Restrict the execution to a limited set of commands. control Send commands to the master. trigger: ``!`` Special character that triggers the execution of salt commands. tag: ``salt/engines/hipchat/incoming`` The event tag on the Salt bus. api_url: ``https://api.hipchat.com`` The URL to the HipChat API. .. versionadded:: 2017.7.0 max_rooms: ``1000`` Maximum number of rooms allowed to fetch. If set to 0, it is able to retrieve the entire list of rooms. wait_time: ``5`` Maximum wait time, in seconds. output_type: ``file`` The type of the output. Choose bewteen: - ``file``: save the output into a temporary file and upload - ``html``: send the output as HTML - ``code``: send the output as code This can be overriden when executing a command, using the ``--out-type`` argument. .. versionadded:: 2017.7.0 outputter: ``nested`` The format to display the data, using the outputters available on the CLI. This argument can also be overriden when executing a command, using the ``--out`` option. .. versionadded:: 2017.7.0 HipChat Example: .. code-block:: text ! test.ping ! test.ping target=minion1 ! test.ping --out=nested ! test.ping --out-type=code --out=table ''' target_room = None if __opts__.get('__role') == 'master': fire_master = salt.utils.event.get_master_event( __opts__, __opts__['sock_dir']).fire_event else: fire_master = None def fire(tag, msg): ''' fire event to salt bus ''' if fire_master: fire_master(msg, tag) else: __salt__['event.send'](tag, msg) def _eval_bot_mentions(all_messages, trigger): ''' yield partner message ''' for message in all_messages: message_text = message['message'] if message_text.startswith(trigger): fire(tag, message) text = message_text.replace(trigger, '').strip() yield message['from']['mention_name'], text token = token or api_key if not token: raise UserWarning("Hipchat token not found") runner_functions = sorted(salt.runner.Runner(__opts__).functions) if not api_url: api_url = _DEFAULT_API_URL hipc = hypchat.HypChat(token, endpoint=api_url) if not hipc: raise UserWarning("Unable to connect to hipchat") log.debug('Connected to Hipchat') rooms_kwargs = {} if max_rooms is None: max_rooms = _DEFAULT_MAX_ROOMS rooms_kwargs['max_results'] = max_rooms elif max_rooms > 0: rooms_kwargs['max_results'] = max_rooms # if max_rooms is 0 => retrieve all (rooms_kwargs is empty dict) all_rooms = hipc.rooms(**rooms_kwargs)['items'] for a_room in all_rooms: if a_room['name'] == room: target_room = a_room if not target_room: log.debug("Unable to connect to room %s", room) # wait for a bit as to not burn through api calls time.sleep(30) raise UserWarning("Unable to connect to room {0}".format(room)) after_message_id = target_room.latest(maxResults=1)['items'][0]['id'] while True: try: new_messages = target_room.latest( not_before=after_message_id)['items'] except hypchat.requests.HttpServiceUnavailable: time.sleep(15) continue after_message_id = new_messages[-1]['id'] for partner, text in _eval_bot_mentions(new_messages[1:], trigger): # bot summoned by partner if not control: log.debug("Engine not configured for control") return # Ensure the user is allowed to run commands if valid_users: if partner not in valid_users: target_room.message('{0} not authorized to run Salt commands'.format(partner)) return args = [] kwargs = {} cmdline = salt.utils.args.shlex_split(text) cmd = cmdline[0] # Evaluate aliases if aliases and isinstance(aliases, dict) and cmd in aliases.keys(): cmdline = aliases[cmd].get('cmd') cmdline = salt.utils.args.shlex_split(cmdline) cmd = cmdline[0] # Parse args and kwargs if len(cmdline) > 1: for item in cmdline[1:]: if '=' in item: (key, value) = item.split('=', 1) kwargs[key] = value else: args.append(item) # Check for target. Otherwise assume * if 'target' not in kwargs: target = '*' else: target = kwargs['target'] del kwargs['target'] # Check for tgt_type. Otherwise assume glob if 'tgt_type' not in kwargs: tgt_type = 'glob' else: tgt_type = kwargs['tgt_type'] del kwargs['tgt_type'] # Check for outputter. Otherwise assume nested if '--out' in kwargs: outputter = kwargs['--out'] del kwargs['--out'] # Check for outputter. Otherwise assume nested if '--out-type' in kwargs: output_type = kwargs['--out-type'] del kwargs['--out-type'] # Ensure the command is allowed if valid_commands: if cmd not in valid_commands: target_room.message('Using {0} is not allowed.'.format(cmd)) return ret = {} if cmd in runner_functions: runner = salt.runner.RunnerClient(__opts__) ret = runner.cmd(cmd, arg=args, kwarg=kwargs) # Default to trying to run as a client module. else: local = salt.client.LocalClient() ret = local.cmd('{0}'.format(target), cmd, args, kwargs, tgt_type='{0}'.format(tgt_type)) nice_args = (' ' + ' '.join(args)) if args else '' nice_kwargs = (' ' + ' '.join('{0}={1}'.format(key, value) for (key, value) in six.iteritems(kwargs))) \ if kwargs else '' message_string = '@{0} Results for: {1}{2}{3} on {4}'.format(partner, cmd, nice_args, nice_kwargs, target) if output_type == 'html': _publish_html_message(token, room, ret, message=message_string, outputter=outputter, api_url=api_url) elif output_type == 'code': _publish_code_message(token, room, ret, message=message_string, outputter=outputter, api_url=api_url) else: tmp_path_fn = salt.utils.files.mkstemp() with salt.utils.files.fopen(tmp_path_fn, 'w+') as fp_: salt.utils.json.dump(ret, fp_, sort_keys=True, indent=4) _publish_file(token, room, tmp_path_fn, message=message_string, api_url=api_url) salt.utils.files.safe_rm(tmp_path_fn) time.sleep(wait_time or _DEFAULT_SLEEP)
def _run(name, **kwargs): """ .. deprecated:: 2017.7.0 Function name stays the same, behaviour will change. Run a single module function ``name`` The module function to execute ``returner`` Specify the returner to send the return of the module execution to ``kwargs`` Pass any arguments needed to execute the function """ ret = {"name": name, "changes": {}, "comment": "", "result": None} if name not in __salt__: ret["comment"] = "Module function {0} is not available".format(name) ret["result"] = False return ret if __opts__["test"]: ret["comment"] = "Module function {0} is set to execute".format(name) return ret aspec = salt.utils.args.get_function_argspec(__salt__[name]) args = [] defaults = {} arglen = 0 deflen = 0 if isinstance(aspec.args, list): arglen = len(aspec.args) if isinstance(aspec.defaults, tuple): deflen = len(aspec.defaults) # Match up the defaults with the respective args for ind in range(arglen - 1, -1, -1): minus = arglen - ind if deflen - minus > -1: defaults[aspec.args[ind]] = aspec.defaults[-minus] # overwrite passed default kwargs for arg in defaults: if arg == "name": if "m_name" in kwargs: defaults[arg] = kwargs.pop("m_name") elif arg == "fun": if "m_fun" in kwargs: defaults[arg] = kwargs.pop("m_fun") elif arg == "state": if "m_state" in kwargs: defaults[arg] = kwargs.pop("m_state") elif arg == "saltenv": if "m_saltenv" in kwargs: defaults[arg] = kwargs.pop("m_saltenv") if arg in kwargs: defaults[arg] = kwargs.pop(arg) missing = set() for arg in aspec.args: if arg == "name": rarg = "m_name" elif arg == "fun": rarg = "m_fun" elif arg == "names": rarg = "m_names" elif arg == "state": rarg = "m_state" elif arg == "saltenv": rarg = "m_saltenv" else: rarg = arg if rarg not in kwargs and arg not in defaults: missing.add(rarg) continue if arg in defaults: args.append(defaults[arg]) else: args.append(kwargs.pop(rarg)) if missing: comment = "The following arguments are missing:" for arg in missing: comment += " {0}".format(arg) ret["comment"] = comment ret["result"] = False return ret if aspec.varargs: if aspec.varargs == "name": rarg = "m_name" elif aspec.varargs == "fun": rarg = "m_fun" elif aspec.varargs == "names": rarg = "m_names" elif aspec.varargs == "state": rarg = "m_state" elif aspec.varargs == "saltenv": rarg = "m_saltenv" else: rarg = aspec.varargs if rarg in kwargs: varargs = kwargs.pop(rarg) if not isinstance(varargs, list): msg = "'{0}' must be a list." ret["comment"] = msg.format(aspec.varargs) ret["result"] = False return ret args.extend(varargs) nkwargs = {} if aspec.keywords and aspec.keywords in kwargs: nkwargs = kwargs.pop(aspec.keywords) if not isinstance(nkwargs, dict): msg = "'{0}' must be a dict." ret["comment"] = msg.format(aspec.keywords) ret["result"] = False return ret try: if aspec.keywords: mret = __salt__[name](*args, **nkwargs) else: mret = __salt__[name](*args) except Exception as e: # pylint: disable=broad-except ret["comment"] = "Module function {0} threw an exception. Exception: {1}".format( name, e) ret["result"] = False return ret else: if mret is not None or mret is not {}: ret["changes"]["ret"] = mret if "returner" in kwargs: ret_ret = { "id": __opts__["id"], "ret": mret, "fun": name, "jid": salt.utils.jid.gen_jid(__opts__), } returners = salt.loader.returners(__opts__, __salt__) if kwargs["returner"] in returners: returners[kwargs["returner"]](ret_ret) ret["comment"] = "Module function {0} executed".format(name) ret["result"] = _get_result(mret, ret["changes"]) return ret
rarg = "m_name" elif arg == "fun": rarg = "m_fun" elif arg == "names": rarg = "m_names" elif arg == "state": rarg = "m_state" elif arg == "saltenv": rarg = "m_saltenv" else: rarg = arg if rarg not in kwargs and arg not in defaults: missing.add(rarg) continue if arg in defaults: args.append(defaults[arg]) else: args.append(kwargs.pop(rarg)) if missing: comment = "The following arguments are missing:" for arg in missing: comment += " {0}".format(arg) ret["comment"] = comment ret["result"] = False return ret if aspec.varargs: if aspec.varargs == "name": rarg = "m_name" elif aspec.varargs == "fun": rarg = "m_fun"