diff options
Diffstat (limited to 'azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands')
10 files changed, 1291 insertions, 0 deletions
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/__init__.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/__init__.py new file mode 100644 index 0000000..ba34a43 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/__init__.py @@ -0,0 +1,30 @@ +# 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. + +""" +CLI commands package. +""" + +from . import ( + executions, + logs, + node_templates, + nodes, + plugins, + reset, + service_templates, + services, + workflows +) diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/executions.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/executions.py new file mode 100644 index 0000000..cecbbc5 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/executions.py @@ -0,0 +1,246 @@ +# 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. + +""" +CLI ``executions`` sub-commands. +""" + +import os + +from .. import helptexts +from .. import table +from .. import utils +from .. import logger as cli_logger +from .. import execution_logging +from ..core import aria +from ...modeling.models import Execution +from ...orchestrator.workflow_runner import WorkflowRunner +from ...orchestrator.workflows.executor.dry import DryExecutor +from ...utils import formatting +from ...utils import threading + +EXECUTION_COLUMNS = ('id', 'workflow_name', 'status', 'service_name', + 'created_at', 'error') + + +@aria.group(name='executions') +@aria.options.verbose() +def executions(): + """ + Manage executions + """ + pass + + +@executions.command(name='show', + short_help='Show information for an execution') +@aria.argument('execution-id') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def show(execution_id, model_storage, logger): + """ + Show information for an execution + + EXECUTION_ID is the unique ID of the execution. + """ + logger.info('Showing execution {0}'.format(execution_id)) + execution = model_storage.execution.get(execution_id) + + table.print_data(EXECUTION_COLUMNS, execution, 'Execution:', col_max_width=50) + + # print execution parameters + logger.info('Execution Inputs:') + if execution.inputs: + #TODO check this section, havent tested it + execution_inputs = [ei.to_dict() for ei in execution.inputs] + for input_name, input_value in formatting.decode_dict( + execution_inputs).iteritems(): + logger.info('\t{0}: \t{1}'.format(input_name, input_value)) + else: + logger.info('\tNo inputs') + + +@executions.command(name='list', + short_help='List executions') +@aria.options.service_name(required=False) +@aria.options.sort_by() +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(service_name, + sort_by, + descending, + model_storage, + logger): + """ + List executions + + If SERVICE_NAME is provided, list executions on that service. Otherwise, list executions on all + services. + """ + if service_name: + logger.info('Listing executions for service {0}...'.format( + service_name)) + service = model_storage.service.get_by_name(service_name) + filters = dict(service=service) + else: + logger.info('Listing all executions...') + filters = {} + + executions_list = model_storage.execution.list( + filters=filters, + sort=utils.storage_sort_param(sort_by, descending)).items + + table.print_data(EXECUTION_COLUMNS, executions_list, 'Executions:') + + +@executions.command(name='start', + short_help='Start a workflow on a service') +@aria.argument('workflow-name') +@aria.options.service_name(required=True) +@aria.options.inputs(help=helptexts.EXECUTION_INPUTS) +@aria.options.dry_execution +@aria.options.task_max_attempts() +@aria.options.task_retry_interval() +@aria.options.mark_pattern() +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def start(workflow_name, + service_name, + inputs, + dry, + task_max_attempts, + task_retry_interval, + mark_pattern, + model_storage, + resource_storage, + plugin_manager, + logger): + """ + Start a workflow on a service + + SERVICE_NAME is the unique name of the service. + + WORKFLOW_NAME is the unique name of the workflow within the service (e.g. "uninstall"). + """ + service = model_storage.service.get_by_name(service_name) + executor = DryExecutor() if dry else None # use WorkflowRunner's default executor + + workflow_runner = \ + WorkflowRunner( + model_storage, resource_storage, plugin_manager, + service_id=service.id, workflow_name=workflow_name, inputs=inputs, executor=executor, + task_max_attempts=task_max_attempts, task_retry_interval=task_retry_interval + ) + logger.info('Starting {0}execution. Press Ctrl+C cancel'.format('dry ' if dry else '')) + + _run_execution(workflow_runner, logger, model_storage, dry, mark_pattern) + + +@executions.command(name='resume', + short_help='Resume a stopped execution') +@aria.argument('execution-id') +@aria.options.dry_execution +@aria.options.retry_failed_tasks +@aria.options.mark_pattern() +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def resume(execution_id, + retry_failed_tasks, + dry, + mark_pattern, + model_storage, + resource_storage, + plugin_manager, + logger): + """ + Resume a stopped execution + + EXECUTION_ID is the unique ID of the execution. + """ + executor = DryExecutor() if dry else None # use WorkflowRunner's default executor + + execution = model_storage.execution.get(execution_id) + if execution.status != execution.CANCELLED: + logger.info("Can't resume execution {execution.id} - " + "execution is in status {execution.status}. " + "Can only resume executions in status {valid_status}" + .format(execution=execution, valid_status=execution.CANCELLED)) + return + + workflow_runner = \ + WorkflowRunner( + model_storage, resource_storage, plugin_manager, + execution_id=execution_id, retry_failed_tasks=retry_failed_tasks, executor=executor, + ) + + logger.info('Resuming {0}execution. Press Ctrl+C cancel'.format('dry ' if dry else '')) + _run_execution(workflow_runner, logger, model_storage, dry, mark_pattern) + + +def _run_execution(workflow_runner, logger, model_storage, dry, mark_pattern): + execution_thread_name = '{0}_{1}'.format(workflow_runner.service.name, + workflow_runner.execution.workflow_name) + execution_thread = threading.ExceptionThread(target=workflow_runner.execute, + name=execution_thread_name) + + execution_thread.start() + + last_task_id = workflow_runner.execution.logs[-1].id if workflow_runner.execution.logs else 0 + log_iterator = cli_logger.ModelLogIterator(model_storage, + workflow_runner.execution_id, + offset=last_task_id) + try: + while execution_thread.is_alive(): + execution_logging.log_list(log_iterator, mark_pattern=mark_pattern) + execution_thread.join(1) + + except KeyboardInterrupt: + _cancel_execution(workflow_runner, execution_thread, logger, log_iterator) + + # It might be the case where some logs were written and the execution was terminated, thus we + # need to drain the remaining logs. + execution_logging.log_list(log_iterator, mark_pattern=mark_pattern) + + # raise any errors from the execution thread (note these are not workflow execution errors) + execution_thread.raise_error_if_exists() + + execution = workflow_runner.execution + logger.info('Execution has ended with "{0}" status'.format(execution.status)) + if execution.status == Execution.FAILED and execution.error: + logger.info('Execution error:{0}{1}'.format(os.linesep, execution.error)) + + if dry: + # remove traces of the dry execution (including tasks, logs, inputs..) + model_storage.execution.delete(execution) + + +def _cancel_execution(workflow_runner, execution_thread, logger, log_iterator): + logger.info('Cancelling execution. Press Ctrl+C again to force-cancel.') + workflow_runner.cancel() + while execution_thread.is_alive(): + try: + execution_logging.log_list(log_iterator) + execution_thread.join(1) + except KeyboardInterrupt: + pass diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/logs.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/logs.py new file mode 100644 index 0000000..b751b97 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/logs.py @@ -0,0 +1,72 @@ +# 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. + +""" +CLI ``logs`` sub-commands. +""" + +from .. import execution_logging +from ..logger import ModelLogIterator +from ..core import aria + + +@aria.group(name='logs') +@aria.options.verbose() +def logs(): + """ + Manage logs of workflow executions + """ + pass + + +@logs.command(name='list', + short_help='List logs for an execution') +@aria.argument('execution-id') +@aria.options.verbose() +@aria.options.mark_pattern() +@aria.pass_model_storage +@aria.pass_logger +def list(execution_id, mark_pattern, model_storage, logger): + """ + List logs for an execution + + EXECUTION_ID is the unique ID of the execution. + """ + logger.info('Listing logs for execution id {0}'.format(execution_id)) + log_iterator = ModelLogIterator(model_storage, execution_id) + + any_logs = execution_logging.log_list(log_iterator, mark_pattern=mark_pattern) + + if not any_logs: + logger.info('\tNo logs') + + +@logs.command(name='delete', + short_help='Delete logs of an execution') +@aria.argument('execution-id') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def delete(execution_id, model_storage, logger): + """ + Delete logs of an execution + + EXECUTION_ID is the unique ID of the execution. + """ + logger.info('Deleting logs for execution id {0}'.format(execution_id)) + logs_list = model_storage.log.list(filters=dict(execution_fk=execution_id)) + for log in logs_list: + model_storage.log.delete(log) + logger.info('Deleted logs for execution id {0}'.format(execution_id)) diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/node_templates.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/node_templates.py new file mode 100644 index 0000000..ec160d2 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/node_templates.py @@ -0,0 +1,100 @@ +# 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. + +""" +CLI ``node-templates`` sub-commands. +""" + +from .. import table +from .. import utils +from ..core import aria + + +NODE_TEMPLATE_COLUMNS = ['id', 'name', 'description', 'service_template_name', 'type_name'] + + +@aria.group(name='node-templates') +@aria.options.verbose() +def node_templates(): + """ + Manages stored service templates' node templates + """ + pass + + +@node_templates.command(name='show', + short_help='Show information for a stored node template') +@aria.argument('node-template-id') +# @aria.options.service_template_name(required=True) +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def show(node_template_id, model_storage, logger): + """ + Show information for a stored node template + + NODE_TEMPLATE_ID is the unique node template ID. + """ + logger.info('Showing node template {0}'.format(node_template_id)) + node_template = model_storage.node_template.get(node_template_id) + + table.print_data(NODE_TEMPLATE_COLUMNS, node_template, 'Node template:', col_max_width=50) + + # print node template properties + logger.info('Node template properties:') + if node_template.properties: + logger.info(utils.get_parameter_templates_as_string(node_template.properties)) + else: + logger.info('\tNo properties') + + # print node IDs + nodes = node_template.nodes + logger.info('Nodes:') + if nodes: + for node in nodes: + logger.info('\t{0}'.format(node.name)) + else: + logger.info('\tNo nodes') + + +@node_templates.command(name='list', + short_help='List stored node templates') +@aria.options.service_template_name() +@aria.options.sort_by('service_template_name') +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(service_template_name, sort_by, descending, model_storage, logger): + """ + List stored node templates + + If SERVICE_TEMPLATE_NAME is provided, list node templates for that stored service template. + Otherwise, list node templates for all service templates. + """ + if service_template_name: + logger.info('Listing node templates for service template {0}...'.format( + service_template_name)) + service_template = model_storage.service_template.get_by_name(service_template_name) + filters = dict(service_template=service_template) + else: + logger.info('Listing all node templates...') + filters = {} + + node_templates_list = model_storage.node_template.list( + filters=filters, + sort=utils.storage_sort_param(sort_by, descending)) + + table.print_data(NODE_TEMPLATE_COLUMNS, node_templates_list, 'Node templates:') diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/nodes.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/nodes.py new file mode 100644 index 0000000..30f1dd4 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/nodes.py @@ -0,0 +1,94 @@ +# 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. + +""" +CLI ``nodes`` sub-commands. +""" + +from .. import table +from .. import utils +from ..core import aria + + +NODE_COLUMNS = ['id', 'name', 'service_name', 'node_template_name', 'state'] + + +@aria.group(name='nodes') +@aria.options.verbose() +def nodes(): + """ + Manage services' nodes + """ + pass + + +@nodes.command(name='show', + short_help='Show information for a node') +@aria.argument('node_id') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def show(node_id, model_storage, logger): + """ + Show information for a node + + NODE_ID is the unique node ID. + """ + logger.info('Showing node {0}'.format(node_id)) + node = model_storage.node.get(node_id) + + table.print_data(NODE_COLUMNS, node, 'Node:', col_max_width=50) + + # print node attributes + logger.info('Node attributes:') + if node.attributes: + for param_name, param in node.attributes.iteritems(): + logger.info('\t{0}: {1}'.format(param_name, param.value)) + else: + logger.info('\tNo attributes') + + +@nodes.command(name='list', + short_help='List node') +@aria.options.service_name(required=False) +@aria.options.sort_by('service_name') +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(service_name, + sort_by, + descending, + model_storage, + logger): + """ + List nodes + + If SERVICE_NAME is provided, list nodes for that service. Otherwise, list nodes for all + services. + """ + if service_name: + logger.info('Listing nodes for service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + filters = dict(service=service) + else: + logger.info('Listing all nodes...') + filters = {} + + nodes_list = model_storage.node.list( + filters=filters, + sort=utils.storage_sort_param(sort_by, descending)) + + table.print_data(NODE_COLUMNS, nodes_list, 'Nodes:') diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/plugins.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/plugins.py new file mode 100644 index 0000000..b5d68a2 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/plugins.py @@ -0,0 +1,111 @@ +# 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. + +""" +CLI ``plugins`` sub-commands. +""" + +from .. import table +from .. import utils +from ..core import aria + + +PLUGIN_COLUMNS = ['id', 'package_name', 'package_version', 'supported_platform', + 'distribution', 'distribution_release', 'uploaded_at'] + + +@aria.group(name='plugins') +@aria.options.verbose() +def plugins(): + """ + Manage plugins + """ + pass + + +@plugins.command(name='validate', + short_help='Validate a plugin archive') +@aria.argument('plugin-path') +@aria.options.verbose() +@aria.pass_plugin_manager +@aria.pass_logger +def validate(plugin_path, plugin_manager, logger): + """ + Validate a plugin archive + + A valid plugin is a wagon (`http://github.com/cloudify-cosmo/wagon`) in the ZIP format (suffix + may also be `.wgn`). + + PLUGIN_PATH is the path to the wagon archive. + """ + logger.info('Validating plugin {0}...'.format(plugin_path)) + plugin_manager.validate_plugin(plugin_path) + logger.info('Plugin validated successfully') + + +@plugins.command(name='install', + short_help='Install a plugin') +@aria.argument('plugin-path') +@aria.options.verbose() +@aria.pass_context +@aria.pass_plugin_manager +@aria.pass_logger +def install(ctx, plugin_path, plugin_manager, logger): + """ + Install a plugin + + A valid plugin is a wagon (`http://github.com/cloudify-cosmo/wagon`) in the ZIP format (suffix + may also be `.wgn`). + + PLUGIN_PATH is the path to the wagon archive. + """ + ctx.invoke(validate, plugin_path=plugin_path) + logger.info('Installing plugin {0}...'.format(plugin_path)) + plugin = plugin_manager.install(plugin_path) + logger.info("Plugin installed. The plugin's id is {0}".format(plugin.id)) + + +@plugins.command(name='show', + short_help='Show information for an installed plugin') +@aria.argument('plugin-id') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def show(plugin_id, model_storage, logger): + """ + Show information for an installed plugin + + PLUGIN_ID is the unique installed plugin ID in this ARIA instance. + """ + logger.info('Showing plugin {0}...'.format(plugin_id)) + plugin = model_storage.plugin.get(plugin_id) + table.print_data(PLUGIN_COLUMNS, plugin, 'Plugin:') + + +@plugins.command(name='list', + short_help='List all installed plugins') +@aria.options.sort_by('uploaded_at') +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(sort_by, descending, model_storage, logger): + """ + List all installed plugins + """ + logger.info('Listing all plugins...') + plugins_list = model_storage.plugin.list( + sort=utils.storage_sort_param(sort_by, descending)).items + table.print_data(PLUGIN_COLUMNS, plugins_list, 'Plugins:') diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/reset.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/reset.py new file mode 100644 index 0000000..c82c707 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/reset.py @@ -0,0 +1,45 @@ +# 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. + +""" +CLI ``reset`` command. +""" + +from .. import helptexts +from ..core import aria +from ..env import env +from ..exceptions import AriaCliError + + +@aria.command(name='reset', + short_help="Reset ARIA working directory") +@aria.options.force(help=helptexts.FORCE_RESET) +@aria.options.reset_config +@aria.pass_logger +@aria.options.verbose() +def reset(force, reset_config, logger): + """ + Reset ARIA working directory + + Deletes installed plugins, service templates, services, executions, and logs. The user + configuration will remain intact unless the `--reset_config` flag has been set as well, in + which case the entire ARIA working directory shall be removed. + """ + if not force: + raise AriaCliError("To reset the ARIA's working directory, you must also provide the force" + " flag ('-f'/'--force').") + + env.reset(reset_config=reset_config) + logger.info("ARIA's working directory has been reset") diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/service_templates.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/service_templates.py new file mode 100644 index 0000000..5a7039c --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/service_templates.py @@ -0,0 +1,244 @@ +# 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. + +""" +CLI ``service-templates`` sub-commands. +""" + +import os + +from .. import csar +from .. import service_template_utils +from .. import table +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) +from ... orchestrator import topology + +DESCRIPTION_FIELD_LENGTH_LIMIT = 20 +SERVICE_TEMPLATE_COLUMNS = \ + ('id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at') + + +@aria.group(name='service-templates') +@aria.options.verbose() +def service_templates(): + """ + Manage service templates + """ + pass + + +@service_templates.command(name='show', + short_help='Show information for a stored service template') +@aria.argument('service-template-name') +@aria.options.verbose() +@aria.pass_model_storage +@aria.options.service_template_mode_full +@aria.options.mode_types +@aria.options.format_json +@aria.options.format_yaml +@aria.pass_logger +def show(service_template_name, model_storage, mode_full, mode_types, format_json, format_yaml, + logger): + """ + Show information for a stored service template + + SERVICE_TEMPLATE_NAME is the unique name of the stored service template. + """ + service_template = model_storage.service_template.get_by_name(service_template_name) + + if format_json or format_yaml: + mode_full = True + + if mode_full: + consumption.ConsumptionContext() + if format_json: + console.puts(formatting.json_dumps(collections.prune(service_template.as_raw))) + elif format_yaml: + console.puts(formatting.yaml_dumps(collections.prune(service_template.as_raw))) + else: + console.puts(topology.Topology().dump(service_template)) + elif mode_types: + console.puts(topology.Topology().dump_types(service_template=service_template)) + 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_name in service_template.services: + logger.info('\t{0}'.format(service_name)) + + +@service_templates.command(name='list', + short_help='List all stored service templates') +@aria.options.sort_by() +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(sort_by, descending, model_storage, logger): + """ + List all stored service templates + """ + + logger.info('Listing all service templates...') + service_templates_list = model_storage.service_template.list( + sort=utils.storage_sort_param(sort_by, descending)) + + column_formatters = \ + dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT)) + table.print_data(SERVICE_TEMPLATE_COLUMNS, service_templates_list, 'Service templates:', + column_formatters=column_formatters) + + +@service_templates.command(name='store', + short_help='Parse and store a service template archive') +@aria.argument('service-template-path') +@aria.argument('service-template-name') +@aria.options.service_template_filename +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def store(service_template_path, service_template_name, service_template_filename, + model_storage, resource_storage, plugin_manager, logger): + """ + Parse and store a service template archive + + SERVICE_TEMPLATE_PATH is the path to the service template archive. + + SERVICE_TEMPLATE_NAME is the unique name to give to the service template in storage. + """ + logger.info('Storing service template {0}...'.format(service_template_name)) + + service_template_path = service_template_utils.get(service_template_path, + service_template_filename) + core = Core(model_storage, resource_storage, plugin_manager) + try: + core.create_service_template(service_template_path, + os.path.dirname(service_template_path), + service_template_name) + except storage_exceptions.StorageError as e: + utils.check_overriding_storage_exceptions(e, 'service template', service_template_name) + raise + logger.info('Service template {0} stored'.format(service_template_name)) + + +@service_templates.command(name='delete', + short_help='Delete a stored service template') +@aria.argument('service-template-name') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def delete(service_template_name, model_storage, resource_storage, plugin_manager, logger): + """ + Delete a stored service template + + SERVICE_TEMPLATE_NAME is the unique name of the stored service template. + """ + logger.info('Deleting service template {0}...'.format(service_template_name)) + service_template = model_storage.service_template.get_by_name(service_template_name) + core = Core(model_storage, resource_storage, plugin_manager) + core.delete_service_template(service_template.id) + logger.info('Service template {0} deleted'.format(service_template_name)) + + +@service_templates.command(name='inputs', + short_help='Show stored service template inputs') +@aria.argument('service-template-name') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def inputs(service_template_name, model_storage, logger): + """ + Show stored service template inputs + + SERVICE_TEMPLATE_NAME is the unique name of the stored service template. + """ + logger.info('Showing inputs for service template {0}...'.format(service_template_name)) + print_service_template_inputs(model_storage, service_template_name, logger) + + +@service_templates.command(name='validate', + short_help='Validate a service template archive') +@aria.argument('service-template') +@aria.options.service_template_filename +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def validate(service_template, service_template_filename, + model_storage, resource_storage, plugin_manager, logger): + """ + Validate a service template archive + + SERVICE_TEMPLATE_PATH is the path to the service template archive. + """ + logger.info('Validating service template: {0}'.format(service_template)) + service_template_path = service_template_utils.get(service_template, service_template_filename) + core = Core(model_storage, resource_storage, plugin_manager) + core.validate_service_template(service_template_path) + logger.info('Service template validated successfully') + + +@service_templates.command(name='create-archive', + short_help='Create a CSAR archive from a service template source') +@aria.argument('service-template-path') +@aria.argument('destination') +@aria.options.verbose() +@aria.pass_logger +def create_archive(service_template_path, destination, logger): + """ + Create a CSAR archive from a service template source + + SERVICE_TEMPLATE_PATH is the path to the service template source. + + DESTINATION is the path to the created CSAR archive. + """ + logger.info('Creating a CSAR archive') + if not destination.endswith(csar.CSAR_FILE_EXTENSION): + destination += csar.CSAR_FILE_EXTENSION + csar.write(service_template_path, destination, logger) + logger.info('CSAR archive created at {0}'.format(destination)) + + +def print_service_template_inputs(model_storage, service_template_name, logger): + service_template = model_storage.service_template.get_by_name(service_template_name) + + logger.info('Service template inputs:') + if service_template.inputs: + logger.info(utils.get_parameter_templates_as_string(service_template.inputs)) + else: + logger.info('\tNo inputs') diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/services.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/services.py new file mode 100644 index 0000000..6752899 --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/services.py @@ -0,0 +1,238 @@ +# 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. + +""" +CLI ``services`` sub-commands. +""" + +import os +from StringIO import StringIO + +from . import service_templates +from .. import helptexts +from .. import table +from .. import utils +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) +from ...orchestrator import topology + + +DESCRIPTION_FIELD_LENGTH_LIMIT = 20 +SERVICE_COLUMNS = ('id', 'name', 'description', 'service_template_name', 'created_at', 'updated_at') + + +@aria.group(name='services') +@aria.options.verbose() +def services(): + """ + Manage services + """ + pass + + +@services.command(name='show', + short_help='Show information for a service') +@aria.argument('service-name') +@aria.options.verbose() +@aria.options.service_mode_full +@aria.options.mode_graph +@aria.options.format_json +@aria.options.format_yaml +@aria.pass_model_storage +@aria.pass_logger +def show(service_name, model_storage, mode_full, mode_graph, format_json, format_yaml, logger): + """ + Show information for a service + + SERVICE_NAME is the unique name of the service. + """ + service = model_storage.service.get_by_name(service_name) + + if format_json or format_yaml: + mode_full = True + + if mode_full: + consumption.ConsumptionContext() + if format_json: + console.puts(formatting.json_dumps(collections.prune(service.as_raw))) + elif format_yaml: + console.puts(formatting.yaml_dumps(collections.prune(service.as_raw))) + else: + console.puts(topology.Topology().dump(service)) + elif mode_graph: + console.puts(topology.Topology().dump_graph(service)) + 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() +@aria.options.descending +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(service_template_name, + sort_by, + descending, + model_storage, + logger): + """ + List services + + If `--service-template-name` is provided, list services based on that service template. + Otherwise, list all services. + """ + if service_template_name: + logger.info('Listing services for service template {0}...'.format( + service_template_name)) + service_template = model_storage.service_template.get_by_name(service_template_name) + filters = dict(service_template=service_template) + else: + logger.info('Listing all services...') + filters = {} + + services_list = model_storage.service.list( + sort=utils.storage_sort_param(sort_by=sort_by, descending=descending), + filters=filters) + table.print_data(SERVICE_COLUMNS, services_list, 'Services:') + + +@services.command(name='create', + short_help='Create a service') +@aria.argument('service-name', required=False) +@aria.options.service_template_name(required=True) +@aria.options.inputs(help=helptexts.SERVICE_INPUTS) +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def create(service_template_name, + service_name, + inputs, # pylint: disable=redefined-outer-name + model_storage, + resource_storage, + plugin_manager, + logger): + """ + Create a service + + SERVICE_NAME is the unique name to give to the service. + """ + logger.info('Creating new service from service template {0}...'.format( + service_template_name)) + core = Core(model_storage, resource_storage, plugin_manager) + service_template = model_storage.service_template.get_by_name(service_template_name) + + try: + service = core.create_service(service_template.id, inputs, service_name) + except storage_exceptions.StorageError as e: + utils.check_overriding_storage_exceptions(e, 'service', service_name) + raise + except modeling_exceptions.ParameterException: + service_templates.print_service_template_inputs(model_storage, service_template_name, + logger) + raise + logger.info("Service created. The service's name is {0}".format(service.name)) + + +@services.command(name='delete', + short_help='Delete a service') +@aria.argument('service-name') +@aria.options.force(help=helptexts.IGNORE_AVAILABLE_NODES) +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_resource_storage +@aria.pass_plugin_manager +@aria.pass_logger +def delete(service_name, force, model_storage, resource_storage, plugin_manager, logger): + """ + Delete a service + + SERVICE_NAME is the unique name of the service. + """ + logger.info('Deleting service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + core = Core(model_storage, resource_storage, plugin_manager) + core.delete_service(service.id, force=force) + logger.info('Service {0} deleted'.format(service_name)) + + +@services.command(name='outputs', + short_help='Show service outputs') +@aria.argument('service-name') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def outputs(service_name, model_storage, logger): + """ + Show service outputs + + SERVICE_NAME is the unique name of the service. + """ + logger.info('Showing outputs for service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + + if service.outputs: + outputs_string = StringIO() + for output_name, output in service.outputs.iteritems(): + outputs_string.write(' - "{0}":{1}'.format(output_name, os.linesep)) + outputs_string.write(' Description: {0}{1}'.format(output.description, os.linesep)) + outputs_string.write(' Value: {0}{1}'.format(output.value, os.linesep)) + logger.info(outputs_string.getvalue()) + else: + logger.info('\tNo outputs') + + +@services.command(name='inputs', + short_help='Show service inputs') +@aria.argument('service-name') +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def inputs(service_name, model_storage, logger): + """ + Show service inputs + + SERVICE_NAME is the unique name of the service. + """ + logger.info('Showing inputs for service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + + if service.inputs: + inputs_string = StringIO() + for input_name, input_ in service.inputs.iteritems(): + inputs_string.write(' - "{0}":{1}'.format(input_name, os.linesep)) + inputs_string.write(' Description: {0}{1}'.format(input_.description, os.linesep)) + inputs_string.write(' Value: {0}{1}'.format(input_.value, os.linesep)) + logger.info(inputs_string.getvalue()) + else: + logger.info('\tNo inputs') diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/workflows.py b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/workflows.py new file mode 100644 index 0000000..ca191aa --- /dev/null +++ b/azure/aria/aria-extension-cloudify/src/aria/aria/cli/commands/workflows.py @@ -0,0 +1,111 @@ +# 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. + +""" +CLI ``worfklows`` sub-commands. +""" + +from .. import table +from ..core import aria +from ..exceptions import AriaCliError + +WORKFLOW_COLUMNS = ['name', 'service_template_name', 'service_name'] + + +@aria.group(name='workflows') +def workflows(): + """ + Manage service workflows + """ + pass + + +@workflows.command(name='show', + short_help='Show information for a service workflow') +@aria.argument('workflow-name') +@aria.options.service_name(required=True) +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def show(workflow_name, service_name, model_storage, logger): + """ + Show information for a service workflow + + SERVICE_NAME is the unique name of the service. + + WORKFLOW_NAME is the unique name of the workflow within the service (e.g. "uninstall"). + """ + logger.info('Retrieving workflow {0} for service {1}'.format( + workflow_name, service_name)) + service = model_storage.service.get_by_name(service_name) + workflow = next((wf for wf in service.workflows.itervalues() if + wf.name == workflow_name), None) + if not workflow: + raise AriaCliError( + 'Workflow {0} not found for service {1}'.format(workflow_name, service_name)) + + defaults = { + 'service_template_name': service.service_template_name, + 'service_name': service.name + } + table.print_data(WORKFLOW_COLUMNS, workflow, 'Workflows:', defaults=defaults) + + # print workflow inputs + required_inputs = dict() + optional_inputs = dict() + for input_name, input in workflow.inputs.iteritems(): + inputs_group = optional_inputs if input.value is not None else required_inputs + inputs_group[input_name] = input + + logger.info('Workflow Inputs:') + logger.info('\tMandatory Inputs:') + for input_name, input in required_inputs.iteritems(): + if input.description is not None: + logger.info('\t\t{0}\t({1})'.format(input_name, + input.description)) + else: + logger.info('\t\t{0}'.format(input_name)) + + logger.info('\tOptional Inputs:') + for input_name, input in optional_inputs.iteritems(): + if input.description is not None: + logger.info('\t\t{0}: \t{1}\t({2})'.format( + input_name, input.value, input.description)) + else: + logger.info('\t\t{0}: \t{1}'.format(input_name, + input.value)) + + +@workflows.command(name='list', + short_help='List service workflows') +@aria.options.service_name(required=True) +@aria.options.verbose() +@aria.pass_model_storage +@aria.pass_logger +def list(service_name, model_storage, logger): + """ + List service workflows + + SERVICE_NAME is the unique name of the service. + """ + logger.info('Listing workflows for service {0}...'.format(service_name)) + service = model_storage.service.get_by_name(service_name) + workflows_list = sorted(service.workflows.itervalues(), key=lambda w: w.name) + + defaults = { + 'service_template_name': service.service_template_name, + 'service_name': service.name + } + table.print_data(WORKFLOW_COLUMNS, workflows_list, 'Workflows:', defaults=defaults) |