# Copyright (c) 2013 Mirantis, Inc. # # Licensed 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 collections import functools import itertools import json import os import shutil import sys import tempfile import uuid import zipfile import jsonpatch from oslo_utils import strutils import six import six.moves from muranoclient.apiclient import exceptions from muranoclient.common import exceptions as common_exceptions from muranoclient.common import utils from muranoclient.v1.package_creator import hot_package from muranoclient.v1.package_creator import mpl_package _bool_from_str_strict = functools.partial( strutils.bool_from_string, strict=True) @utils.arg('--all-tenants', action='store_true', default=False, help='Allows to list environments from all tenants' ' (admin only).') @utils.arg('--tenant', metavar="", default=None, help='Allows to list environments for a given tenant' ' (admin only).') def do_environment_list(mc, args=None): """List the environments.""" if args is None: args = {} all_tenants = getattr(args, 'all_tenants', False) tenant = getattr(args, 'tenant', None) environments = mc.environments.list(all_tenants, tenant) _print_environment_list(environments) def _print_environment_list(environments): field_labels = ['ID', 'Name', 'Status', 'Created', 'Updated'] fields = ['id', 'name', 'status', 'created', 'updated'] utils.print_list(environments, fields, field_labels, sortby=0) def _generate_join_existing_net(net, subnet): res = { 'defaultNetworks': { 'environment': { '?': { 'id': uuid.uuid4().hex, 'type': 'io.murano.resources.ExistingNeutronNetwork' }, }, 'flat': None } } if net: res['defaultNetworks']['environment']['internalNetworkName'] = net if subnet: res['defaultNetworks']['environment']['internalSubnetworkName'] = \ subnet return res @utils.arg("--join-net-id", metavar="", help="Network id to join.",) @utils.arg("--join-subnet-id", metavar="", help="Subnetwork id to join.",) @utils.arg("--region", metavar="", help="Name of the target OpenStack region.",) @utils.arg("name", metavar="", help="Environment name.") def do_environment_create(mc, args): """Create an environment.""" body = {"name": args.name, "region": args.region} if args.join_net_id or args.join_subnet_id: body.update(_generate_join_existing_net( args.join_net_id, args.join_subnet_id)) environment = mc.environments.create(body) _print_environment_list([environment]) @utils.arg("id", metavar="", nargs="+", help="Id or name of environment(s) to delete.") @utils.arg('--abandon', action='store_true', default=False, help='If set will abandon environment without deleting any' ' of its resources.') def do_environment_delete(mc, args): """Delete an environment.""" abandon = getattr(args, 'abandon', False) failure_count = 0 for environment_id in args.id: try: environment = utils.find_resource(mc.environments, environment_id) mc.environments.delete(environment.id, abandon) except exceptions.NotFound: failure_count += 1 print("Failed to delete '{0}'; environment not found". format(environment_id)) if failure_count == len(args.id): raise exceptions.CommandError("Unable to find and delete any of the " "specified environments.") do_environment_list(mc) @utils.arg("id", metavar="", help="Environment ID or name.") @utils.arg("name", metavar="", help="A name to which the environment will be renamed.") def do_environment_rename(mc, args): """Rename an environment.""" try: environment = utils.find_resource(mc.environments, args.id) environment = mc.environments.update(environment.id, args.name) except exceptions.NotFound: raise exceptions.CommandError("Environment %s not found" % args.id) else: _print_environment_list([environment]) @utils.arg("id", metavar="", help="Environment ID or name.") @utils.arg("--session-id", metavar="", default='', help="Id of a config session.") @utils.arg("--only-apps", action='store_true', help="Only print apps of the environment (useful for automation).") def do_environment_show(mc, args): """Display environment details.""" try: environment = utils.find_resource( mc.environments, args.id, session_id=args.session_id) except exceptions.NotFound: raise exceptions.CommandError("Environment %s not found" % args.id) else: if getattr(args, 'only_apps', False): print(utils.json_formatter(environment.services)) else: formatters = { "id": utils.text_wrap_formatter, "created": utils.text_wrap_formatter, "name": utils.text_wrap_formatter, "tenant_id": utils.text_wrap_formatter, "services": utils.json_formatter, } utils.print_dict(environment.to_dict(), formatters=formatters) @utils.arg("id", metavar="", help="ID of Environment to deploy.") @utils.arg("--session-id", metavar="", required=True, help="ID of configuration session to deploy.") def do_environment_deploy(mc, args): """Start deployment of a murano environment session.""" mc.sessions.deploy(args.id, args.session_id) do_environment_show(mc, args) @utils.arg("id", help="ID of Environment to call action against.") @utils.arg("--action-id", metavar="", required=True, help="ID of action to run.") @utils.arg("--arguments", metavar='', nargs='*', help="Action arguments.") def do_environment_action_call(mc, args): """Call action `ACTION` in environment `ID`. Returns id of an asynchronous task, that executes the action. Actions can only be called on a `deployed` environment. To view actions available in a given environment use `environment-show` command. """ arguments = {} for argument in args.arguments or []: if '=' not in argument: raise exceptions.CommandError( "Argument should be in form of KEY=VALUE. Found: {0}".format( argument)) k, v = argument.split('=', 1) try: v = json.loads(v) except ValueError: # treat value as a string if it doesn't load as json pass arguments[k] = v task_id = mc.actions.call( args.id, args.action_id, arguments=arguments) print("Created task, id: {0}".format(task_id)) @utils.arg("id", metavar="", help="ID of Environment where task is being executed.") @utils.arg("--task-id", metavar="", required=True, help="ID of action to run.") def do_environment_action_get_result(mc, args): """Get result of `TASK` in environment `ID`.""" result = mc.actions.get_result(args.id, args.task_id) print("Task id result: {0}".format(result)) @utils.arg("class_name", metavar='', help="FQN of the class with static method") @utils.arg("method_name", metavar='', help="Static method to run") @utils.arg("--arguments", metavar='', nargs='*', help="Method arguments. No arguments by default") @utils.arg("--package-name", metavar='', default='', help='Optional FQN of the package to look for the class in') @utils.arg("--class-version", default='', help='Optional version of the class, otherwise version =0 is ' 'used ') def do_static_action_call(mc, args): """Call static method `METHOD` of the class `CLASS` with `ARGUMENTS`. Returns the result of the method execution. `PACKAGE` and `CLASS_VERSION` can be specified optionally to find class in a particular package and to look for the specific version of a class respectively. """ arguments = {} for argument in args.arguments or []: if '=' not in argument: raise exceptions.CommandError( "Argument should be in form of KEY=VALUE. Found: {0}".format( argument)) key, value = argument.split('=', 1) try: value = json.loads(value) except ValueError: # treat value as a string if it doesn't load as json pass arguments[key] = value request_body = { "className": args.class_name, "methodName": args.method_name, "packageName": args.package_name or None, "classVersion": args.class_version or '=0', "parameters": arguments } print("Waiting for result...") try: result = mc.static_actions.call(request_body).get_result() print("Static action result: {0}".format(result)) except Exception as e: print(str(e)) @utils.arg("id", metavar="", help="ID of Environment to add session to.") def do_environment_session_create(mc, args): """Creates a new configuration session for environment ID.""" environment_id = args.id session_id = mc.sessions.configure(environment_id).id print("Created new session:") formatters = {"id": utils.text_wrap_formatter} utils.print_dict({"id": session_id}, formatters=formatters) @utils.arg("id", metavar="", help="ID of Environment to edit.") @utils.arg("filename", metavar="FILE", nargs="?", help="File to read jsonpatch from (defaults to stdin).") @utils.arg("--session-id", metavar="", required=True, help="Id of a config session.") def do_environment_apps_edit(mc, args): """Edit environment's object model. `FILE` is path to a file, that contains jsonpatch, that describes changes to be made to environment's object-model. [ { "op": "add", "path": "/-", "value": { ... your-app object model here ... } }, { "op": "replace", "path": "/0/?/name", "value": "new_name" }, ] NOTE: Values '===id1===', '===id2===', etc. in the resulting object-model will be substituted with uuids. For more info on jsonpatch see RFC 6902 """ jp_obj = None if not args.filename: jp_obj = json.load(sys.stdin) else: with open(args.filename) as fpatch: jp_obj = json.load(fpatch) jpatch = jsonpatch.JsonPatch(jp_obj) environment_id = args.id session_id = args.session_id environment = mc.environments.get(environment_id, session_id) object_model = jpatch.apply(environment.services) utils.traverse_and_replace(object_model) mc.services.put( environment_id, path='/', data=jpatch.apply(environment.services), session_id=session_id) @utils.arg("id", metavar="", help="ID of Environment to show.") @utils.arg("--path", metavar="", default='/', help="Path to Environment model section. Defaults to '/'.") @utils.arg("--session-id", metavar="", help="Id of a config session.") def do_environment_model_show(mc, args): """Display an environment's object model.""" session_id = args.session_id or None path = six.moves.urllib.parse.quote(args.path) env_model = mc.environments.get_model(args.id, path, session_id) print(utils.json_formatter(env_model)) @utils.arg("id", metavar="", help="ID of Environment to edit.") @utils.arg("filename", metavar="", nargs="?", help="File to read JSON-patch from (defaults to stdin).") @utils.arg("--session-id", metavar="", required=True, help="Id of a config session.") def do_environment_model_edit(mc, args): """Edit an environment's object model.""" jp_obj = None if not args.filename: jp_obj = json.load(sys.stdin) else: with open(args.filename) as fpatch: jp_obj = json.load(fpatch) if not isinstance(jp_obj, list): raise exceptions.CommandError('JSON-patch must be a list of changes') for change in jp_obj: if 'op' not in change or 'path' not in change: raise exceptions.CommandError('Every change in JSON-patch must ' 'contain "op" and "path" keys') op = change['op'] if op not in ['add', 'replace', 'remove']: raise exceptions.CommandError('The value of "op" item must be ' '"add", "replace" or "remove", ' 'got {0}'.format(op)) if op != 'remove' and 'value' not in change: raise exceptions.CommandError('"add" or "replace" change in ' 'JSON-patch must contain "value" ' 'key') session_id = args.session_id new_model = mc.environments.update_model(args.id, jp_obj, session_id) print(utils.json_formatter(new_model)) def do_env_template_list(mc, args=None): """List the environments templates.""" if args is None: args = {} env_templates = mc.env_templates.list() _print_env_template_list(env_templates) def _print_env_template_list(env_templates): field_labels = ['ID', 'Name', 'Created', 'Updated', 'Is public'] fields = ['id', 'name', 'created', 'updated', 'is_public'] utils.print_list(env_templates, fields, field_labels, sortby=0) @utils.arg("name", metavar="", help="Environment template name.") @utils.arg("--is-public", action='store_true', default=False, help='Make the template available for users from other tenants.') def do_env_template_create(mc, args): """Create an environment template.""" env_template = mc.env_templates.create( {"name": args.name, "is_public": args.is_public}) _print_env_template_list([env_template]) @utils.arg("id", metavar="", help="Environment template ID.") @utils.arg("name", metavar="", help="New environment name.") @utils.arg("--region", metavar="", help="Name of the target OpenStack region.",) def do_env_template_create_env(mc, args): """Create a new environment from template.""" try: data = {} data["name"] = args.name if args.region: data["region"] = args.region template = mc.env_templates.create_env(args.id, data) except common_exceptions.HTTPNotFound: raise exceptions.CommandError("Environment template %s not found" % args.id) else: formatters = { "environment_id": utils.text_wrap_formatter, "session_id": utils.text_wrap_formatter } utils.print_dict(template.to_dict(), formatters=formatters) @utils.arg("id", metavar="", help="Environment template ID.") def do_env_template_show(mc, args): """Display environment template details.""" try: env_template = mc.env_templates.get(args.id) except common_exceptions.HTTPNotFound: raise exceptions.CommandError("Environment template %s not found" % args.id) else: formatters = { "id": utils.text_wrap_formatter, "created": utils.text_wrap_formatter, "name": utils.text_wrap_formatter, "tenant_id": utils.text_wrap_formatter, "services": utils.json_formatter, } utils.print_dict(env_template.to_dict(), formatters=formatters) @utils.arg("id", metavar="", help="Environment template ID.") @utils.arg('app_template_file', metavar='', help='Path to the template.') def do_env_template_add_app(mc, args): """Add application to the environment template.""" with open(args.app_template_file, "r") as app_file: app_templates = json.load(app_file) if not isinstance(app_templates, list): app_templates = [app_templates] for app_template in app_templates: mc.env_templates.create_app(args.id, app_template) do_env_template_show(mc, args) @utils.arg("id", metavar="", help="Environment template ID.") @utils.arg("app_id", metavar="", help="Application ID.") def do_env_template_del_app(mc, args): """Delete application from the environment template.""" mc.env_templates.delete_app(args.id, args.app_id) do_env_template_show(mc, args) @utils.arg("id", metavar="", help="Environment template ID.") @utils.arg("name", metavar="", help="Environment template name.") def do_env_template_update(mc, args): """Update an environment template.""" env_template = mc.env_templates.update(args.id, args.name) _print_env_template_list([env_template]) @utils.arg("id", metavar="", nargs="+", help="ID of environment(s) template to delete.") def do_env_template_delete(mc, args): """Delete an environment template.""" failure_count = 0 for env_template_id in args.id: try: mc.env_templates.delete(env_template_id) except common_exceptions.HTTPNotFound: failure_count += 1 mns = "Failed to delete '{0}'; environment template not found".\ format(env_template_id) if failure_count == len(args.id): raise exceptions.CommandError(mns) do_env_template_list(mc) @utils.arg("id", metavar="", help="Environment template ID.") @utils.arg("name", metavar="", help="New environment template name.") def do_env_template_clone(mc, args): """Create a new template, cloned from template.""" try: env_template = mc.env_templates.clone(args.id, args.name) except common_exceptions.HTTPNotFound: raise exceptions.CommandError("Environment template %s not found" % args.id) else: formatters = { "id": utils.text_wrap_formatter, "created": utils.text_wrap_formatter, "updated": utils.text_wrap_formatter, "version": utils.text_wrap_formatter, "name": utils.text_wrap_formatter, "tenant_id": utils.text_wrap_formatter, "is_public": utils.text_wrap_formatter, "services": utils.json_formatter, } utils.print_dict(env_template.to_dict(), formatters=formatters) @utils.arg("id", metavar="", nargs='?', default=None, help="Environment ID for which to list deployments.") @utils.arg('--all-environments', action='store_true', default=False, help="Lists all deployments for all environments in user's tenant.") def do_deployment_list(mc, args): """List deployments for an environment or multiple environments.""" all_environments = getattr(args, 'all_environments', False) env_id = getattr(args, 'id', None) if env_id and all_environments: raise exceptions.CommandError( 'Environment ID and all-environments flag cannot both be set.') elif not env_id and not all_environments: raise exceptions.CommandError( 'Either environment ID or all-environments flag must be set.') try: if all_environments: deployments = mc.deployments.list(None, all_environments) else: environment = utils.find_resource(mc.environments, env_id) deployments = mc.deployments.list(environment.id) except exceptions.NotFound: if env_id: msg = "Environment %s not found" % env_id else: msg = "Environments not found" raise exceptions.CommandError(msg) else: field_labels = ["ID", "State", "Created", "Updated", "Finished"] fields = ["id", "state", "created", "updated", "finished"] utils.print_list(deployments, fields, field_labels, sortby=0) @utils.arg("--limit", type=int, default=0, help='Show limited number of packages') @utils.arg("--marker", default='', help='Show packages starting from package with id excluding it') @utils.arg("--include-disabled", default=False, action="store_true") @utils.arg("--owned", default=False, action="store_true") @utils.arg('--search', metavar='', dest='search', required=False, help='Show packages, that match search keys fuzzily') @utils.arg('--name', metavar='', dest='name', required=False, help='Show packages, whose name match parameter exactly') @utils.arg('--fqn', metavar="", dest='fqn', required=False, help='Show packages, ' 'whose fully qualified name match parameter exactly') @utils.arg('--type', metavar='', dest='type', required=False, help='Show packages, whose type match parameter exactly') @utils.arg('--category', metavar='', dest='category', required=False, help='Show packages, whose categories include parameter') @utils.arg('--class_name', metavar='', dest='class_name', required=False, help='Show packages, whose class name match parameter exactly') @utils.arg('--tag', metavar='', dest='tag', required=False, help='Show packages, whose tags include parameter') def do_package_list(mc, args=None): """List available packages.""" filter_args = { "include_disabled": getattr(args, 'include_disabled', False), "owned": getattr(args, 'owned', False), } if args: if args.limit < 0: raise exceptions.CommandError( '--limit parameter must be non-negative') if args.limit != 0: filter_args['limit'] = args.limit if args.marker: filter_args['marker'] = args.marker if args.search: filter_args['search'] = args.search if args.name: filter_args['name'] = args.name if args.fqn: filter_args['fqn'] = args.fqn if args.type: filter_args['type'] = args.type if args.category: filter_args['category'] = args.category if args.class_name: filter_args['class_name'] = args.class_name if args.tag: filter_args['tag'] = args.tag packages = mc.packages.filter(**filter_args) if not args or args.limit == 0: _print_package_list(packages) else: _print_package_list(itertools.islice(packages, args.limit)) def _print_package_list(packages): field_labels = ["ID", "Name", "FQN", "Author", "Active", "Is Public", "Type", "Version"] fields = ["id", "name", "fully_qualified_name", "author", "enabled", "is_public", "type", "version"] utils.print_list(packages, fields, field_labels, sortby=0) @utils.arg("id", metavar="", help="Package ID to download.") @utils.arg("filename", metavar="file", nargs="?", help="Filename to save package to. If it is not specified and " "there is no stdout redirection the package won't be saved.") def do_package_download(mc, args): """Download a package to a filename or stdout.""" def download_to_fh(package_id, fh): fh.write(mc.packages.download(package_id)) try: if args.filename: with open(args.filename, 'wb') as fh: download_to_fh(args.id, fh) print("Package downloaded to %s" % args.filename) elif not sys.stdout.isatty(): download_to_fh(args.id, sys.stdout) else: msg = ('No stdout redirection or local file specified for ' 'downloaded package. Please specify a local file to save ' 'downloaded package or redirect output to another source.') raise exceptions.CommandError(msg) except common_exceptions.HTTPNotFound: raise exceptions.CommandError("Package %s not found" % args.id) @utils.arg("id", metavar="", help="Package ID to show.") def do_package_show(mc, args): """Display details for a package.""" try: package = mc.packages.get(args.id) except common_exceptions.HTTPNotFound: raise exceptions.CommandError("Package %s not found" % args.id) else: to_display = dict( id=package.id, type=package.type, owner_id=package.owner_id, name=package.name, fully_qualified_name=package.fully_qualified_name, is_public=package.is_public, enabled=package.enabled, class_definitions=", ".join(package.class_definitions), categories=", ".join(package.categories), tags=", ".join(package.tags), description=package.description ) formatters = { 'class_definitions': utils.text_wrap_formatter, 'categories': utils.text_wrap_formatter, 'tags': utils.text_wrap_formatter, 'description': utils.text_wrap_formatter, } utils.print_dict(to_display, formatters) @utils.arg("id", metavar="", nargs='+', help="Package ID to delete.") def do_package_delete(mc, args): """Delete a package.""" failure_count = 0 for package_id in args.id: try: mc.packages.delete(package_id) print("Deleted package '{0}'".format(package_id)) except exceptions.NotFound: failure_count += 1 print("Failed to delete '{0}'; package not found". format(package_id)) if failure_count == len(args.id): raise exceptions.CommandError("Unable to find and delete any of the " "specified packages.") else: do_package_list(mc) def _handle_package_exists(mc, data, package, exists_action): name = package.manifest['FullName'] version = package.manifest.get('Version', '0') while True: print("Importing package {0}".format(name)) try: return mc.packages.create(data, {name: package.file()}) except common_exceptions.HTTPConflict: print("Importing package {0} failed. Package with the same" " name/classes is already registered.".format(name)) allowed_results = ['s', 'u', 'a'] res = exists_action if not res: while True: print("What do you want to do? (s)kip, (u)pdate, (a)bort") res = six.moves.input() if res in allowed_results: break if res == 's': print("Skipping.") return None elif res == 'a': print("Exiting.") sys.exit() elif res == 'u': pkgs = list(mc.packages.filter(fqn=name, version=version, owned=True)) if not pkgs: msg = ( "Got a conflict response, but could not find the " "package '{0}' in the current tenant.\nThis probably " "means the conflicting package is in another tenant.\n" "Please delete it manually." ).format(name) raise exceptions.CommandError(msg) elif len(pkgs) > 1: msg = ( "Got {0} packages with name '{1}'.\nI do not trust " "myself, please delete the package manually." ).format(len(pkgs), name) raise exceptions.CommandError(msg) print("Deleting package {0}({1})".format(name, pkgs[0].id)) mc.packages.delete(pkgs[0].id) continue @utils.arg('filename', metavar='', nargs='+', help='URL of the murano zip package, FQPN, path to zip package' ' or path to directory with package.') @utils.arg('-c', '--categories', metavar='', nargs='*', help='Category list to attach.') @utils.arg('--is-public', action='store_true', default=False, help='Make the package available for users from other tenants.') @utils.arg('--package-version', default='', help='Version of the package to use from repository ' '(ignored when importing with multiple packages).') @utils.arg('--exists-action', default='', choices=['a', 's', 'u'], help='Default action when a package already exists: ' '(s)kip, (u)pdate, (a)bort.') @utils.arg('--dep-exists-action', default='', choices=['a', 's', 'u'], help='Default action when a dependency package already exists: ' '(s)kip, (u)pdate, (a)bort.') def do_package_import(mc, args): """Import a package. `FILE` can be either a path to a zip file, url or a FQPN. You can use `--` to separate `FILE`s from other arguments. Categories have to be separated with a space and have to be already present in murano. """ data = {"is_public": args.is_public} exception_occurred = False version = args.package_version if version and len(args.filename) >= 2: print("Requested to import more than one package, " "ignoring version.") version = '' if args.categories: data["categories"] = args.categories total_reqs = collections.OrderedDict() main_packages_names = [] for filename in args.filename: if os.path.isfile(filename) or os.path.isdir(filename): _file = filename else: print("Package file '{0}' does not exist, attempting to download" "".format(filename)) _file = utils.to_url( filename, version=version, base_url=args.murano_repo_url, extension='.zip', path='apps/', ) try: package = utils.Package.from_file(_file) except Exception as e: print("Failed to create package for '{0}', reason: {1}".format( filename, e)) exception_occurred = True continue total_reqs.update(package.requirements(base_url=args.murano_repo_url)) main_packages_names.append(package.manifest['FullName']) imported_list = [] dep_exists_action = args.dep_exists_action if dep_exists_action == '': dep_exists_action = args.exists_action for name, package in six.iteritems(total_reqs): image_specs = package.images() if image_specs: print("Inspecting required images") try: imgs = utils.ensure_images( glance_client=mc.glance_client, image_specs=image_specs, base_url=args.murano_repo_url, is_package_public=args.is_public) for img in imgs: print("Added {0}, {1} image".format( img['name'], img['id'])) except Exception as e: print("Error {0} occurred while installing " "images for {1}".format(e, name)) exception_occurred = True if name in main_packages_names: exists_action = args.exists_action else: exists_action = dep_exists_action try: imported_package = _handle_package_exists( mc, data, package, exists_action) if imported_package: imported_list.append(imported_package) except Exception as e: print("Error {0} occurred while installing package {1}".format( e, name)) exception_occurred = True if imported_list: _print_package_list(imported_list) if exception_occurred: # NOTE(jose-phillips) Leave a Warning to users in case some packages # can be uploaded successfully. if imported_list: print("Warning: there were some errors during the operation.") sys.exit(1) else: sys.exit(1) @utils.arg("id", metavar="", help="Package ID to update.") @utils.arg('--is-public', type=_bool_from_str_strict, metavar='{true|false}', help='Make package available to users from other tenants.') @utils.arg('--enabled', type=_bool_from_str_strict, metavar='{true|false}', help='Make package active and available for deployments.') @utils.arg('--name', default=None, help='New name for the package.') @utils.arg('--description', default=None, help='New package description.') @utils.arg('--tags', metavar='', nargs='*', default=None, help='A list of keywords connected to the application.') def do_package_update(mc, args): """Update an existing package.""" data = {} parameters = ('is_public', 'enabled', 'name', 'description', 'tags') for parameter in parameters: param_value = getattr(args, parameter, None) if param_value is not None: data[parameter] = param_value mc.packages.update(args.id, data) do_package_show(mc, args) @utils.arg('filename', metavar='', nargs='+', help='Bundle URL, bundle name, or path to the bundle file.') @utils.arg('--is-public', action='store_true', default=False, help='Make packages available to users from other tenants.') @utils.arg('--exists-action', default='', choices=['a', 's', 'u'], help='Default action when a package already exists.') def do_bundle_import(mc, args): """Import a bundle. `FILE` can be either a path to a zip file, URL, or name from repo. If `FILE` is a local file, treat names of packages in a bundle as file names, relative to location of the bundle file. Requirements are first searched in the same directory. """ total_reqs = collections.OrderedDict() for filename in args.filename: local_path = None if os.path.isfile(filename): _file = filename local_path = os.path.dirname(os.path.abspath(filename)) else: print("Bundle file '{0}' does not exist, attempting to download" "".format(filename)) _file = utils.to_url( filename, base_url=args.murano_repo_url, path='bundles/', extension='.bundle', ) try: bundle_file = utils.Bundle.from_file(_file) except Exception as e: print("Failed to create bundle for '{0}', reason: {1}".format( filename, e)) continue data = {"is_public": args.is_public} for package in bundle_file.packages( base_url=args.murano_repo_url, path=local_path): requirements = package.requirements( base_url=args.murano_repo_url, path=local_path, ) total_reqs.update(requirements) imported_list = [] for name, dep_package in six.iteritems(total_reqs): image_specs = dep_package.images() if image_specs: print("Inspecting required images") try: imgs = utils.ensure_images( glance_client=mc.glance_client, image_specs=image_specs, base_url=args.murano_repo_url, local_path=local_path, is_package_public=args.is_public) for img in imgs: print("Added {0}, {1} image".format( img['name'], img['id'])) except Exception as e: print("Error {0} occurred while installing " "images for {1}".format(e, name)) try: imported_package = _handle_package_exists( mc, data, dep_package, args.exists_action) if imported_package: imported_list.append(imported_package) except exceptions.CommandError: raise except Exception as e: print("Error {0} occurred while " "installing package {1}".format(e, name)) if imported_list: _print_package_list(imported_list) def _handle_save_packages(packages, dst, base_url, no_images): downloaded_images = [] for name, pkg in six.iteritems(packages): if not no_images: image_specs = pkg.images() for image_spec in image_specs: if not image_spec["Name"]: print("Invalid image.lst file for {0} package. " "'Name' section is absent.".format(name)) continue if image_spec["Name"] not in downloaded_images: print("Package {0} depends on image {1}. " "Downloading...".format(name, image_spec["Name"])) try: utils.save_image_local(image_spec, base_url, dst) downloaded_images.append(image_spec["Name"]) except Exception as e: print("Error {0} occurred while saving image {1}". format(e, image_spec["Name"])) try: pkg.save(dst) print("Package {0} has been successfully saved".format(name)) except Exception as e: print("Error {0} occurred while saving package {1}".format( e, name)) @utils.arg('filename', metavar='', help='Bundle URL, bundle name, or path to the bundle file.') @utils.arg('-p', '--path', metavar='', help='Path to the directory to store packages. If not set will use ' 'current directory.') @utils.arg('--no-images', action='store_true', default=False, help='If set will skip images downloading.') def do_bundle_save(mc, args): """Save a bundle. This will download a bundle of packages with all dependencies to specified path. If path doesn't exist it will be created. """ bundle = args.filename base_url = args.murano_repo_url if args.path: if not os.path.exists(args.path): os.makedirs(args.path) dst = args.path else: dst = os.getcwd() total_reqs = collections.OrderedDict() if os.path.isfile(bundle): _file = bundle else: print("Bundle file '{0}' does not exist, attempting to download" .format(bundle)) _file = utils.to_url( bundle, base_url=base_url, path='bundles/', extension='.bundle', ) try: bundle_file = utils.Bundle.from_file(_file) except Exception as e: msg = "Failed to create bundle for {0}, reason: {1}".format(bundle, e) raise exceptions.CommandError(msg) for package in bundle_file.packages(base_url=base_url): requirements = package.requirements(base_url=base_url) total_reqs.update(requirements) no_images = getattr(args, 'no_images', False) _handle_save_packages(total_reqs, dst, base_url, no_images) try: bundle_file.save(dst, binary=False) print("Bundle file {0} has been successfully saved".format(bundle)) except Exception as e: print("Error {0} occurred while saving bundle {1}".format(e, bundle)) @utils.arg('package', metavar='', nargs='+', help='Package URL or name.') @utils.arg('-p', '--path', metavar='', help='Path to the directory to store package. If not set will use ' 'current directory.') @utils.arg('--package-version', default='', help='Version of the package to use from repository ' '(ignored when saving with multiple packages).') @utils.arg('--no-images', action='store_true', default=False, help='If set will skip images downloading.') def do_package_save(mc, args): """Save a package. This will download package(s) with all dependencies to specified path. If path doesn't exist it will be created. """ base_url = args.murano_repo_url if args.path: if not os.path.exists(args.path): os.makedirs(args.path) dst = args.path else: dst = os.getcwd() version = args.package_version if version and len(args.filename) >= 2: print("Requested to save more than one package, " "ignoring version.") version = '' total_reqs = collections.OrderedDict() for package in args.package: _file = utils.to_url( package, version=version, base_url=base_url, extension='.zip', path='apps/', ) try: pkg = utils.Package.from_file(_file) except Exception as e: print("Failed to create package for '{0}', reason: {1}".format( package, e)) continue total_reqs.update(pkg.requirements(base_url=base_url)) no_images = getattr(args, 'no_images', False) _handle_save_packages(total_reqs, dst, base_url, no_images) @utils.arg('id', metavar='', help='Environment ID to show applications from.') @utils.arg('-p', '--path', metavar='', help='Level of detalization to show. ' 'Leave empty to browse all applications in the environment.', default='/') def do_app_show(mc, args): """List applications, added to specified environment.""" if args.path == '/': apps = mc.services.list(args.id) formatters = {'id': lambda x: getattr(x, '?')['id'], 'type': lambda x: getattr(x, '?')['type']} field_labels = ['Id', 'Name', 'Type'] fields = ['id', 'name', 'type'] utils.print_list(apps, fields, field_labels, formatters=formatters) else: if not args.path.startswith('/'): args.path = '/' + args.path app = mc.services.get(args.id, args.path) # If app with specified path is not found, it is empty. if hasattr(app, '?'): formatters = {} for key in app.to_dict().keys(): formatters[key] = utils.json_formatter utils.print_dict(app.to_dict(), formatters) else: raise exceptions.CommandError("Could not find application at path" " %s" % args.path) @utils.arg('-t', '--template', metavar='', help='Path to the Heat template to import as ' 'an Application Definition.') @utils.arg('-c', '--classes-dir', metavar='', help='Path to the directory containing application classes.') @utils.arg('-r', '--resources-dir', metavar='', help='Path to the directory containing application resources.') @utils.arg('-n', '--name', metavar='', help='Display name of the Application in Catalog.') @utils.arg('-f', '--full-name', metavar='', help='Fully-qualified name of the Application in Catalog.') @utils.arg('-a', '--author', metavar='', help='Name of the publisher.') @utils.arg('--tags', help='A list of keywords connected to the application.', metavar='', nargs='*') @utils.arg('-d', '--description', metavar='', help='Detailed description for the Application in Catalog.') @utils.arg('-o', '--output', metavar='', help='The name of the output file archive to save locally.') @utils.arg('-u', '--ui', metavar='', help='Dynamic UI form definition.') @utils.arg('--type', help='Package type. Possible values: Application or Library.') @utils.arg('-l', '--logo', metavar='', help='Path to the package logo.') def do_package_create(mc, args): """Create an application package.""" if args.template and args.classes_dir: raise exceptions.CommandError( "Provide --template for a HOT-based package, OR" " --classes-dir for a MuranoPL-based package") if not args.template and not args.classes_dir: raise exceptions.CommandError( "Provide --template for a HOT-based package, OR at least" " --classes-dir for a MuranoPL-based package") directory_path = None try: archive_name = args.output if args.output else None if args.template: directory_path = hot_package.prepare_package(args) if not archive_name: archive_name = os.path.basename(args.template) archive_name = os.path.splitext(archive_name)[0] + ".zip" else: directory_path = mpl_package.prepare_package(args) if not archive_name: archive_name = tempfile.mkstemp( prefix="murano_", dir=os.getcwd())[1] + ".zip" _make_archive(archive_name, directory_path) print("Application package is available at " + os.path.abspath(archive_name)) finally: if directory_path: shutil.rmtree(directory_path) def _make_archive(archive_name, path): zip_file = zipfile.ZipFile(archive_name, 'w') for root, dirs, files in os.walk(path): for f in files: zip_file.write(os.path.join(root, f), arcname=os.path.join(os.path.relpath(root, path), f)) def do_category_list(mc, args=None): """List all available categories.""" if args is None: args = {} categories = mc.categories.list() _print_category_list(categories) def _print_category_list(categories): field_labels = ["ID", "Name"] fields = ["id", "name"] utils.print_list(categories, fields, field_labels) @utils.arg("id", metavar="", help="ID of a category(s) to show.") def do_category_show(mc, args): """Display category details.""" category = mc.categories.get(args.id) packages = mc.packages.filter(category=category.name) to_display = dict(id=category.id, name=category.name, packages=', '.join(p.name for p in packages)) formatters = {'packages': utils.text_wrap_formatter} utils.print_dict(to_display, formatters) @utils.arg("name", metavar="", help="Category name.") def do_category_create(mc, args): """Create a category.""" category = mc.categories.add({"name": args.name}) _print_category_list([category]) @utils.arg("id", metavar="", nargs="+", help="ID of a category(ies) to delete.") def do_category_delete(mc, args): """Delete a category.""" failure_count = 0 for category_id in args.id: try: mc.categories.delete(category_id) except common_exceptions.HTTPNotFound: failure_count += 1 print("Failed to delete '{0}'; category not found". format(category_id)) if failure_count == len(args.id): raise exceptions.CommandError("Unable to find and delete any of the " "specified categories.") do_category_list(mc) @utils.arg("class_name", metavar="", help="Class FQN") @utils.arg("method_names", metavar="", help="Method name", nargs='*') @utils.arg("--package-name", default=None, help="FQN of the package where the class is located") @utils.arg("--class-version", default='=0', help="Class version or version range (version spec)") def do_class_schema(mc, args): """Display class schema""" schema = mc.schemas.get(args.class_name, args.method_names, class_version=args.class_version, package_name=args.package_name) print(utils.json_formatter(schema.data))