Browse Source
Remove rootwrap code copied from oslo-incubator, make the {neutron,quantum}-rootwrap console_script entrypoints point to oslo.rootwrap code instead. Adjust bin/{neutron,quantum}-rootwrap[-xen-dom0] so that it calls into oslo.rootwrap.cmd. Change-Id: I22df4060d6bca6affd7761fec49d2767ca8f59cf Implements: blueprint neutron-oslo-rootwrapchanges/88/71588/2
11 changed files with 7 additions and 644 deletions
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright (c) 2011 OpenStack Foundation. |
||||
# All Rights Reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
||||
# not use this file except in compliance with the License. You may obtain |
||||
# a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
||||
# License for the specific language governing permissions and limitations |
||||
# under the License. |
@ -1,138 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright (c) 2011 OpenStack Foundation. |
||||
# All Rights Reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
||||
# not use this file except in compliance with the License. You may obtain |
||||
# a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
||||
# License for the specific language governing permissions and limitations |
||||
# under the License. |
||||
|
||||
"""Root wrapper for OpenStack services |
||||
|
||||
Filters which commands a service is allowed to run as another user. |
||||
|
||||
To use this with neutron, you should set the following in |
||||
neutron.conf: |
||||
rootwrap_config=/etc/neutron/rootwrap.conf |
||||
|
||||
You also need to let the neutron user run neutron-rootwrap |
||||
as root in sudoers: |
||||
neutron ALL = (root) NOPASSWD: /usr/bin/neutron-rootwrap |
||||
/etc/neutron/rootwrap.conf * |
||||
|
||||
Service packaging should deploy .filters files only on nodes where |
||||
they are needed, to avoid allowing more than is necessary. |
||||
""" |
||||
|
||||
from __future__ import print_function |
||||
|
||||
import ConfigParser |
||||
import logging |
||||
import os |
||||
import pwd |
||||
import signal |
||||
import subprocess |
||||
import sys |
||||
|
||||
|
||||
RC_UNAUTHORIZED = 99 |
||||
RC_NOCOMMAND = 98 |
||||
RC_BADCONFIG = 97 |
||||
RC_NOEXECFOUND = 96 |
||||
|
||||
|
||||
def _subprocess_setup(): |
||||
# Python installs a SIGPIPE handler by default. This is usually not what |
||||
# non-Python subprocesses expect. |
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
||||
|
||||
|
||||
def _exit_error(execname, message, errorcode, log=True): |
||||
print("%s: %s" % (execname, message), file=sys.stderr) |
||||
if log: |
||||
logging.error(message) |
||||
sys.exit(errorcode) |
||||
|
||||
|
||||
def _getlogin(): |
||||
try: |
||||
return os.getlogin() |
||||
except OSError: |
||||
return (os.getenv('USER') or |
||||
os.getenv('USERNAME') or |
||||
os.getenv('LOGNAME')) |
||||
|
||||
|
||||
def main(): |
||||
# Split arguments, require at least a command |
||||
execname = sys.argv.pop(0) |
||||
if len(sys.argv) < 2: |
||||
_exit_error(execname, "No command specified", RC_NOCOMMAND, log=False) |
||||
|
||||
configfile = sys.argv.pop(0) |
||||
userargs = sys.argv[:] |
||||
|
||||
# Add ../ to sys.path to allow running from branch |
||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname), |
||||
os.pardir, os.pardir)) |
||||
if os.path.exists(os.path.join(possible_topdir, "neutron", "__init__.py")): |
||||
sys.path.insert(0, possible_topdir) |
||||
|
||||
from neutron.openstack.common.rootwrap import wrapper |
||||
|
||||
# Load configuration |
||||
try: |
||||
rawconfig = ConfigParser.RawConfigParser() |
||||
rawconfig.read(configfile) |
||||
config = wrapper.RootwrapConfig(rawconfig) |
||||
except ValueError as exc: |
||||
msg = "Incorrect value in %s: %s" % (configfile, exc.message) |
||||
_exit_error(execname, msg, RC_BADCONFIG, log=False) |
||||
except ConfigParser.Error: |
||||
_exit_error(execname, "Incorrect configuration file: %s" % configfile, |
||||
RC_BADCONFIG, log=False) |
||||
|
||||
if config.use_syslog: |
||||
wrapper.setup_syslog(execname, |
||||
config.syslog_log_facility, |
||||
config.syslog_log_level) |
||||
|
||||
# Execute command if it matches any of the loaded filters |
||||
filters = wrapper.load_filters(config.filters_path) |
||||
try: |
||||
filtermatch = wrapper.match_filter(filters, userargs, |
||||
exec_dirs=config.exec_dirs) |
||||
if filtermatch: |
||||
command = filtermatch.get_command(userargs, |
||||
exec_dirs=config.exec_dirs) |
||||
if config.use_syslog: |
||||
logging.info("(%s > %s) Executing %s (filter match = %s)" % ( |
||||
_getlogin(), pwd.getpwuid(os.getuid())[0], |
||||
command, filtermatch.name)) |
||||
|
||||
obj = subprocess.Popen(command, |
||||
stdin=sys.stdin, |
||||
stdout=sys.stdout, |
||||
stderr=sys.stderr, |
||||
preexec_fn=_subprocess_setup, |
||||
env=filtermatch.get_environment(userargs)) |
||||
obj.wait() |
||||
sys.exit(obj.returncode) |
||||
|
||||
except wrapper.FilterMatchNotExecutable as exc: |
||||
msg = ("Executable not found: %s (filter match = %s)" |
||||
% (exc.match.exec_path, exc.match.name)) |
||||
_exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog) |
||||
|
||||
except wrapper.NoFilterMatched: |
||||
msg = ("Unauthorized command: %s (no filter matched)" |
||||
% ' '.join(userargs)) |
||||
_exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog) |
@ -1,318 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright (c) 2011 OpenStack Foundation. |
||||
# All Rights Reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
||||
# not use this file except in compliance with the License. You may obtain |
||||
# a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
||||
# License for the specific language governing permissions and limitations |
||||
# under the License. |
||||
|
||||
import os |
||||
import re |
||||
|
||||
|
||||
class CommandFilter(object): |
||||
"""Command filter only checking that the 1st argument matches exec_path.""" |
||||
|
||||
def __init__(self, exec_path, run_as, *args): |
||||
self.name = '' |
||||
self.exec_path = exec_path |
||||
self.run_as = run_as |
||||
self.args = args |
||||
self.real_exec = None |
||||
|
||||
def get_exec(self, exec_dirs=[]): |
||||
"""Returns existing executable, or empty string if none found.""" |
||||
if self.real_exec is not None: |
||||
return self.real_exec |
||||
self.real_exec = "" |
||||
if os.path.isabs(self.exec_path): |
||||
if os.access(self.exec_path, os.X_OK): |
||||
self.real_exec = self.exec_path |
||||
else: |
||||
for binary_path in exec_dirs: |
||||
expanded_path = os.path.join(binary_path, self.exec_path) |
||||
if os.access(expanded_path, os.X_OK): |
||||
self.real_exec = expanded_path |
||||
break |
||||
return self.real_exec |
||||
|
||||
def match(self, userargs): |
||||
"""Only check that the first argument (command) matches exec_path.""" |
||||
return userargs and os.path.basename(self.exec_path) == userargs[0] |
||||
|
||||
def get_command(self, userargs, exec_dirs=[]): |
||||
"""Returns command to execute (with sudo -u if run_as != root).""" |
||||
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path |
||||
if (self.run_as != 'root'): |
||||
# Used to run commands at lesser privileges |
||||
return ['sudo', '-u', self.run_as, to_exec] + userargs[1:] |
||||
return [to_exec] + userargs[1:] |
||||
|
||||
def get_environment(self, userargs): |
||||
"""Returns specific environment to set, None if none.""" |
||||
return None |
||||
|
||||
|
||||
class RegExpFilter(CommandFilter): |
||||
"""Command filter doing regexp matching for every argument.""" |
||||
|
||||
def match(self, userargs): |
||||
# Early skip if command or number of args don't match |
||||
if (not userargs or len(self.args) != len(userargs)): |
||||
# DENY: argument numbers don't match |
||||
return False |
||||
# Compare each arg (anchoring pattern explicitly at end of string) |
||||
for (pattern, arg) in zip(self.args, userargs): |
||||
try: |
||||
if not re.match(pattern + '$', arg): |
||||
break |
||||
except re.error: |
||||
# DENY: Badly-formed filter |
||||
return False |
||||
else: |
||||
# ALLOW: All arguments matched |
||||
return True |
||||
|
||||
# DENY: Some arguments did not match |
||||
return False |
||||
|
||||
|
||||
class PathFilter(CommandFilter): |
||||
"""Command filter checking that path arguments are within given dirs |
||||
|
||||
One can specify the following constraints for command arguments: |
||||
1) pass - pass an argument as is to the resulting command |
||||
2) some_str - check if an argument is equal to the given string |
||||
3) abs path - check if a path argument is within the given base dir |
||||
|
||||
A typical rootwrapper filter entry looks like this: |
||||
# cmdname: filter name, raw command, user, arg_i_constraint [, ...] |
||||
chown: PathFilter, /bin/chown, root, nova, /var/lib/images |
||||
|
||||
""" |
||||
|
||||
def match(self, userargs): |
||||
if not userargs or len(userargs) < 2: |
||||
return False |
||||
|
||||
command, arguments = userargs[0], userargs[1:] |
||||
|
||||
equal_args_num = len(self.args) == len(arguments) |
||||
exec_is_valid = super(PathFilter, self).match(userargs) |
||||
args_equal_or_pass = all( |
||||
arg == 'pass' or arg == value |
||||
for arg, value in zip(self.args, arguments) |
||||
if not os.path.isabs(arg) # arguments not specifying abs paths |
||||
) |
||||
paths_are_within_base_dirs = all( |
||||
os.path.commonprefix([arg, os.path.realpath(value)]) == arg |
||||
for arg, value in zip(self.args, arguments) |
||||
if os.path.isabs(arg) # arguments specifying abs paths |
||||
) |
||||
|
||||
return (equal_args_num and |
||||
exec_is_valid and |
||||
args_equal_or_pass and |
||||
paths_are_within_base_dirs) |
||||
|
||||
def get_command(self, userargs, exec_dirs=[]): |
||||
command, arguments = userargs[0], userargs[1:] |
||||
|
||||
# convert path values to canonical ones; copy other args as is |
||||
args = [os.path.realpath(value) if os.path.isabs(arg) else value |
||||
for arg, value in zip(self.args, arguments)] |
||||
|
||||
return super(PathFilter, self).get_command([command] + args, |
||||
exec_dirs) |
||||
|
||||
|
||||
class KillFilter(CommandFilter): |
||||
"""Specific filter for the kill calls. |
||||
|
||||
1st argument is the user to run /bin/kill under |
||||
2nd argument is the location of the affected executable |
||||
if the argument is not absolute, it is checked against $PATH |
||||
Subsequent arguments list the accepted signals (if any) |
||||
|
||||
This filter relies on /proc to accurately determine affected |
||||
executable, so it will only work on procfs-capable systems (not OSX). |
||||
""" |
||||
|
||||
def __init__(self, *args): |
||||
super(KillFilter, self).__init__("/bin/kill", *args) |
||||
|
||||
def match(self, userargs): |
||||
if not userargs or userargs[0] != "kill": |
||||
return False |
||||
args = list(userargs) |
||||
if len(args) == 3: |
||||
# A specific signal is requested |
||||
signal = args.pop(1) |
||||
if signal not in self.args[1:]: |
||||
# Requested signal not in accepted list |
||||
return False |
||||
else: |
||||
if len(args) != 2: |
||||
# Incorrect number of arguments |
||||
return False |
||||
if len(self.args) > 1: |
||||
# No signal requested, but filter requires specific signal |
||||
return False |
||||
try: |
||||
command = os.readlink("/proc/%d/exe" % int(args[1])) |
||||
except (ValueError, OSError): |
||||
# Incorrect PID |
||||
return False |
||||
|
||||
# NOTE(yufang521247): /proc/PID/exe may have '\0' on the |
||||
# end, because python doen't stop at '\0' when read the |
||||
# target path. |
||||
command = command.partition('\0')[0] |
||||
|
||||
# NOTE(dprince): /proc/PID/exe may have ' (deleted)' on |
||||
# the end if an executable is updated or deleted |
||||
if command.endswith(" (deleted)"): |
||||
command = command[:-len(" (deleted)")] |
||||
|
||||
kill_command = self.args[0] |
||||
|
||||
if os.path.isabs(kill_command): |
||||
return kill_command == command |
||||
|
||||
return (os.path.isabs(command) and |
||||
kill_command == os.path.basename(command) and |
||||
os.path.dirname(command) in os.environ.get('PATH', '' |
||||
).split(':')) |
||||
|
||||
|
||||
class ReadFileFilter(CommandFilter): |
||||
"""Specific filter for the utils.read_file_as_root call.""" |
||||
|
||||
def __init__(self, file_path, *args): |
||||
self.file_path = file_path |
||||
super(ReadFileFilter, self).__init__("/bin/cat", "root", *args) |
||||
|
||||
def match(self, userargs): |
||||
return (userargs == ['cat', self.file_path]) |
||||
|
||||
|
||||
class IpFilter(CommandFilter): |
||||
"""Specific filter for the ip utility to that does not match exec.""" |
||||
|
||||
def match(self, userargs): |
||||
if userargs[0] == 'ip': |
||||
if userargs[1] == 'netns': |
||||
return (userargs[2] in ('list', 'add', 'delete')) |
||||
else: |
||||
return True |
||||
|
||||
|
||||
class EnvFilter(CommandFilter): |
||||
"""Specific filter for the env utility. |
||||
|
||||
Behaves like CommandFilter, except that it handles |
||||
leading env A=B.. strings appropriately. |
||||
""" |
||||
|
||||
def _extract_env(self, arglist): |
||||
"""Extract all leading NAME=VALUE arguments from arglist.""" |
||||
|
||||
envs = set() |
||||
for arg in arglist: |
||||
if '=' not in arg: |
||||
break |
||||
envs.add(arg.partition('=')[0]) |
||||
return envs |
||||
|
||||
def __init__(self, exec_path, run_as, *args): |
||||
super(EnvFilter, self).__init__(exec_path, run_as, *args) |
||||
|
||||
env_list = self._extract_env(self.args) |
||||
# Set exec_path to X when args are in the form of |
||||
# env A=a B=b C=c X Y Z |
||||
if "env" in exec_path and len(env_list) < len(self.args): |
||||
self.exec_path = self.args[len(env_list)] |
||||
|
||||
def match(self, userargs): |
||||
# ignore leading 'env' |
||||
if userargs[0] == 'env': |
||||
userargs.pop(0) |
||||
|
||||
# require one additional argument after configured ones |
||||
if len(userargs) < len(self.args): |
||||
return False |
||||
|
||||
# extract all env args |
||||
user_envs = self._extract_env(userargs) |
||||
filter_envs = self._extract_env(self.args) |
||||
user_command = userargs[len(user_envs):len(user_envs) + 1] |
||||
|
||||
# match first non-env argument with CommandFilter |
||||
return (super(EnvFilter, self).match(user_command) |
||||
and len(filter_envs) and user_envs == filter_envs) |
||||
|
||||
def exec_args(self, userargs): |
||||
args = userargs[:] |
||||
|
||||
# ignore leading 'env' |
||||
if args[0] == 'env': |
||||
args.pop(0) |
||||
|
||||
# Throw away leading NAME=VALUE arguments |
||||
while args and '=' in args[0]: |
||||
args.pop(0) |
||||
|
||||
return args |
||||
|
||||
def get_command(self, userargs, exec_dirs=[]): |
||||
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path |
||||
return [to_exec] + self.exec_args(userargs)[1:] |
||||
|
||||
def get_environment(self, userargs): |
||||
env = os.environ.copy() |
||||
|
||||
# ignore leading 'env' |
||||
if userargs[0] == 'env': |
||||
userargs.pop(0) |
||||
|
||||
# Handle leading NAME=VALUE pairs |
||||
for a in userargs: |
||||
env_name, equals, env_value = a.partition('=') |
||||
if not equals: |
||||
break |
||||
if env_name and env_value: |
||||
env[env_name] = env_value |
||||
|
||||
return env |
||||
|
||||
|
||||
class ChainingFilter(CommandFilter): |
||||
def exec_args(self, userargs): |
||||
return [] |
||||
|
||||
|
||||
class IpNetnsExecFilter(ChainingFilter): |
||||
"""Specific filter for the ip utility to that does match exec.""" |
||||
|
||||
def match(self, userargs): |
||||
# Network namespaces currently require root |
||||
# require <ns> argument |
||||
if self.run_as != "root" or len(userargs) < 4: |
||||
return False |
||||
|
||||
return (userargs[:3] == ['ip', 'netns', 'exec']) |
||||
|
||||
def exec_args(self, userargs): |
||||
args = userargs[4:] |
||||
if args: |
||||
args[0] = os.path.basename(args[0]) |
||||
return args |
@ -1,165 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright (c) 2011 OpenStack Foundation. |
||||
# All Rights Reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
||||
# not use this file except in compliance with the License. You may obtain |
||||
# a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
||||
# License for the specific language governing permissions and limitations |
||||
# under the License. |
||||
|
||||
|
||||
import logging |
||||
import logging.handlers |
||||
import os |
||||
import string |
||||
|
||||
from six import moves |
||||
|
||||
from neutron.openstack.common.rootwrap import filters |
||||
|
||||
|
||||
class NoFilterMatched(Exception): |
||||
"""This exception is raised when no filter matched.""" |
||||
pass |
||||
|
||||
|
||||
class FilterMatchNotExecutable(Exception): |
||||
"""Raised when a filter matched but no executable was found.""" |
||||
def __init__(self, match=None, **kwargs): |
||||
self.match = match |
||||
|
||||
|
||||
class RootwrapConfig(object): |
||||
|
||||
def __init__(self, config): |
||||
# filters_path |
||||
self.filters_path = config.get("DEFAULT", "filters_path").split(",") |
||||
|
||||
# exec_dirs |
||||
if config.has_option("DEFAULT", "exec_dirs"): |
||||
self.exec_dirs = config.get("DEFAULT", "exec_dirs").split(",") |
||||
else: |
||||
self.exec_dirs = [] |
||||
# Use system PATH if exec_dirs is not specified |
||||
if "PATH" in os.environ: |
||||
self.exec_dirs = os.environ['PATH'].split(':') |
||||
|
||||
# syslog_log_facility |
||||
if config.has_option("DEFAULT", "syslog_log_facility"): |
||||
v = config.get("DEFAULT", "syslog_log_facility") |
||||
facility_names = logging.handlers.SysLogHandler.facility_names |
||||
self.syslog_log_facility = getattr(logging.handlers.SysLogHandler, |
||||
v, None) |
||||
if self.syslog_log_facility is None and v in facility_names: |
||||
self.syslog_log_facility = facility_names.get(v) |
||||
if self.syslog_log_facility is None: |
||||
raise ValueError('Unexpected syslog_log_facility: %s' % v) |
||||
else: |
||||
default_facility = logging.handlers.SysLogHandler.LOG_SYSLOG |
||||
self.syslog_log_facility = default_facility |
||||
|
||||
# syslog_log_level |
||||
if config.has_option("DEFAULT", "syslog_log_level"): |
||||
v = config.get("DEFAULT", "syslog_log_level") |
||||
self.syslog_log_level = logging.getLevelName(v.upper()) |
||||
if (self.syslog_log_level == "Level %s" % v.upper()): |
||||
raise ValueError('Unexepected syslog_log_level: %s' % v) |
||||
else: |
||||
self.syslog_log_level = logging.ERROR |
||||
|
||||
# use_syslog |
||||
if config.has_option("DEFAULT", "use_syslog"): |
||||
self.use_syslog = config.getboolean("DEFAULT", "use_syslog") |
||||
else: |
||||
self.use_syslog = False |
||||
|
||||
|
||||
def setup_syslog(execname, facility, level): |
||||
rootwrap_logger = logging.getLogger() |
||||
rootwrap_logger.setLevel(level) |
||||
handler = logging.handlers.SysLogHandler(address='/dev/log', |
||||
facility=facility) |
||||
handler.setFormatter(logging.Formatter( |
||||
os.path.basename(execname) + ': %(message)s')) |
||||
rootwrap_logger.addHandler(handler) |
||||
|
||||
|
||||
def build_filter(class_name, *args): |
||||
"""Returns a filter object of class class_name.""" |
||||
if not hasattr(filters, class_name): |
||||
logging.warning("Skipping unknown filter class (%s) specified " |
||||
"in filter definitions" % class_name) |
||||
return None |
||||
filterclass = getattr(filters, class_name) |
||||
return filterclass(*args) |
||||
|
||||
|
||||
def load_filters(filters_path): |
||||
"""Load filters from a list of directories.""" |
||||
filterlist = [] |
||||
for filterdir in filters_path: |
||||
if not os.path.isdir(filterdir): |
||||
continue |
||||
for filterfile in filter(lambda f: not f.startswith('.'), |
||||
os.listdir(filterdir)): |
||||
filterconfig = moves.configparser.RawConfigParser() |
||||
filterconfig.read(os.path.join(filterdir, filterfile)) |
||||
for (name, value) in filterconfig.items("Filters"): |
||||
filterdefinition = [string.strip(s) for s in value.split(',')] |
||||
newfilter = build_filter(*filterdefinition) |
||||
if newfilter is None: |
||||
continue |
||||
newfilter.name = name |
||||
filterlist.append(newfilter) |
||||
return filterlist |
||||
|
||||
|
||||
def match_filter(filter_list, userargs, exec_dirs=[]): |
||||
"""Checks user command and arguments through command filters. |
||||
|
||||
Returns the first matching filter. |
||||
|
||||
Raises NoFilterMatched if no filter matched. |
||||
Raises FilterMatchNotExecutable if no executable was found for the |
||||
best filter match. |
||||
""" |
||||
first_not_executable_filter = None |
||||
|
||||
for f in filter_list: |
||||
if f.match(userargs): |
||||
if isinstance(f, filters.ChainingFilter): |
||||
# This command calls exec verify that remaining args |
||||
# matches another filter. |
||||
def non_chain_filter(fltr): |
||||
return (fltr.run_as == f.run_as |
||||
and not isinstance(fltr, filters.ChainingFilter)) |
||||
|
||||
leaf_filters = [fltr for fltr in filter_list |
||||
if non_chain_filter(fltr)] |
||||
args = f.exec_args(userargs) |
||||
if (not args or not match_filter(leaf_filters, |
||||
args, exec_dirs=exec_dirs)): |
||||
continue |
||||
|
||||
# Try other filters if executable is absent |
||||
if not f.get_exec(exec_dirs=exec_dirs): |
||||
if not first_not_executable_filter: |
||||
first_not_executable_filter = f |
||||
continue |
||||
# Otherwise return matching filter for execution |
||||
return f |
||||
|
||||
if first_not_executable_filter: |
||||
# A filter matched, but no executable was found for it |
||||
raise FilterMatchNotExecutable(match=first_not_executable_filter) |
||||
|
||||
# No filter matched |
||||
raise NoFilterMatched() |
Loading…
Reference in new issue