blob: 3f3f70fbe901a378077ddfd85435484db2f4f6bf [file] [log] [blame] [view]
# Command Hooks for the Aurora Client
## Introduction/Motivation
We've got hooks in the client that surround API calls. These are
pretty awkward, because they don't correlate with user actions. For
example, suppose we wanted a policy that said users weren't allowed to
kill all instances of a production job at once.
Right now, all that we could hook would be the "killJob" api call. But
kill (at least in newer versions of the client) normally runs in
batches. If a user called killall, what we would see on the API level
is a series of "killJob" calls, each of which specified a batch of
instances. We woudn't be able to distinguish between really killing
all instances of a job (which is forbidden under this policy), and
carefully killing in batches (which is permitted.) In each case, the
hook would just see a series of API calls, and couldn't find out what
the actual command being executed was!
For most policy enforcement, what we really want to be able to do is
look at and vet the commands that a user is performing, not the API
calls that the client uses to implement those commands.
So I propose that we add a new kind of hooks, which surround noun/verb
commands. A hook will register itself to handle a collection of (noun,
verb) pairs. Whenever any of those noun/verb commands are invoked, the
hooks methods will be called around the execution of the verb. A
pre-hook will have the ability to reject a command, preventing the
verb from being executed.
## Registering Hooks
These hooks will be registered via configuration plugins. A configuration plugin
can register hooks using an API. Hooks registered this way are, effectively,
hardwired into the client executable.
The order of execution of hooks is unspecified: they may be called in
any order. There is no way to guarantee that one hook will execute
before some other hook.
### Global Hooks
Commands registered by the python call are called _global_ hooks,
because they will run for all configurations, whether or not they
specify any hooks in the configuration file.
In the implementation, hooks are registered in the module
`apache.aurora.client.cli.command_hooks`, using the class
`GlobalCommandHookRegistry`. A global hook can be registered by calling
`GlobalCommandHookRegistry.register_command_hook` in a configuration plugin.
### The API
class CommandHook(object)
@property
def name(self):
"""Returns a name for the hook."
def get_nouns(self):
"""Return the nouns that have verbs that should invoke this hook."""
def get_verbs(self, noun):
"""Return the verbs for a particular noun that should invoke his hook."""
@abstractmethod
def pre_command(self, noun, verb, context, commandline):
"""Execute a hook before invoking a verb.
* noun: the noun being invoked.
* verb: the verb being invoked.
* context: the context object that will be used to invoke the verb.
The options object will be initialized before calling the hook
* commandline: the original argv collection used to invoke the client.
Returns: True if the command should be allowed to proceed; False if the command
should be rejected.
"""
def post_command(self, noun, verb, context, commandline, result):
"""Execute a hook after invoking a verb.
* noun: the noun being invoked.
* verb: the verb being invoked.
* context: the context object that will be used to invoke the verb.
The options object will be initialized before calling the hook
* commandline: the original argv collection used to invoke the client.
* result: the result code returned by the verb.
Returns: nothing
"""
class GlobalCommandHookRegistry(object):
@classmethod
def register_command_hook(self, hook):
pass
### Skipping Hooks
To skip a hook, a user uses a command-line option, `--skip-hooks`. The option can either
specify specific hooks to skip, or "all":
* `aurora --skip-hooks=all job create east/bozo/devel/myjob` will create a job
without running any hooks.
* `aurora --skip-hooks=test,iq create east/bozo/devel/myjob` will create a job,
and will skip only the hooks named "test" and "iq".