You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by av...@apache.org on 2017/04/19 11:21:25 UTC
[07/12] incubator-ariatosca git commit: reviewed helptexts module;
renamed cli.cli package to cli.core
reviewed helptexts module; renamed cli.cli package to cli.core
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/79f5d78e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/79f5d78e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/79f5d78e
Branch: refs/heads/cli-tests
Commit: 79f5d78ebe31327e7635ae75551ba636ac5fd1dc
Parents: 3bff159
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Tue Apr 18 16:55:52 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Tue Apr 18 16:55:52 2017 +0300
----------------------------------------------------------------------
aria/cli/cli/__init__.py | 14 -
aria/cli/cli/aria.py | 439 ----------------------------
aria/cli/cli/helptexts.py | 57 ----
aria/cli/commands/executions.py | 5 +-
aria/cli/commands/logs.py | 2 +-
aria/cli/commands/node_templates.py | 4 +-
aria/cli/commands/nodes.py | 2 +-
aria/cli/commands/plugins.py | 4 +-
aria/cli/commands/reset.py | 4 +-
aria/cli/commands/service_templates.py | 2 +-
aria/cli/commands/services.py | 5 +-
aria/cli/commands/workflows.py | 2 +-
aria/cli/core/__init__.py | 14 +
aria/cli/core/aria.py | 429 +++++++++++++++++++++++++++
aria/cli/helptexts.py | 49 ++++
aria/cli/main.py | 2 +-
16 files changed, 509 insertions(+), 525 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/cli/__init__.py
----------------------------------------------------------------------
diff --git a/aria/cli/cli/__init__.py b/aria/cli/cli/__init__.py
deleted file mode 100644
index ae1e83e..0000000
--- a/aria/cli/cli/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/cli/aria.py
----------------------------------------------------------------------
diff --git a/aria/cli/cli/aria.py b/aria/cli/cli/aria.py
deleted file mode 100644
index 548be23..0000000
--- a/aria/cli/cli/aria.py
+++ /dev/null
@@ -1,439 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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 sys
-import difflib
-import StringIO
-import traceback
-from functools import wraps
-
-import click
-
-from ..env import (
- env,
- logger
-)
-from ..cli import helptexts
-from ..inputs import inputs_to_dict
-from ..constants import DEFAULT_SERVICE_TEMPLATE_FILENAME
-from ...utils.exceptions import get_exception_as_string
-from ... import __version__
-
-
-CLICK_CONTEXT_SETTINGS = dict(
- help_option_names=['-h', '--help'],
- token_normalize_func=lambda param: param.lower())
-
-
-class MutuallyExclusiveOption(click.Option):
- """Makes options mutually exclusive. The option must pass a `cls` argument
- with this class name and a `mutually_exclusive` argument with a list of
- argument names it is mutually exclusive with.
-
- NOTE: All mutually exclusive options must use this. It's not enough to
- use it in just one of the options.
- """
-
- def __init__(self, *args, **kwargs):
- self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
- self.mutuality_error_message = \
- kwargs.pop('mutuality_error_message',
- helptexts.DEFAULT_MUTUALITY_MESSAGE)
- self.mutuality_string = ', '.join(self.mutually_exclusive)
- if self.mutually_exclusive:
- help = kwargs.get('help', '')
- kwargs['help'] = (
- '{0}. This argument is mutually exclusive with '
- 'arguments: [{1}] ({2})'.format(
- help,
- self.mutuality_string,
- self.mutuality_error_message))
- super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)
-
- def handle_parse_result(self, ctx, opts, args):
- if self.mutually_exclusive.intersection(opts) and self.name in opts:
- raise click.UsageError(
- 'Illegal usage: `{0}` is mutually exclusive with '
- 'arguments: [{1}] ({2}).'.format(
- self.name,
- self.mutuality_string,
- self.mutuality_error_message))
- return super(MutuallyExclusiveOption, self).handle_parse_result(
- ctx, opts, args)
-
-
-def _format_version_data(version,
- prefix=None,
- suffix=None,
- infix=None):
- all_data = dict(version=version)
- all_data['prefix'] = prefix or ''
- all_data['suffix'] = suffix or ''
- all_data['infix'] = infix or ''
- output = StringIO.StringIO()
- output.write('{prefix}{version}'.format(**all_data))
- output.write('{suffix}'.format(**all_data))
- return output.getvalue()
-
-
-def show_version(ctx, param, value):
- if not value:
- return
-
- cli_version = _format_version_data(
- __version__,
- prefix='ARIA CLI ',
- infix=' ' * 5,
- suffix='')
-
- logger.info(cli_version)
- ctx.exit()
-
-
-def inputs_callback(ctx, param, value):
- """Allow to pass any inputs we provide to a command as
- processed inputs instead of having to call `inputs_to_dict`
- inside the command.
-
- `@aria.options.inputs` already calls this callback so that
- every time you use the option it returns the inputs as a
- dictionary.
- """
- if not value:
- return {}
-
- return inputs_to_dict(value)
-
-
-def set_verbosity_level(ctx, param, value):
- if not value:
- return
-
- env.logging.verbosity_level = value
-
-
-def set_cli_except_hook():
-
- def recommend(possible_solutions):
- logger.info('Possible solutions:')
- for solution in possible_solutions:
- logger.info(' - {0}'.format(solution))
-
- def new_excepthook(tpe, value, trace):
- if env.logging.is_high_verbose_level():
- # log error including traceback
- logger.error(get_exception_as_string(tpe, value, trace))
- else:
- # write the full error to the log file
- with open(env.logging.log_file, 'a') as log_file:
- traceback.print_exception(
- etype=tpe,
- value=value,
- tb=trace,
- file=log_file)
- # print only the error message
- print value
-
- if hasattr(value, 'possible_solutions'):
- recommend(getattr(value, 'possible_solutions'))
-
- sys.excepthook = new_excepthook
-
-
-def pass_logger(func):
- """Simply passes the logger to a command.
- """
- # Wraps here makes sure the original docstring propagates to click
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(logger=logger, *args, **kwargs)
-
- return wrapper
-
-
-def pass_plugin_manager(func):
- """Simply passes the plugin manager to a command.
- """
- # Wraps here makes sure the original docstring propagates to click
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(plugin_manager=env.plugin_manager, *args, **kwargs)
-
- return wrapper
-
-
-def pass_model_storage(func):
- """Simply passes the model storage to a command.
- """
- # Wraps here makes sure the original docstring propagates to click
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(model_storage=env.model_storage, *args, **kwargs)
-
- return wrapper
-
-
-def pass_resource_storage(func):
- """Simply passes the resource storage to a command.
- """
- # Wraps here makes sure the original docstring propagates to click
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(resource_storage=env.resource_storage, *args, **kwargs)
-
- return wrapper
-
-
-def pass_context(func):
- """Make click context ARIA specific
-
- This exists purely for aesthetic reasons, otherwise
- Some decorators are called `@click.something` instead of
- `@aria.something`
- """
- return click.pass_context(func)
-
-
-class AliasedGroup(click.Group):
- def __init__(self, *args, **kwargs):
- self.max_suggestions = kwargs.pop("max_suggestions", 3)
- self.cutoff = kwargs.pop("cutoff", 0.5)
- super(AliasedGroup, self).__init__(*args, **kwargs)
-
- def get_command(self, ctx, cmd_name):
- cmd = click.Group.get_command(self, ctx, cmd_name)
- if cmd is not None:
- return cmd
- matches = \
- [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
- if not matches:
- return None
- elif len(matches) == 1:
- return click.Group.get_command(self, ctx, matches[0])
- ctx.fail('Too many matches: {0}'.format(', '.join(sorted(matches))))
-
- def resolve_command(self, ctx, args):
- """Override clicks ``resolve_command`` method
- and appends *Did you mean ...* suggestions
- to the raised exception message.
- """
- try:
- return super(AliasedGroup, self).resolve_command(ctx, args)
- except click.exceptions.UsageError as error:
- error_msg = str(error)
- original_cmd_name = click.utils.make_str(args[0])
- matches = difflib.get_close_matches(
- original_cmd_name,
- self.list_commands(ctx),
- self.max_suggestions,
- self.cutoff)
- if matches:
- error_msg += '{0}{0}Did you mean one of these?{0} {1}'.format(
- os.linesep,
- '{0} '.format(os.linesep).join(matches, ))
- raise click.exceptions.UsageError(error_msg, error.ctx)
-
-
-def group(name):
- """Allow to create a group with a default click context
- and a cls for click's `didyoueamn` without having to repeat
- it for every group.
- """
- return click.group(
- name=name,
- context_settings=CLICK_CONTEXT_SETTINGS,
- cls=AliasedGroup)
-
-
-def command(*args, **kwargs):
- """Make Click commands ARIA specific
-
- This exists purely for aesthetical reasons, otherwise
- Some decorators are called `@click.something` instead of
- `@aria.something`
- """
- return click.command(*args, **kwargs)
-
-
-def argument(*args, **kwargs):
- """Make Click arguments ARIA specific
-
- This exists purely for aesthetic reasons, otherwise
- Some decorators are called `@click.something` instead of
- `@aria.something`
- """
- return click.argument(*args, **kwargs)
-
-
-class Options(object):
- def __init__(self):
- """The options api is nicer when you use each option by calling
- `@aria.options.some_option` instead of `@aria.some_option`.
-
- Note that some options are attributes and some are static methods.
- The reason for that is that we want to be explicit regarding how
- a developer sees an option. It it can receive arguments, it's a
- method - if not, it's an attribute.
- """
- self.version = click.option(
- '--version',
- is_flag=True,
- callback=show_version,
- expose_value=False,
- is_eager=True,
- help=helptexts.VERSION)
-
- self.inputs = click.option(
- '-i',
- '--inputs',
- multiple=True,
- callback=inputs_callback,
- help=helptexts.INPUTS)
-
- self.json_output = click.option(
- '--json-output',
- is_flag=True,
- help=helptexts.JSON_OUTPUT)
-
- self.dry_execution = click.option(
- '--dry',
- is_flag=True,
- help=helptexts.DRY_EXECUTION)
-
- self.reset_config = click.option(
- '--reset-config',
- is_flag=True,
- help=helptexts.RESET_CONFIG)
-
- self.enable_colors = click.option(
- '--enable-colors',
- is_flag=True,
- default=False,
- help=helptexts.ENABLE_COLORS)
-
- self.node_name = click.option(
- '-n',
- '--node-name',
- required=False,
- help=helptexts.NODE_NAME)
-
- self.descending = click.option(
- '--descending',
- required=False,
- is_flag=True,
- default=False,
- help=helptexts.DESCENDING)
-
- self.service_template_filename = click.option(
- '-n',
- '--service-template-filename',
- default=DEFAULT_SERVICE_TEMPLATE_FILENAME,
- help=helptexts.SERVICE_TEMPLATE_FILENAME)
-
- @staticmethod
- def verbose(expose_value=False):
- return click.option(
- '-v',
- '--verbose',
- count=True,
- callback=set_verbosity_level,
- expose_value=expose_value,
- is_eager=True,
- help=helptexts.VERBOSE)
-
- @staticmethod
- def force(help):
- return click.option(
- '-f',
- '--force',
- is_flag=True,
- help=help)
-
- @staticmethod
- def task_max_attempts(default=1):
- return click.option(
- '--task-max-attempts',
- type=int,
- default=default,
- help=helptexts.TASK_MAX_ATTEMPTS.format(default))
-
- @staticmethod
- def sort_by(default='created_at'):
- return click.option(
- '--sort-by',
- required=False,
- default=default,
- help=helptexts.SORT_BY)
-
- @staticmethod
- def task_retry_interval(default=1):
- return click.option(
- '--task-retry-interval',
- type=int,
- default=default,
- help=helptexts.TASK_RETRY_INTERVAL.format(default))
-
- @staticmethod
- def service_id(required=False):
- return click.option(
- '-s',
- '--service-id',
- required=required,
- help=helptexts.SERVICE_ID)
-
- @staticmethod
- def execution_id(required=False):
- return click.option(
- '-e',
- '--execution-id',
- required=required,
- help=helptexts.EXECUTION_ID)
-
- @staticmethod
- def service_template_id(required=False):
- return click.option(
- '-t',
- '--service-template-id',
- required=required,
- help=helptexts.SERVICE_TEMPLATE_ID)
-
- @staticmethod
- def service_template_path(required=False):
- return click.option(
- '-p',
- '--service-template-path',
- required=required,
- type=click.Path(exists=True))
-
- @staticmethod
- def service_name(required=False):
- return click.option(
- '-s',
- '--service-name',
- required=required,
- help=helptexts.SERVICE_ID)
-
- @staticmethod
- def service_template_name(required=False):
- return click.option(
- '-t',
- '--service-template-name',
- required=required,
- help=helptexts.SERVICE_ID)
-
-
-options = Options()
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/cli/helptexts.py
----------------------------------------------------------------------
diff --git a/aria/cli/cli/helptexts.py b/aria/cli/cli/helptexts.py
deleted file mode 100644
index f8b315c..0000000
--- a/aria/cli/cli/helptexts.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-VERBOSE = \
- "Show verbose output. You can supply this up to three times (i.e. -vvv)"
-VERSION = "Display the version and exit"
-
-INPUTS_PARAMS_USAGE = (
- '(Can be provided as wildcard based paths '
- '(*.yaml, /my_inputs/, etc..) to YAML files, a JSON string or as '
- 'key1=value1;key2=value2). This argument can be used multiple times'
-)
-
-SERVICE_TEMPLATE_PATH = "The path to the application's service template file"
-SERVICE_TEMPLATE_ID = "The unique identifier for the service template"
-
-FORCE_RESET = "Confirmation for resetting ARIA's working directory"
-RESET_CONFIG = "Reset ARIA's user configuration"
-ENABLE_COLORS = "Enable colors in logger (use --hard when working with" \
- " an initialized environment) [default: False]"
-
-DRY_EXECUTION = "Execute a workflow dry run (prints operations information without causing side " \
- "effects)"
-SERVICE_TEMPLATE_FILENAME = (
- "The name of the archive's main service template file. "
- "This is only relevant if uploading an archive")
-INPUTS = "Inputs for the service {0}".format(INPUTS_PARAMS_USAGE)
-PARAMETERS = "Parameters for the workflow {0}".format(INPUTS_PARAMS_USAGE)
-TASK_RETRY_INTERVAL = \
- "How long of a minimal interval should occur between task retry attempts [default: {0}]"
-TASK_MAX_ATTEMPTS = \
- "How many times should a task be attempted in case of failures [default: {0}]"
-
-JSON_OUTPUT = "Output events in a consumable JSON format"
-
-SERVICE_ID = "The unique identifier for the service"
-EXECUTION_ID = "The unique identifier for the execution"
-IGNORE_AVAILABLE_NODES = "Delete the service even if it has available nodes"
-
-NODE_NAME = "The node's name"
-
-DEFAULT_MUTUALITY_MESSAGE = 'Cannot be used simultaneously'
-
-SORT_BY = "Key for sorting the list"
-DESCENDING = "Sort list in descending order [default: False]"
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/executions.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/executions.py b/aria/cli/commands/executions.py
index 730fd29..cd12ead 100644
--- a/aria/cli/commands/executions.py
+++ b/aria/cli/commands/executions.py
@@ -15,9 +15,10 @@
import os
+from .. import helptexts
from .. import utils
+from ..core import aria
from ..table import print_data
-from ..cli import aria
from ...modeling.models import Execution
from ...orchestrator.workflow_runner import WorkflowRunner
from ...orchestrator.workflows.executor.dry import DryExecutor
@@ -103,7 +104,7 @@ def list(service_name,
short_help='Execute a workflow')
@aria.argument('workflow-name')
@aria.options.service_name(required=True)
-@aria.options.inputs
+@aria.options.inputs(help=helptexts.EXECUTION_INPUTS)
@aria.options.dry_execution
@aria.options.task_max_attempts()
@aria.options.task_retry_interval()
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/logs.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/logs.py b/aria/cli/commands/logs.py
index f8873cd..8888fef 100644
--- a/aria/cli/commands/logs.py
+++ b/aria/cli/commands/logs.py
@@ -14,7 +14,7 @@
# limitations under the License.
from .. import utils
-from ..cli import aria
+from ..core import aria
@aria.group(name='logs')
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/node_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/node_templates.py b/aria/cli/commands/node_templates.py
index cf50ceb..b63b630 100644
--- a/aria/cli/commands/node_templates.py
+++ b/aria/cli/commands/node_templates.py
@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from ..table import print_data
from .. import utils
-from ..cli import aria
+from ..core import aria
+from ..table import print_data
NODE_TEMPLATE_COLUMNS = ['id', 'name', 'description', 'service_template_name', 'type_name']
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/nodes.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/nodes.py b/aria/cli/commands/nodes.py
index fd65e24..b1f2acc 100644
--- a/aria/cli/commands/nodes.py
+++ b/aria/cli/commands/nodes.py
@@ -14,7 +14,7 @@
# limitations under the License.
from .. import utils
-from ..cli import aria
+from ..core import aria
from ..table import print_data
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/plugins.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/plugins.py b/aria/cli/commands/plugins.py
index 9e7d449..680284f 100644
--- a/aria/cli/commands/plugins.py
+++ b/aria/cli/commands/plugins.py
@@ -15,9 +15,9 @@
import zipfile
-from ..table import print_data
-from ..cli import aria
+from ..core import aria
from ..exceptions import AriaCliError
+from ..table import print_data
from ..utils import storage_sort_param
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/reset.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/reset.py b/aria/cli/commands/reset.py
index 775f555..1fe0714 100644
--- a/aria/cli/commands/reset.py
+++ b/aria/cli/commands/reset.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from ..cli import aria
-from ..cli import helptexts
+from .. import helptexts
+from ..core import aria
from ..env import env
from ..exceptions import AriaCliError
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/service_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py
index 8e0e91c..93dc188 100644
--- a/aria/cli/commands/service_templates.py
+++ b/aria/cli/commands/service_templates.py
@@ -19,7 +19,7 @@ import os
from .. import utils
from .. import csar
from .. import service_template_utils
-from ..cli import aria
+from ..core import aria
from ..table import print_data
from ..exceptions import AriaCliError
from ..utils import handle_storage_exception
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/services.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/services.py b/aria/cli/commands/services.py
index 78899c5..4728509 100644
--- a/aria/cli/commands/services.py
+++ b/aria/cli/commands/services.py
@@ -18,7 +18,8 @@ import os
from StringIO import StringIO
from . import service_templates
-from ..cli import aria, helptexts
+from .. import helptexts
+from ..core import aria
from ..exceptions import AriaCliError
from ..table import print_data
from ..utils import storage_sort_param, handle_storage_exception
@@ -74,7 +75,7 @@ def list(service_template_name,
short_help='Create a services')
@aria.argument('service-name', required=False)
@aria.options.service_template_name(required=True)
-@aria.options.inputs
+@aria.options.inputs(help=helptexts.SERVICE_INPUTS)
@aria.options.verbose()
@aria.pass_model_storage
@aria.pass_resource_storage
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/commands/workflows.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/workflows.py b/aria/cli/commands/workflows.py
index 72dea5b..d380fac 100644
--- a/aria/cli/commands/workflows.py
+++ b/aria/cli/commands/workflows.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from ..core import aria
from ..table import print_data
-from ..cli import aria
from ..exceptions import AriaCliError
WORKFLOW_COLUMNS = ['name', 'service_template_name', 'service_name']
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/core/__init__.py
----------------------------------------------------------------------
diff --git a/aria/cli/core/__init__.py b/aria/cli/core/__init__.py
new file mode 100644
index 0000000..ae1e83e
--- /dev/null
+++ b/aria/cli/core/__init__.py
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/core/aria.py
----------------------------------------------------------------------
diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py
new file mode 100644
index 0000000..cd1036e
--- /dev/null
+++ b/aria/cli/core/aria.py
@@ -0,0 +1,429 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 sys
+import difflib
+import StringIO
+import traceback
+from functools import wraps
+
+import click
+
+from ..env import (
+ env,
+ logger
+)
+from .. import helptexts
+from ..inputs import inputs_to_dict
+from ..constants import DEFAULT_SERVICE_TEMPLATE_FILENAME
+from ...utils.exceptions import get_exception_as_string
+from ... import __version__
+
+
+CLICK_CONTEXT_SETTINGS = dict(
+ help_option_names=['-h', '--help'],
+ token_normalize_func=lambda param: param.lower())
+
+
+class MutuallyExclusiveOption(click.Option):
+ """Makes options mutually exclusive. The option must pass a `cls` argument
+ with this class name and a `mutually_exclusive` argument with a list of
+ argument names it is mutually exclusive with.
+
+ NOTE: All mutually exclusive options must use this. It's not enough to
+ use it in just one of the options.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
+ self.mutuality_error_message = \
+ kwargs.pop('mutuality_error_message',
+ helptexts.DEFAULT_MUTUALITY_MESSAGE)
+ self.mutuality_string = ', '.join(self.mutually_exclusive)
+ if self.mutually_exclusive:
+ help = kwargs.get('help', '')
+ kwargs['help'] = (
+ '{0}. This argument is mutually exclusive with '
+ 'arguments: [{1}] ({2})'.format(
+ help,
+ self.mutuality_string,
+ self.mutuality_error_message))
+ super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)
+
+ def handle_parse_result(self, ctx, opts, args):
+ if self.mutually_exclusive.intersection(opts) and self.name in opts:
+ raise click.UsageError(
+ 'Illegal usage: `{0}` is mutually exclusive with '
+ 'arguments: [{1}] ({2}).'.format(
+ self.name,
+ self.mutuality_string,
+ self.mutuality_error_message))
+ return super(MutuallyExclusiveOption, self).handle_parse_result(
+ ctx, opts, args)
+
+
+def _format_version_data(version,
+ prefix=None,
+ suffix=None,
+ infix=None):
+ all_data = dict(version=version)
+ all_data['prefix'] = prefix or ''
+ all_data['suffix'] = suffix or ''
+ all_data['infix'] = infix or ''
+ output = StringIO.StringIO()
+ output.write('{prefix}{version}'.format(**all_data))
+ output.write('{suffix}'.format(**all_data))
+ return output.getvalue()
+
+
+def show_version(ctx, param, value):
+ if not value:
+ return
+
+ cli_version = _format_version_data(
+ __version__,
+ prefix='ARIA CLI ',
+ infix=' ' * 5,
+ suffix='')
+
+ logger.info(cli_version)
+ ctx.exit()
+
+
+def inputs_callback(ctx, param, value):
+ """Allow to pass any inputs we provide to a command as
+ processed inputs instead of having to call `inputs_to_dict`
+ inside the command.
+
+ `@aria.options.inputs` already calls this callback so that
+ every time you use the option it returns the inputs as a
+ dictionary.
+ """
+ if not value:
+ return {}
+
+ return inputs_to_dict(value)
+
+
+def set_verbosity_level(ctx, param, value):
+ if not value:
+ return
+
+ env.logging.verbosity_level = value
+
+
+def set_cli_except_hook():
+
+ def recommend(possible_solutions):
+ logger.info('Possible solutions:')
+ for solution in possible_solutions:
+ logger.info(' - {0}'.format(solution))
+
+ def new_excepthook(tpe, value, trace):
+ if env.logging.is_high_verbose_level():
+ # log error including traceback
+ logger.error(get_exception_as_string(tpe, value, trace))
+ else:
+ # write the full error to the log file
+ with open(env.logging.log_file, 'a') as log_file:
+ traceback.print_exception(
+ etype=tpe,
+ value=value,
+ tb=trace,
+ file=log_file)
+ # print only the error message
+ print value
+
+ if hasattr(value, 'possible_solutions'):
+ recommend(getattr(value, 'possible_solutions'))
+
+ sys.excepthook = new_excepthook
+
+
+def pass_logger(func):
+ """Simply passes the logger to a command.
+ """
+ # Wraps here makes sure the original docstring propagates to click
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(logger=logger, *args, **kwargs)
+
+ return wrapper
+
+
+def pass_plugin_manager(func):
+ """Simply passes the plugin manager to a command.
+ """
+ # Wraps here makes sure the original docstring propagates to click
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(plugin_manager=env.plugin_manager, *args, **kwargs)
+
+ return wrapper
+
+
+def pass_model_storage(func):
+ """Simply passes the model storage to a command.
+ """
+ # Wraps here makes sure the original docstring propagates to click
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(model_storage=env.model_storage, *args, **kwargs)
+
+ return wrapper
+
+
+def pass_resource_storage(func):
+ """Simply passes the resource storage to a command.
+ """
+ # Wraps here makes sure the original docstring propagates to click
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(resource_storage=env.resource_storage, *args, **kwargs)
+
+ return wrapper
+
+
+def pass_context(func):
+ """Make click context ARIA specific
+
+ This exists purely for aesthetic reasons, otherwise
+ Some decorators are called `@click.something` instead of
+ `@aria.something`
+ """
+ return click.pass_context(func)
+
+
+class AliasedGroup(click.Group):
+ def __init__(self, *args, **kwargs):
+ self.max_suggestions = kwargs.pop("max_suggestions", 3)
+ self.cutoff = kwargs.pop("cutoff", 0.5)
+ super(AliasedGroup, self).__init__(*args, **kwargs)
+
+ def get_command(self, ctx, cmd_name):
+ cmd = click.Group.get_command(self, ctx, cmd_name)
+ if cmd is not None:
+ return cmd
+ matches = \
+ [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
+ if not matches:
+ return None
+ elif len(matches) == 1:
+ return click.Group.get_command(self, ctx, matches[0])
+ ctx.fail('Too many matches: {0}'.format(', '.join(sorted(matches))))
+
+ def resolve_command(self, ctx, args):
+ """Override clicks ``resolve_command`` method
+ and appends *Did you mean ...* suggestions
+ to the raised exception message.
+ """
+ try:
+ return super(AliasedGroup, self).resolve_command(ctx, args)
+ except click.exceptions.UsageError as error:
+ error_msg = str(error)
+ original_cmd_name = click.utils.make_str(args[0])
+ matches = difflib.get_close_matches(
+ original_cmd_name,
+ self.list_commands(ctx),
+ self.max_suggestions,
+ self.cutoff)
+ if matches:
+ error_msg += '{0}{0}Did you mean one of these?{0} {1}'.format(
+ os.linesep,
+ '{0} '.format(os.linesep).join(matches, ))
+ raise click.exceptions.UsageError(error_msg, error.ctx)
+
+
+def group(name):
+ """Allow to create a group with a default click context
+ and a cls for click's `didyoueamn` without having to repeat
+ it for every group.
+ """
+ return click.group(
+ name=name,
+ context_settings=CLICK_CONTEXT_SETTINGS,
+ cls=AliasedGroup)
+
+
+def command(*args, **kwargs):
+ """Make Click commands ARIA specific
+
+ This exists purely for aesthetical reasons, otherwise
+ Some decorators are called `@click.something` instead of
+ `@aria.something`
+ """
+ return click.command(*args, **kwargs)
+
+
+def argument(*args, **kwargs):
+ """Make Click arguments ARIA specific
+
+ This exists purely for aesthetic reasons, otherwise
+ Some decorators are called `@click.something` instead of
+ `@aria.something`
+ """
+ return click.argument(*args, **kwargs)
+
+
+class Options(object):
+ def __init__(self):
+ """The options api is nicer when you use each option by calling
+ `@aria.options.some_option` instead of `@aria.some_option`.
+
+ Note that some options are attributes and some are static methods.
+ The reason for that is that we want to be explicit regarding how
+ a developer sees an option. It it can receive arguments, it's a
+ method - if not, it's an attribute.
+ """
+ self.version = click.option(
+ '--version',
+ is_flag=True,
+ callback=show_version,
+ expose_value=False,
+ is_eager=True,
+ help=helptexts.VERSION)
+
+ self.json_output = click.option(
+ '--json-output',
+ is_flag=True,
+ help=helptexts.JSON_OUTPUT)
+
+ self.dry_execution = click.option(
+ '--dry',
+ is_flag=True,
+ help=helptexts.DRY_EXECUTION)
+
+ self.reset_config = click.option(
+ '--reset-config',
+ is_flag=True,
+ help=helptexts.RESET_CONFIG)
+
+ self.descending = click.option(
+ '--descending',
+ required=False,
+ is_flag=True,
+ default=False,
+ help=helptexts.DESCENDING)
+
+ self.service_template_filename = click.option(
+ '-n',
+ '--service-template-filename',
+ default=DEFAULT_SERVICE_TEMPLATE_FILENAME,
+ help=helptexts.SERVICE_TEMPLATE_FILENAME)
+
+ @staticmethod
+ def verbose(expose_value=False):
+ return click.option(
+ '-v',
+ '--verbose',
+ count=True,
+ callback=set_verbosity_level,
+ expose_value=expose_value,
+ is_eager=True,
+ help=helptexts.VERBOSE)
+
+ @staticmethod
+ def inputs(help):
+ return click.option(
+ '-i',
+ '--inputs',
+ multiple=True,
+ callback=inputs_callback,
+ help=help)
+
+ @staticmethod
+ def force(help):
+ return click.option(
+ '-f',
+ '--force',
+ is_flag=True,
+ help=help)
+
+ @staticmethod
+ def task_max_attempts(default=1):
+ return click.option(
+ '--task-max-attempts',
+ type=int,
+ default=default,
+ help=helptexts.TASK_MAX_ATTEMPTS.format(default))
+
+ @staticmethod
+ def sort_by(default='created_at'):
+ return click.option(
+ '--sort-by',
+ required=False,
+ default=default,
+ help=helptexts.SORT_BY)
+
+ @staticmethod
+ def task_retry_interval(default=1):
+ return click.option(
+ '--task-retry-interval',
+ type=int,
+ default=default,
+ help=helptexts.TASK_RETRY_INTERVAL.format(default))
+
+ @staticmethod
+ def service_id(required=False):
+ return click.option(
+ '-s',
+ '--service-id',
+ required=required,
+ help=helptexts.SERVICE_ID)
+
+ @staticmethod
+ def execution_id(required=False):
+ return click.option(
+ '-e',
+ '--execution-id',
+ required=required,
+ help=helptexts.EXECUTION_ID)
+
+ @staticmethod
+ def service_template_id(required=False):
+ return click.option(
+ '-t',
+ '--service-template-id',
+ required=required,
+ help=helptexts.SERVICE_TEMPLATE_ID)
+
+ @staticmethod
+ def service_template_path(required=False):
+ return click.option(
+ '-p',
+ '--service-template-path',
+ required=required,
+ type=click.Path(exists=True))
+
+ @staticmethod
+ def service_name(required=False):
+ return click.option(
+ '-s',
+ '--service-name',
+ required=required,
+ help=helptexts.SERVICE_ID)
+
+ @staticmethod
+ def service_template_name(required=False):
+ return click.option(
+ '-t',
+ '--service-template-name',
+ required=required,
+ help=helptexts.SERVICE_ID)
+
+
+options = Options()
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/helptexts.py
----------------------------------------------------------------------
diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py
new file mode 100644
index 0000000..6e31f47
--- /dev/null
+++ b/aria/cli/helptexts.py
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+
+DEFAULT_MUTUALITY_MESSAGE = 'Cannot be used simultaneously'
+VERBOSE = \
+ "Show verbose output. You can supply this up to three times (i.e. -vvv)"
+
+VERSION = "Display the version and exit"
+FORCE_RESET = "Confirmation for resetting ARIA's working directory"
+RESET_CONFIG = "Reset ARIA's user configuration"
+
+SERVICE_TEMPLATE_ID = "The unique identifier for the service template"
+SERVICE_ID = "The unique identifier for the service"
+EXECUTION_ID = "The unique identifier for the execution"
+
+SERVICE_TEMPLATE_PATH = "The path to the application's service template file"
+SERVICE_TEMPLATE_FILENAME = (
+ "The name of the archive's main service template file. "
+ "This is only relevant if uploading a non-csar archive")
+INPUTS_PARAMS_USAGE = (
+ '(Can be provided as wildcard based paths '
+ '(*.yaml, /my_inputs/, etc..) to YAML files, a JSON string or as '
+ 'key1=value1;key2=value2). This argument can be used multiple times')
+SERVICE_INPUTS = "Inputs for the service {0}".format(INPUTS_PARAMS_USAGE)
+EXECUTION_INPUTS = "Inputs for the execution {0}".format(INPUTS_PARAMS_USAGE)
+
+TASK_RETRY_INTERVAL = \
+ "How long of a minimal interval should occur between task retry attempts [default: {0}]"
+TASK_MAX_ATTEMPTS = \
+ "How many times should a task be attempted in case of failures [default: {0}]"
+DRY_EXECUTION = "Execute a workflow dry run (prints operations information without causing side " \
+ "effects)"
+IGNORE_AVAILABLE_NODES = "Delete the service even if it has available nodes"
+SORT_BY = "Key for sorting the list"
+DESCENDING = "Sort list in descending order [default: False]"
+JSON_OUTPUT = "Output logs in a consumable JSON format"
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/79f5d78e/aria/cli/main.py
----------------------------------------------------------------------
diff --git a/aria/cli/main.py b/aria/cli/main.py
index 01d224c..02cf095 100644
--- a/aria/cli/main.py
+++ b/aria/cli/main.py
@@ -15,7 +15,7 @@
from aria import install_aria_extensions
from aria.cli import commands
-from aria.cli.cli import aria
+from aria.cli.core import aria
@aria.group(name='aria')