diff --git a/README.md b/README.md index 028a013..f73eae0 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,15 @@ interactive: True # Runs the command in reporting mode. The standard output of the remote command will # be dumped to the screen instead of the exit code. report: True + +# The following list forms the arguments for the command line. They are set as +# bash shell variables when running the command. This allows them to be used +# within the command +# NOTE: this key is optional +arguments: + - arg1 + - arg2 + ... ``` # Configuring Run Parameters diff --git a/src/appliance_cli/command_generation.py b/src/appliance_cli/command_generation.py index b6d9357..78d47b5 100644 --- a/src/appliance_cli/command_generation.py +++ b/src/appliance_cli/command_generation.py @@ -173,7 +173,7 @@ def add_argument(name): is_required = False for n in arg_n: add_argument(n) else: - add_argument(arg_name) + add_argument(arg_n) return args_map diff --git a/src/commands/run.py b/src/commands/run.py index 2310659..028da2e 100644 --- a/src/commands/run.py +++ b/src/commands/run.py @@ -33,7 +33,8 @@ def run(): runner_cmd = { 'help': Config.help, - **run_options + **run_options, + 'arguments': Config.args } runner_group = { 'help': (lambda names: "Run tools in {}".format(' '.join(names))), @@ -44,7 +45,7 @@ def run(): @command_creator.tools(run, command = runner_cmd, group = runner_group) @cli_utils.with__node__group @cli_utils.ignore_parent_commands - def runner(_ctx, configs, _a, options, nodes): + def runner(_ctx, configs, argv, options, nodes): def error_if_invalid_interactive_batch(): matches = [c for c in configs if c.interactive()] if matches and (len(configs) > 1 or len(nodes) > 1): @@ -66,13 +67,43 @@ def is_quiet(): if first.interactive() or first.report: return True else: return False + def argument_hash(config): + keys = config.args() + arg_hash = {} + for index, value in enumerate(argv): + arg_hash[keys[index]] = value + return arg_hash + + def build_batches(): + def build(config): + batch = Batch(config = config.path) + batch.build_jobs(*nodes) + batch.build_shell_variables(**argument_hash(config)) + return batch + return list(map(lambda c: build(c), configs)) + + def get_confirmation(batches, nodes): + tool_names = '\n '.join([b.config_model.name() for b in batches]) + node_names = groups_util.compress_nodes(nodes).replace('],', ']\n ') + node_tag = 'node' if len(nodes) == 1 else 'nodes' + click.echo(""" +You are about to run: + {} +Over {}: + {} +""".strip().format(tool_names, node_tag, node_names)) + question = "Please enter [y/n] to confirm" + affirmatives = ['y', 'ye', 'yes'] + reply = click.prompt(question).lower() + if reply in affirmatives: return True + error_if_no_nodes() - batches = list(map(lambda c: Batch(config = c.path), configs)) error_if_invalid_interactive_batch() - if not (options['yes'].value or get_confirmation(batches, nodes)): - return - for batch in batches: batch.build_jobs(*nodes) - execute_batches(batches, quiet = is_quiet()) + + batches = build_batches() + + if (options['yes'].value or get_confirmation(batches, nodes)): + execute_batches(batches, quiet = is_quiet()) def execute_batches(batches, quiet = False): def run_print(string): @@ -116,6 +147,9 @@ def remove_done_tasks(): for batch in batches: session.add(batch) session.commit() + # Ensure the models are loaded from the db + batch.jobs + batch.shell_variables run_print('Executing: {}'.format(batch.name())) tasks = map(lambda j: j.task(thread_pool = pool), batch.jobs) loop.run_until_complete(start_tasks(tasks)) @@ -127,19 +161,3 @@ def remove_done_tasks(): session.commit() Session.remove() run_print('Done') - - def get_confirmation(batches, nodes): - tool_names = '\n '.join([b.config_model.name() for b in batches]) - node_names = groups_util.compress_nodes(nodes).replace('],', ']\n ') - node_tag = 'node' if len(nodes) == 1 else 'nodes' - click.echo(""" -You are about to run: - {} -Over {}: - {} -""".strip().format(tool_names, node_tag, node_names)) - question = "Please enter [y/n] to confirm" - affirmatives = ['y', 'ye', 'yes'] - reply = click.prompt(question).lower() - if reply in affirmatives: - return True diff --git a/src/models/batch.py b/src/models/batch.py index 5ea78db..093ef45 100644 --- a/src/models/batch.py +++ b/src/models/batch.py @@ -4,6 +4,7 @@ from database import Base from models.config import Config from models.job import Job +from models.shell_variable import ShellVariable class Batch(Base): @@ -21,7 +22,12 @@ def help(self): return self.config_model.help() def command(self): - return self.config_model.command() + var_models = self.shell_variables + var_map = map(lambda v: '='.join([v.key, v.value]), var_models) + var_str = ' && '.join(var_map) + cmd = self.config_model.command() + if var_str: cmd = ' && '.join([var_str, cmd]) + return cmd def command_exists(self): return self.config_model.command_exists() @@ -31,3 +37,10 @@ def is_interactive(self): def build_jobs(self, *nodes): return list(map(lambda n: Job(node = n, batch = self), nodes)) + + def build_shell_variables(self, **variables): + def build(key): + args = { 'key': key, 'value': variables[key], 'batch': self } + return ShellVariable(**args) + + return list(map(lambda k: build(k), variables.keys())) diff --git a/src/models/config.py b/src/models/config.py index 08fd42b..6b85a02 100644 --- a/src/models/config.py +++ b/src/models/config.py @@ -50,6 +50,9 @@ def help(self): if not self.data['help']: self.data['help'] = default return self.data['help'] + def args(self): + return self.arguments or [] + # TODO: Deprecated, avoid usage def interactive_only(self): return self.interactive() diff --git a/src/models/shell_variable.py b/src/models/shell_variable.py new file mode 100644 index 0000000..dbf9ed1 --- /dev/null +++ b/src/models/shell_variable.py @@ -0,0 +1,24 @@ + +from sqlalchemy import Column, String, Integer, ForeignKey +from sqlalchemy.orm import relationship, validates + +from database import Base + +import click + +class ShellVariable(Base): + + + key = Column(String) + value = Column(String) + batch_id = Column(Integer, ForeignKey('batches.id')) + batch = relationship("Batch", backref="shell_variables") + + + @validates('value') + def validate_value(self, _, value): + try: + assert value.isalnum() + return value + except AssertionError: + raise click.ClickException('The arguments must be alphanumeric')