ARIA-148 Add CLI display commands
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/57eeb716 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/57eeb716 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/57eeb716 Branch: refs/heads/ARIA-148-extra-cli-commands Commit: 57eeb7162af01d5955dcc0ed443533682ed4c9d2 Parents: 0ec2370 Author: Tal Liron <tal.li...@gmail.com> Authored: Thu Apr 20 17:54:47 2017 -0500 Committer: Tal Liron <tal.li...@gmail.com> Committed: Mon May 8 13:34:46 2017 -0500 ---------------------------------------------------------------------- aria/cli/commands/service_templates.py | 71 +++++++++++++++++++---------- aria/cli/commands/services.py | 52 ++++++++++++++++++++- aria/cli/core/aria.py | 34 +++++++++++++- aria/cli/helptexts.py | 8 +++- aria/cli/table.py | 2 +- aria/modeling/service_instance.py | 4 +- aria/modeling/service_template.py | 2 +- aria/modeling/types.py | 5 ++ aria/utils/collections.py | 2 +- 9 files changed, 146 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/cli/commands/service_templates.py ---------------------------------------------------------------------- diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py index 2537012..6e5b530 100644 --- a/aria/cli/commands/service_templates.py +++ b/aria/cli/commands/service_templates.py @@ -23,11 +23,13 @@ from .. import utils from ..core import aria from ...core import Core from ...storage import exceptions as storage_exceptions +from ...parser import consumption +from ...utils import (formatting, collections, console) DESCRIPTION_FIELD_LENGTH_LIMIT = 20 SERVICE_TEMPLATE_COLUMNS = \ - ['id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at'] + ('id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at') @aria.group(name='service-templates') @@ -43,32 +45,51 @@ def service_templates(): @aria.argument('service-template-name') @aria.options.verbose() @aria.pass_model_storage +@aria.options.show_all +@aria.options.show_types +@aria.options.show_json +@aria.options.show_yaml @aria.pass_logger -def show(service_template_name, model_storage, logger): - """Show information for a specific service templates +def show(service_template_name, model_storage, all, types, json, yaml, logger): + """Show information for a specific service template `SERVICE_TEMPLATE_NAME` is the name of the service template to show information on. """ - logger.info('Showing service template {0}...'.format(service_template_name)) service_template = model_storage.service_template.get_by_name(service_template_name) - service_template_dict = service_template.to_dict() - service_template_dict['#services'] = len(service_template.services) - column_formatters = \ - dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT)) - columns = SERVICE_TEMPLATE_COLUMNS + ['#services'] - table.print_data(columns, service_template_dict, 'Service-template:', - column_formatters=column_formatters, col_max_width=50) - - if service_template_dict['description'] is not None: - logger.info('Description:') - logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '', - os.linesep)) - - if service_template.services: - logger.info('Existing services:') - for service in service_template.services: - logger.info('\t{0}'.format(service.name)) + if json or yaml: + all = True + + if all: + consumption.ConsumptionContext() + if json: + console.puts(formatting.json_dumps(collections.prune(service_template.as_raw))) + elif yaml: + console.puts(formatting.yaml_dumps(collections.prune(service_template.as_raw))) + else: + service_template.dump() + elif types: + consumption.ConsumptionContext() + service_template.dump_types() + else: + logger.info('Showing service template {0}...'.format(service_template_name)) + service_template_dict = service_template.to_dict() + service_template_dict['#services'] = len(service_template.services) + columns = SERVICE_TEMPLATE_COLUMNS + ('#services',) + column_formatters = \ + dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT)) + table.print_data(columns, service_template_dict, 'Service-template:', + column_formatters=column_formatters, col_max_width=50) + + if service_template_dict['description'] is not None: + logger.info('Description:') + logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '', + os.linesep)) + + if service_template.services: + logger.info('Existing services:') + for service in service_template.services: + logger.info('\t{0}'.format(service.name)) @service_templates.command(name='list', @@ -135,6 +156,7 @@ def store(service_template_path, service_template_name, service_template_filenam @aria.pass_logger def delete(service_template_name, model_storage, resource_storage, plugin_manager, logger): """Delete a service template + `SERVICE_TEMPLATE_NAME` is the name of the service template to delete. """ logger.info('Deleting service template {0}...'.format(service_template_name)) @@ -172,7 +194,7 @@ def validate(service_template, service_template_filename, model_storage, resource_storage, plugin_manager, logger): """Validate a service template - `SERVICE_TEMPLATE` is the path or url of the service template or archive to validate. + `SERVICE_TEMPLATE` is the path or URL of the service template or archive to validate. """ logger.info('Validating service template: {0}'.format(service_template)) service_template_path = service_template_utils.get(service_template, service_template_filename) @@ -188,11 +210,10 @@ def validate(service_template, service_template_filename, @aria.options.verbose() @aria.pass_logger def create_archive(service_template_path, destination, logger): - """Create a csar archive on the local file system + """Create a CSAR archive `service_template_path` is the path of the service template to create the archive from - - `destination` is the path of the output CSAR archive file + `destination` is the path of the output CSAR archive """ logger.info('Creating a CSAR archive') csar.write(os.path.dirname(service_template_path), service_template_path, destination, logger) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/cli/commands/services.py ---------------------------------------------------------------------- diff --git a/aria/cli/commands/services.py b/aria/cli/commands/services.py index 50b530a..464c958 100644 --- a/aria/cli/commands/services.py +++ b/aria/cli/commands/services.py @@ -25,9 +25,12 @@ from ..core import aria from ...core import Core from ...modeling import exceptions as modeling_exceptions from ...storage import exceptions as storage_exceptions +from ...parser import consumption +from ...utils import (formatting, collections, console) -SERVICE_COLUMNS = ['id', 'name', 'service_template_name', 'created_at', 'updated_at'] +DESCRIPTION_FIELD_LENGTH_LIMIT = 20 +SERVICE_COLUMNS = ('id', 'name', 'description', 'service_template_name' 'created_at', 'updated_at') @aria.group(name='services') @@ -38,6 +41,53 @@ def services(): pass +@services.command(name='show', + short_help='Display service information') +@aria.argument('service-name') +@aria.options.verbose() +@aria.options.show_all +@aria.options.show_graph +@aria.options.show_json +@aria.options.show_yaml +@aria.pass_model_storage +@aria.pass_logger +def show(service_name, model_storage, all, graph, json, yaml, logger): + """Show information for a specific service template + + `SERVICE_NAME` is the name of the service to display information on. + """ + logger.info('Showing service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + + if json or yaml: + all = True + + if all: + consumption.ConsumptionContext() + if json: + console.puts(formatting.json_dumps(collections.prune(service.as_raw))) + elif yaml: + console.puts(formatting.yaml_dumps(collections.prune(service.as_raw))) + else: + service.dump() + elif graph: + consumption.ConsumptionContext() + service.dump_graph() + else: + logger.info('Showing service {0}...'.format(service_name)) + service_dict = service.to_dict() + columns = SERVICE_COLUMNS + column_formatters = \ + dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT)) + table.print_data(columns, service_dict, 'Service:', + column_formatters=column_formatters, col_max_width=50) + + if service_dict['description'] is not None: + logger.info('Description:') + logger.info('{0}{1}'.format(service_dict['description'].encode('UTF-8') or '', + os.linesep)) + + @services.command(name='list', short_help='List services') @aria.options.service_template_name() @aria.options.sort_by() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/cli/core/aria.py ---------------------------------------------------------------------- diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py index ed6afa1..d303e16 100644 --- a/aria/cli/core/aria.py +++ b/aria/cli/core/aria.py @@ -281,12 +281,12 @@ def argument(*args, **kwargs): class Options(object): def __init__(self): - """The options api is nicer when you use each option by calling + """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 + a developer sees an option. If it can receive arguments, it's a method - if not, it's an attribute. """ self.version = click.option( @@ -325,6 +325,36 @@ class Options(object): default=defaults.SERVICE_TEMPLATE_FILENAME, help=helptexts.SERVICE_TEMPLATE_FILENAME) + self.show_all = click.option( + '-a', + '--all', + is_flag=True, + help=helptexts.SHOW_ALL) + + self.show_json = click.option( + '-j', + '--json', + is_flag=True, + help=helptexts.SHOW_JSON) + + self.show_yaml = click.option( + '-y', + '--yaml', + is_flag=True, + help=helptexts.SHOW_YAML) + + self.show_types = click.option( + '-t', + '--types', + is_flag=True, + help=helptexts.SHOW_TYPES) + + self.show_graph = click.option( + '-g', + '--graph', + is_flag=True, + help=helptexts.SHOW_GRAPH) + @staticmethod def verbose(expose_value=False): return click.option( http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/cli/helptexts.py ---------------------------------------------------------------------- diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py index 8641822..f46bee0 100644 --- a/aria/cli/helptexts.py +++ b/aria/cli/helptexts.py @@ -29,7 +29,7 @@ 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") + "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 ' @@ -48,3 +48,9 @@ SORT_BY = "Key for sorting the list" DESCENDING = "Sort list in descending order [default: False]" JSON_OUTPUT = "Output logs in a consumable JSON format" MARK_PATTERN = "Mark a regex pattern in the logs" + +SHOW_ALL = "Show all information" +SHOW_JSON = "Show in JSON format (implies --all)" +SHOW_YAML = "Show in YAML format (implies --all)" +SHOW_TYPES = "Show only the type hierarchies" +SHOW_GRAPH = "Show only the node graph" http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/cli/table.py ---------------------------------------------------------------------- diff --git a/aria/cli/table.py b/aria/cli/table.py index 408f81e..abc673e 100644 --- a/aria/cli/table.py +++ b/aria/cli/table.py @@ -85,7 +85,7 @@ def _generate(cols, data, column_formatters=None, defaults=None): return val else: - return defaults[column] + return defaults.get(column) if defaults is not None else None column_formatters = column_formatters or dict() pretty_table = PrettyTable(list(cols)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index ad8e7ed..915f0f6 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -1011,7 +1011,7 @@ class SubstitutionBase(InstanceModelMixin): @property def as_raw(self): return collections.OrderedDict(( - ('node_type_name', self.node_type_name), + ('node_type_name', self.node_type.name), ('mappings', formatting.as_raw_dict(self.mappings)))) def validate(self): @@ -1119,7 +1119,7 @@ class SubstitutionMappingBase(InstanceModelMixin): @property def as_raw(self): return collections.OrderedDict(( - ('name', self.name))) + ('name', self.name),)) def coerce_values(self, container, report_issues): pass http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 7fab4fc..cea80c9 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -1042,7 +1042,7 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin): @property def as_raw(self): return collections.OrderedDict(( - ('name', self.name))) + ('name', self.name),)) def coerce_values(self, container, report_issues): pass http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/modeling/types.py ---------------------------------------------------------------------- diff --git a/aria/modeling/types.py b/aria/modeling/types.py index 06f171c..7460f47 100644 --- a/aria/modeling/types.py +++ b/aria/modeling/types.py @@ -22,6 +22,7 @@ from sqlalchemy import ( event ) from sqlalchemy.ext import mutable +from ruamel import yaml from . import exceptions @@ -206,6 +207,8 @@ class _StrictDict(object): (_StrictDictMixin, _MutableDict), {'_key_cls': key_cls, '_value_cls': value_cls} ) + yaml.representer.RoundTripRepresenter.add_representer( + listener_cls, yaml.representer.RoundTripRepresenter.represent_list) self._strict_map[strict_dict_map_key] = _StrictValue(type_cls=strict_dict_cls, listener_cls=listener_cls) @@ -242,6 +245,8 @@ class _StrictList(object): (_StrictListMixin, _MutableList), {'_item_cls': item_cls} ) + yaml.representer.RoundTripRepresenter.add_representer( + listener_cls, yaml.representer.RoundTripRepresenter.represent_list) self._strict_map[item_cls] = _StrictValue(type_cls=strict_list_cls, listener_cls=listener_cls) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/57eeb716/aria/utils/collections.py ---------------------------------------------------------------------- diff --git a/aria/utils/collections.py b/aria/utils/collections.py index 03feabd..1e732aa 100644 --- a/aria/utils/collections.py +++ b/aria/utils/collections.py @@ -249,7 +249,7 @@ def prune(value, is_removable_function=is_removable): else: prune(v, is_removable_function) elif isinstance(value, dict): - for k, v in value.iteritems(): + for k, v in value.items(): if is_removable_function(value, k, v): del value[k] else: