diff options
Diffstat (limited to 'openstackclient/image')
| -rw-r--r-- | openstackclient/image/v2/image.py | 493 | ||||
| -rw-r--r-- | openstackclient/image/v2/metadef_namespaces.py | 247 |
2 files changed, 670 insertions, 70 deletions
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 2342fd3e..71dcc731 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -22,6 +22,7 @@ import os import sys from cinderclient import api_versions +from openstack import exceptions as sdk_exceptions from openstack.image import image_signer from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns @@ -29,11 +30,10 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import uuidutils from openstackclient.common import progressbar from openstackclient.i18n import _ -from openstackclient.identity import common +from openstackclient.identity import common as identity_common if os.name == "nt": import msvcrt @@ -134,34 +134,32 @@ def _get_member_columns(item): ) -def get_data_file(args): - if args.file: - return (open(args.file, 'rb'), args.file) +def get_data_from_stdin(): + # distinguish cases where: + # (1) stdin is not valid (as in cron jobs): + # openstack ... <&- + # (2) image data is provided through stdin: + # openstack ... < /tmp/file + # (3) no image data provided + # openstack ... + try: + os.fstat(0) + except OSError: + # (1) stdin is not valid + return None + + if not sys.stdin.isatty(): + # (2) image data is provided through stdin + image = sys.stdin + if hasattr(sys.stdin, 'buffer'): + image = sys.stdin.buffer + if msvcrt: + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + + return image else: - # distinguish cases where: - # (1) stdin is not valid (as in cron jobs): - # openstack ... <&- - # (2) image data is provided through stdin: - # openstack ... < /tmp/file - # (3) no image data provided - # openstack ... - try: - os.fstat(0) - except OSError: - # (1) stdin is not valid - return (None, None) - if not sys.stdin.isatty(): - # (2) image data is provided through stdin - image = sys.stdin - if hasattr(sys.stdin, 'buffer'): - image = sys.stdin.buffer - if msvcrt: - msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) - - return (image, None) - else: - # (3) - return (None, None) + # (3) + return None class AddProjectToImage(command.ShowOne): @@ -179,24 +177,22 @@ class AddProjectToImage(command.ShowOne): metavar="<project>", help=_("Project to associate with image (ID)"), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity - if uuidutils.is_uuid_like(parsed_args.project): - project_id = parsed_args.project - else: - project_id = common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id image = image_client.find_image( - parsed_args.image, ignore_missing=False + parsed_args.image, + ignore_missing=False, ) obj = image_client.add_member( @@ -277,6 +273,7 @@ class CreateImage(command.ShowOne): source_group = parser.add_mutually_exclusive_group() source_group.add_argument( "--file", + dest="filename", metavar="<file>", help=_("Upload image from local file"), ) @@ -299,7 +296,10 @@ class CreateImage(command.ShowOne): "--progress", action="store_true", default=False, - help=_("Show upload progress bar."), + help=_( + "Show upload progress bar " + "(ignored if passing data via stdin)" + ), ) parser.add_argument( '--sign-key-path', @@ -398,7 +398,7 @@ class CreateImage(command.ShowOne): "Force the use of glance image import instead of direct upload" ), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, @@ -451,7 +451,7 @@ class CreateImage(command.ShowOne): kwargs['visibility'] = parsed_args.visibility if parsed_args.project: - kwargs['owner_id'] = common.find_project( + kwargs['owner_id'] = identity_common.find_project( identity_client, parsed_args.project, parsed_args.project_domain, @@ -463,7 +463,15 @@ class CreateImage(command.ShowOne): # open the file first to ensure any failures are handled before the # image is created. Get the file name (if it is file, and not stdin) # for easier further handling. - fp, fname = get_data_file(parsed_args) + if parsed_args.filename: + try: + fp = open(parsed_args.filename, 'rb') + except FileNotFoundError: + raise exceptions.CommandError( + '%r is not a valid file' % parsed_args.filename, + ) + else: + fp = get_data_from_stdin() if fp is not None and parsed_args.volume: msg = _( @@ -472,24 +480,24 @@ class CreateImage(command.ShowOne): ) raise exceptions.CommandError(msg) - if fp is None and parsed_args.file: - LOG.warning(_("Failed to get an image file.")) - return {}, {} - - if fp is not None and parsed_args.progress: - filesize = os.path.getsize(fname) + if parsed_args.progress and parsed_args.filename: + # NOTE(stephenfin): we only show a progress bar if the user + # requested it *and* we're reading from a file (not stdin) + filesize = os.path.getsize(parsed_args.filename) if filesize is not None: kwargs['validate_checksum'] = False kwargs['data'] = progressbar.VerboseFileWrapper(fp, filesize) - elif fname: - kwargs['filename'] = fname + else: + kwargs['data'] = fp + elif parsed_args.filename: + kwargs['filename'] = parsed_args.filename elif fp: kwargs['validate_checksum'] = False kwargs['data'] = fp # sign an image using a given local private key file if parsed_args.sign_key_path or parsed_args.sign_cert_id: - if not parsed_args.file: + if not parsed_args.filename: msg = _( "signing an image requires the --file option, " "passing files via stdin when signing is not " @@ -544,6 +552,10 @@ class CreateImage(command.ShowOne): kwargs['img_signature_key_type'] = signer.padding_method image = image_client.create_image(**kwargs) + + if parsed_args.filename: + fp.close() + return _format_image(image) def _take_action_volume(self, parsed_args): @@ -653,7 +665,8 @@ class DeleteImage(command.Command): for image in parsed_args.images: try: image_obj = image_client.find_image( - image, ignore_missing=False + image, + ignore_missing=False, ) image_client.delete_image(image_obj.id) except Exception as e: @@ -754,7 +767,7 @@ class ListImage(command.Lister): metavar='<project>', help=_("Search by project (admin only) (name or ID)"), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--tag', metavar='<tag>', @@ -824,7 +837,10 @@ class ListImage(command.Lister): if parsed_args.limit: kwargs['limit'] = parsed_args.limit if parsed_args.marker: - kwargs['marker'] = image_client.find_image(parsed_args.marker).id + kwargs['marker'] = image_client.find_image( + parsed_args.marker, + ignore_missing=False, + ).id if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.status: @@ -835,7 +851,7 @@ class ListImage(command.Lister): kwargs['tag'] = parsed_args.tag project_id = None if parsed_args.project: - project_id = common.find_project( + project_id = identity_common.find_project( identity_client, parsed_args.project, parsed_args.project_domain, @@ -914,14 +930,17 @@ class ListImageProjects(command.Lister): metavar="<image>", help=_("Image (name or ID)"), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image columns = ("Image ID", "Member ID", "Status") - image_id = image_client.find_image(parsed_args.image).id + image_id = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ).id data = image_client.members(image=image_id) @@ -952,19 +971,22 @@ class RemoveProjectImage(command.Command): metavar="<project>", help=_("Project to disassociate with image (name or ID)"), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity - project_id = common.find_project( - identity_client, parsed_args.project, parsed_args.project_domain + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, ).id image = image_client.find_image( - parsed_args.image, ignore_missing=False + parsed_args.image, + ignore_missing=False, ) image_client.remove_member(member=project_id, image=image.id) @@ -978,6 +1000,7 @@ class SaveImage(command.Command): parser.add_argument( "--file", metavar="<filename>", + dest="filename", help=_("Downloaded image save filename (default: stdout)"), ) parser.add_argument( @@ -989,9 +1012,12 @@ class SaveImage(command.Command): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = image_client.find_image(parsed_args.image) + image = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ) - output_file = parsed_args.file + output_file = parsed_args.filename if output_file is None: output_file = getattr(sys.stdout, "buffer", sys.stdout) @@ -1164,7 +1190,7 @@ class SetImage(command.Command): metavar="<project>", help=_("Set an alternate project on this image (name or ID)"), ) - common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, @@ -1180,7 +1206,10 @@ class SetImage(command.Command): const="accepted", dest="membership", default=None, - help=_("Accept the image membership"), + help=_( + "Accept the image membership for either the project indicated " + "by '--project', if provided, or the current user's project" + ), ) membership_group.add_argument( "--reject", @@ -1188,7 +1217,10 @@ class SetImage(command.Command): const="rejected", dest="membership", default=None, - help=_("Reject the image membership"), + help=_( + "Reject the image membership for either the project indicated " + "by '--project', if provided, or the current user's project" + ), ) membership_group.add_argument( "--pending", @@ -1236,7 +1268,7 @@ class SetImage(command.Command): ) project_id = None if parsed_args.project: - project_id = common.find_project( + project_id = identity_common.find_project( identity_client, parsed_args.project, parsed_args.project_domain, @@ -1357,7 +1389,8 @@ class ShowImage(command.ShowOne): image_client = self.app.client_manager.image image = image_client.find_image( - parsed_args.image, ignore_missing=False + parsed_args.image, + ignore_missing=False, ) info = _format_image(image, parsed_args.human_readable) @@ -1401,7 +1434,8 @@ class UnsetImage(command.Command): def take_action(self, parsed_args): image_client = self.app.client_manager.image image = image_client.find_image( - parsed_args.image, ignore_missing=False + parsed_args.image, + ignore_missing=False, ) kwargs = {} @@ -1469,3 +1503,322 @@ class UnsetImage(command.Command): "Failed to unset %(propret)s of %(proptotal)s" " properties." ) % {'propret': propret, 'proptotal': proptotal} raise exceptions.CommandError(msg) + + +class StageImage(command.Command): + _description = _( + "Upload data for a specific image to staging.\n" + "This requires support for the interoperable image import process, " + "which was first introduced in Image API version 2.6 " + "(Glance 16.0.0 (Queens))" + ) + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + '--file', + metavar='<file>', + dest='filename', + help=_( + 'Local file that contains disk image to be uploaded. ' + 'Alternatively, images can be passed via stdin.' + ), + ) + # NOTE(stephenfin): glanceclient had a --size argument but it didn't do + # anything so we have chosen not to port this + parser.add_argument( + '--progress', + action='store_true', + default=False, + help=_( + 'Show upload progress bar ' + '(ignored if passing data via stdin)' + ), + ) + parser.add_argument( + 'image', + metavar='<image>', + help=_('Image to upload data for (name or ID)'), + ) + + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + image = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ) + # open the file first to ensure any failures are handled before the + # image is created. Get the file name (if it is file, and not stdin) + # for easier further handling. + if parsed_args.filename: + try: + fp = open(parsed_args.filename, 'rb') + except FileNotFoundError: + raise exceptions.CommandError( + '%r is not a valid file' % parsed_args.filename, + ) + else: + fp = get_data_from_stdin() + + kwargs = {} + + if parsed_args.progress and parsed_args.filename: + # NOTE(stephenfin): we only show a progress bar if the user + # requested it *and* we're reading from a file (not stdin) + filesize = os.path.getsize(parsed_args.filename) + if filesize is not None: + kwargs['data'] = progressbar.VerboseFileWrapper(fp, filesize) + else: + kwargs['data'] = fp + elif parsed_args.filename: + kwargs['filename'] = parsed_args.filename + elif fp: + kwargs['data'] = fp + + image_client.stage_image(image, **kwargs) + + +class ImportImage(command.ShowOne): + _description = _( + "Initiate the image import process.\n" + "This requires support for the interoperable image import process, " + "which was first introduced in Image API version 2.6 " + "(Glance 16.0.0 (Queens))" + ) + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + 'image', + metavar='<image>', + help=_('Image to initiate import process for (name or ID)'), + ) + # TODO(stephenfin): Uncomment help text when we have this command + # implemented + parser.add_argument( + '--method', + metavar='<method>', + default='glance-direct', + dest='import_method', + choices=[ + 'glance-direct', + 'web-download', + 'glance-download', + 'copy-image', + ], + help=_( + "Import method used for image import process. " + "Not all deployments will support all methods. " + # "Valid values can be retrieved with the 'image import " + # "methods' command. " + "The 'glance-direct' method (default) requires images be " + "first staged using the 'image-stage' command." + ), + ) + parser.add_argument( + '--uri', + metavar='<uri>', + help=_( + "URI to download the external image " + "(only valid with the 'web-download' import method)" + ), + ) + parser.add_argument( + '--remote-image', + metavar='<REMOTE_IMAGE>', + help=_( + "The image of remote glance (ID only) to be imported " + "(only valid with the 'glance-download' import method)" + ), + ) + parser.add_argument( + '--remote-region', + metavar='<REMOTE_GLANCE_REGION>', + help=_( + "The remote Glance region to download the image from " + "(only valid with the 'glance-download' import method)" + ), + ) + parser.add_argument( + '--remote-service-interface', + metavar='<REMOTE_SERVICE_INTERFACE>', + help=_( + "The remote Glance service interface to use when importing " + "images " + "(only valid with the 'glance-download' import method)" + ), + ) + stores_group = parser.add_mutually_exclusive_group() + stores_group.add_argument( + '--store', + metavar='<STORE>', + dest='stores', + nargs='*', + help=_( + "Backend store to upload image to " + "(specify multiple times to upload to multiple stores) " + "(either '--store' or '--all-stores' required with the " + "'copy-image' import method)" + ), + ) + stores_group.add_argument( + '--all-stores', + help=_( + "Make image available to all stores " + "(either '--store' or '--all-stores' required with the " + "'copy-image' import method)" + ), + ) + parser.add_argument( + '--allow-failure', + action='store_true', + dest='allow_failure', + default=True, + help=_( + 'When uploading to multiple stores, indicate that the import ' + 'should be continue should any of the uploads fail. ' + 'Only usable with --stores or --all-stores' + ), + ) + parser.add_argument( + '--disallow-failure', + action='store_true', + dest='allow_failure', + default=True, + help=_( + 'When uploading to multiple stores, indicate that the import ' + 'should be reverted should any of the uploads fail. ' + 'Only usable with --stores or --all-stores' + ), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for operation to complete'), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + try: + import_info = image_client.get_import_info() + except sdk_exceptions.ResourceNotFound: + msg = _( + 'The Image Import feature is not supported by this deployment' + ) + raise exceptions.CommandError(msg) + + import_methods = import_info.import_methods['value'] + + if parsed_args.import_method not in import_methods: + msg = _( + "The '%s' import method is not supported by this deployment. " + "Supported: %s" + ) + raise exceptions.CommandError( + msg % (parsed_args.import_method, ', '.join(import_methods)), + ) + + if parsed_args.import_method == 'web-download': + if not parsed_args.uri: + msg = _( + "The '--uri' option is required when using " + "'--method=web-download'" + ) + raise exceptions.CommandError(msg) + else: + if parsed_args.uri: + msg = _( + "The '--uri' option is only supported when using " + "'--method=web-download'" + ) + raise exceptions.CommandError(msg) + + if parsed_args.import_method == 'glance-download': + if not (parsed_args.remote_region and parsed_args.remote_image): + msg = _( + "The '--remote-region' and '--remote-image' options are " + "required when using '--method=web-download'" + ) + raise exceptions.CommandError(msg) + else: + if parsed_args.remote_region: + msg = _( + "The '--remote-region' option is only supported when " + "using '--method=glance-download'" + ) + raise exceptions.CommandError(msg) + + if parsed_args.remote_image: + msg = _( + "The '--remote-image' option is only supported when using " + "'--method=glance-download'" + ) + raise exceptions.CommandError(msg) + + if parsed_args.remote_service_interface: + msg = _( + "The '--remote-service-interface' option is only " + "supported when using '--method=glance-download'" + ) + raise exceptions.CommandError(msg) + + if parsed_args.import_method == 'copy-image': + if not (parsed_args.stores or parsed_args.all_stores): + msg = _( + "The '--stores' or '--all-stores' options are required " + "when using '--method=copy-image'" + ) + raise exceptions.CommandError(msg) + + image = image_client.find_image(parsed_args.image) + + if not image.container_format and not image.disk_format: + msg = _( + "The 'container_format' and 'disk_format' properties " + "must be set on an image before it can be imported" + ) + raise exceptions.CommandError(msg) + + if parsed_args.import_method == 'glance-direct': + if image.status != 'uploading': + msg = _( + "The 'glance-direct' import method can only be used with " + "an image in status 'uploading'" + ) + raise exceptions.CommandError(msg) + elif parsed_args.import_method == 'web-download': + if image.status != 'queued': + msg = _( + "The 'web-download' import method can only be used with " + "an image in status 'queued'" + ) + raise exceptions.CommandError(msg) + elif parsed_args.import_method == 'copy-image': + if image.status != 'active': + msg = _( + "The 'copy-image' import method can only be used with " + "an image in status 'active'" + ) + raise exceptions.CommandError(msg) + + image_client.import_image( + image, + method=parsed_args.import_method, + uri=parsed_args.uri, + remote_region=parsed_args.remote_region, + remote_image=parsed_args.remote_image, + remote_service_interface=parsed_args.remote_service_interface, + stores=parsed_args.stores, + all_stores=parsed_args.all_stores, + all_stores_must_succeed=not parsed_args.allow_failure, + ) + + info = _format_image(image) + return zip(*sorted(info.items())) diff --git a/openstackclient/image/v2/metadef_namespaces.py b/openstackclient/image/v2/metadef_namespaces.py index 158fd94e..f09f2002 100644 --- a/openstackclient/image/v2/metadef_namespaces.py +++ b/openstackclient/image/v2/metadef_namespaces.py @@ -15,8 +15,11 @@ """Image V2 Action Implementations""" +import logging + from osc_lib.cli import format_columns from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -25,6 +28,149 @@ _formatters = { 'tags': format_columns.ListColumn, } +LOG = logging.getLogger(__name__) + + +def _format_namespace(namespace): + info = {} + + fields_to_show = [ + 'created_at', + 'description', + 'display_name', + 'namespace', + 'owner', + 'protected', + 'schema', + 'visibility', + ] + + namespace = namespace.to_dict(ignore_none=True, original_names=True) + + # split out the usual key and the properties which are top-level + for key in namespace: + if key in fields_to_show: + info[key] = namespace.get(key) + elif key == "resource_type_associations": + info[key] = [resource_type['name'] + for resource_type in namespace.get(key)] + elif key == 'properties': + info['properties'] = list(namespace.get(key).keys()) + + return info + + +class CreateMetadefNameSpace(command.ShowOne): + _description = _("Create a metadef namespace") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "namespace", + metavar="<namespace>", + help=_("New metadef namespace name"), + ) + parser.add_argument( + "--display-name", + metavar="<display_name>", + help=_("A user-friendly name for the namespace."), + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("A description of the namespace"), + ) + visibility_group = parser.add_mutually_exclusive_group() + visibility_group.add_argument( + "--public", + action="store_const", + const="public", + dest="visibility", + help=_("Set namespace visibility 'public'"), + ) + visibility_group.add_argument( + "--private", + action="store_const", + const="private", + dest="visibility", + help=_("Set namespace visibility 'private'"), + ) + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( + "--protected", + action="store_const", + const=True, + dest="is_protected", + help=_("Prevent metadef namespace from being deleted"), + ) + protected_group.add_argument( + "--unprotected", + action="store_const", + const=False, + dest="is_protected", + help=_("Allow metadef namespace to be deleted (default)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + filter_keys = [ + 'namespace', + 'display_name', + 'description' + ] + kwargs = {} + + for key in filter_keys: + argument = getattr(parsed_args, key, None) + if argument is not None: + kwargs[key] = argument + + if parsed_args.is_protected is not None: + kwargs['protected'] = parsed_args.is_protected + + if parsed_args.visibility is not None: + kwargs['visibility'] = parsed_args.visibility + + data = image_client.create_metadef_namespace(**kwargs) + + return zip(*sorted(data.items())) + + +class DeleteMetadefNameSpace(command.Command): + _description = _("Delete metadef namespace") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "namespace_name", + metavar="<namespace_name>", + nargs="+", + help=_("An identifier (a name) for the namespace"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + result = 0 + for i in parsed_args.namespace_name: + try: + namespace = image_client.get_metadef_namespace(i) + image_client.delete_metadef_namespace(namespace.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete namespace with name or " + "ID '%(namespace)s': %(e)s"), + {'namespace': i, 'e': e} + ) + + if result > 0: + total = len(parsed_args.namespace_name) + msg = (_("%(result)s of %(total)s namespace failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + class ListMetadefNameSpaces(command.Lister): _description = _("List metadef namespaces") @@ -63,3 +209,104 @@ class ListMetadefNameSpaces(command.Lister): formatters=_formatters, ) for s in data) ) + + +class SetMetadefNameSpace(command.Command): + _description = _("Set metadef namespace properties") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "namespace", + metavar="<namespace>", + help=_("Namespace (name) for the namespace"), + ) + parser.add_argument( + "--display-name", + metavar="<display_name>", + help=_("Set a user-friendly name for the namespace."), + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Set the description of the namespace"), + ) + visibility_group = parser.add_mutually_exclusive_group() + visibility_group.add_argument( + "--public", + action="store_const", + const="public", + dest="visibility", + help=_("Set namespace visibility 'public'"), + ) + visibility_group.add_argument( + "--private", + action="store_const", + const="private", + dest="visibility", + help=_("Set namespace visibility 'private'"), + ) + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( + "--protected", + action="store_const", + const=True, + dest="is_protected", + help=_("Prevent metadef namespace from being deleted"), + ) + protected_group.add_argument( + "--unprotected", + action="store_const", + const=False, + dest="is_protected", + help=_("Allow metadef namespace to be deleted (default)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + namespace = parsed_args.namespace + + filter_keys = [ + 'namespace', + 'display_name', + 'description' + ] + kwargs = {} + + for key in filter_keys: + argument = getattr(parsed_args, key, None) + if argument is not None: + kwargs[key] = argument + + if parsed_args.is_protected is not None: + kwargs['protected'] = parsed_args.is_protected + + if parsed_args.visibility is not None: + kwargs['visibility'] = parsed_args.visibility + + image_client.update_metadef_namespace(namespace, **kwargs) + + +class ShowMetadefNameSpace(command.ShowOne): + _description = _("Show a metadef namespace") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "namespace_name", + metavar="<namespace_name>", + help=_("Namespace (name) for the namespace"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + namespace_name = parsed_args.namespace_name + + data = image_client.get_metadef_namespace(namespace_name) + info = _format_namespace(data) + + return zip(*sorted(info.items())) |
