diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index e554f09fe..905e282b0 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -28,7 +28,7 @@ md-namespace-import,,Import a metadata definitions namespace from file or standa md-namespace-list,image metadef namespace list,List metadata definitions namespaces. md-namespace-objects-delete,,Delete all metadata definitions objects inside a specific namespace. md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. -md-namespace-resource-type-list,image metadef resource type list,List resource types associated to specific namespace. +md-namespace-resource-type-list,image metadef resource type association list,List resource types associated to specific namespace. md-namespace-show,image metadef namespace show,Describe a specific metadata definitions namespace. md-namespace-tags-delete,,Delete all metadata definitions tags inside a specific namespace. md-namespace-update,,Update an existing metadata definitions namespace. @@ -43,9 +43,9 @@ md-property-delete,image metadef property delete,Delete a specific metadata defi md-property-list,image metadef property list,List metadata definitions properties inside a specific namespace. md-property-show,image metadef property show,Describe a specific metadata definitions property inside a namespace. md-property-update,image metadef property set,Update metadata definitions property inside a namespace. -md-resource-type-associate,,Associate resource type with a metadata definitions namespace. -md-resource-type-deassociate,,Deassociate resource type with a metadata definitions namespace. -md-resource-type-list,,List available resource type names. +md-resource-type-associate,image metadef resource type association create,Associate resource type with a metadata definitions namespace. +md-resource-type-deassociate,image metadef resource type association delete,Deassociate resource type with a metadata definitions namespace. +md-resource-type-list,image metadef resource type list,List available resource type names. md-tag-create,,Add a new metadata definitions tag inside a namespace. md-tag-create-multiple,,Create new metadata definitions tags inside a namespace. md-tag-delete,,Delete a specific metadata definitions tag inside a namespace. diff --git a/openstackclient/image/v2/metadef_resource_type_association.py b/openstackclient/image/v2/metadef_resource_type_association.py new file mode 100644 index 000000000..e6461e5da --- /dev/null +++ b/openstackclient/image/v2/metadef_resource_type_association.py @@ -0,0 +1,189 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns + ) + + +class CreateMetadefResourceTypeAssociation(command.ShowOne): + _description = _("Create metadef resource type association") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "namespace", + metavar="", + help=_( + "The name of the namespace you want to create the " + "resource type association in" + ), + ) + parser.add_argument( + "name", + metavar="", + help=_("A name of the new resource type"), + ) + parser.add_argument( + "--properties-target", + metavar="", + help=_( + "Some resource types allow more than one " + "key/value pair per instance." + ), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + kwargs = {} + + kwargs['namespace'] = parsed_args.namespace + kwargs['name'] = parsed_args.name + + if parsed_args.properties_target: + kwargs['properties_target'] = parsed_args.properties_target + + obj = image_client.create_metadef_resource_type_association( + parsed_args.namespace, **kwargs + ) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteMetadefResourceTypeAssociation(command.Command): + _description = _("Delete metadef resource type association") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "metadef_namespace", + metavar="", + help=_("The name of the namespace whose details you want to see"), + ) + parser.add_argument( + "name", + metavar="", + nargs="+", + help=_( + "The name of the resource type(s) (repeat option to delete" + "multiple metadef resource type associations)" + ), + ) + parser.add_argument( + "--force", + dest='force', + action='store_true', + default=False, + help=_( + "Force delete the resource type association if the" + "namespace is protected" + ), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + result = 0 + for resource_type in parsed_args.name: + try: + metadef_namespace = image_client.get_metadef_namespace( + parsed_args.metadef_namespace + ) + + kwargs = {} + is_initially_protected = ( + True if metadef_namespace.is_protected else False + ) + if is_initially_protected and parsed_args.force: + kwargs['is_protected'] = False + + image_client.update_metadef_namespace( + metadef_namespace.namespace, **kwargs + ) + + try: + image_client.delete_metadef_resource_type_association( + resource_type, metadef_namespace, ignore_missing=False + ) + finally: + if is_initially_protected: + kwargs['is_protected'] = True + image_client.update_metadef_namespace( + metadef_namespace.namespace, **kwargs + ) + + except Exception as e: + result += 1 + LOG.error( + _( + "Failed to delete resource type with name or " + "ID '%(resource_type)s': %(e)s" + ), + {'resource_type': resource_type, 'e': e}, + ) + + if result > 0: + total = len(parsed_args.metadef_namespace) + msg = _( + "%(result)s of %(total)s resource type failed to delete." + ) % {'result': result, 'total': total} + raise exceptions.CommandError(msg) + + +class ListMetadefResourceTypeAssociations(command.Lister): + _description = _("List metadef resource type associations") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + "metadef_namespace", + metavar="", + help=_("The name of the namespace whose details you want to see"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + data = image_client.metadef_resource_type_associations( + parsed_args.metadef_namespace, + ) + columns = ['Name'] + column_headers = columns + return ( + column_headers, + ( + utils.get_item_properties( + s, + columns, + ) + for s in data + ), + ) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 6565495fb..13ff77d1b 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -381,3 +381,44 @@ def create_one_metadef_object(attrs=None): # Overwrite default attributes if there are some attributes set metadef_objects_list.update(attrs) return metadef_object.MetadefObject(**metadef_objects_list) + + +def create_one_resource_type_association(attrs=None): + """Create a fake MetadefResourceTypeAssociation. + + :param attrs: A dictionary with all attributes of + metadef_resource_type_association member + :type attrs: dict + :return: A fake MetadefResourceTypeAssociation object + :rtype: A `metadef_resource_type_association. + MetadefResourceTypeAssociation` + """ + attrs = attrs or {} + + metadef_resource_type_association_info = { + 'namespace_name': 'OS::Compute::Quota', + 'name': 'OS::Nova::Flavor', + } + + metadef_resource_type_association_info.update(attrs) + return metadef_resource_type.MetadefResourceTypeAssociation( + **metadef_resource_type_association_info + ) + + +def create_resource_type_associations(attrs=None, count=2): + """Create mutiple fake resource type associations/ + + :param attrs: A dictionary with all attributes of + metadef_resource_type_association member + :type attrs: dict + :return: A list of fake MetadefResourceTypeAssociation objects + :rtype: list + """ + resource_type_associations = [] + for n in range(0, count): + resource_type_associations.append( + create_one_resource_type_association(attrs) + ) + + return resource_type_associations diff --git a/openstackclient/tests/unit/image/v2/test_metadef_resource_type_association.py b/openstackclient/tests/unit/image/v2/test_metadef_resource_type_association.py new file mode 100644 index 000000000..ac1d65008 --- /dev/null +++ b/openstackclient/tests/unit/image/v2/test_metadef_resource_type_association.py @@ -0,0 +1,131 @@ +# 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. + +from openstackclient.image.v2 import metadef_resource_type_association +from openstackclient.tests.unit.image.v2 import fakes as resource_type_fakes + + +class TestMetadefResourceTypeAssociationCreate( + resource_type_fakes.TestImagev2 +): + resource_type_association = ( + resource_type_fakes.create_one_resource_type_association() + ) + + columns = ( + 'created_at', + 'id', + 'name', + 'prefix', + 'properties_target', + 'updated_at', + ) + + data = ( + resource_type_association.created_at, + resource_type_association.id, + resource_type_association.name, + resource_type_association.prefix, + resource_type_association.properties_target, + resource_type_association.updated_at, + ) + + def setUp(self): + super().setUp() + + self.image_client.create_metadef_resource_type_association.return_value = ( + self.resource_type_association + ) + self.cmd = metadef_resource_type_association.CreateMetadefResourceTypeAssociation( + self.app, None + ) + + def test_resource_type_association_create(self): + arglist = [ + self.resource_type_association.namespace_name, + self.resource_type_association.name, + ] + + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestMetadefResourceTypeAssociationDelete( + resource_type_fakes.TestImagev2 +): + resource_type_association = ( + resource_type_fakes.create_one_resource_type_association() + ) + + def setUp(self): + super().setUp() + + self.image_client.delete_metadef_resource_type_association.return_value = ( + self.resource_type_association + ) + self.cmd = metadef_resource_type_association.DeleteMetadefResourceTypeAssociation( + self.app, None + ) + + def test_resource_type_association_delete(self): + arglist = [ + self.resource_type_association.namespace_name, + self.resource_type_association.name, + ] + + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + + +class TestMetadefResourceTypeAssociationList(resource_type_fakes.TestImagev2): + resource_type_associations = ( + resource_type_fakes.create_resource_type_associations() + ) + + columns = ['Name'] + + datalist = [ + (resource_type_association.name,) + for resource_type_association in resource_type_associations + ] + + def setUp(self): + super().setUp() + + self.image_client.metadef_resource_type_associations.side_effect = [ + self.resource_type_associations, + [], + ] + + self.cmd = metadef_resource_type_association.ListMetadefResourceTypeAssociations( + self.app, None + ) + + def test_resource_type_association_list(self): + arglist = [ + self.resource_type_associations[0].namespace_name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, data) diff --git a/releasenotes/notes/add-image-metadef-resource-type-association-commands-4d373d7d8eca5d55.yaml b/releasenotes/notes/add-image-metadef-resource-type-association-commands-4d373d7d8eca5d55.yaml new file mode 100644 index 000000000..eb03566fa --- /dev/null +++ b/releasenotes/notes/add-image-metadef-resource-type-association-commands-4d373d7d8eca5d55.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Added ``image metadef resource type association list`` + to list resource type associations for the image service. + This is equivalent to the + ``md-namespace-resource-type-list`` command in glance. + - | + Added ``image metadef resource type association create`` + to create a resource type association for the image service. + This is equivalent to the + ``md-resource-type-associate`` command in glance. + - | + Added ``image metadef resource type association delete`` + to delete a resource type association for the image service. + This is equivalent to the + ``md-resource-type-deassociate`` command in glance. diff --git a/setup.cfg b/setup.cfg index ceb94adc5..d49bb6bcc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -411,6 +411,9 @@ openstack.image.v2 = image_metadef_property_show = openstackclient.image.v2.metadef_properties:ShowMetadefProperty image_metadef_resource_type_list = openstackclient.image.v2.metadef_resource_types:ListMetadefResourceTypes + image_metadef_resource_type_association_create = openstackclient.image.v2.metadef_resource_type_association:CreateMetadefResourceTypeAssociation + image_metadef_resource_type_association_delete = openstackclient.image.v2.metadef_resource_type_association:DeleteMetadefResourceTypeAssociation + image_metadef_resource_type_association_list = openstackclient.image.v2.metadef_resource_type_association:ListMetadefResourceTypeAssociations cached_image_list = openstackclient.image.v2.cache:ListCachedImage cached_image_queue = openstackclient.image.v2.cache:QueueCachedImage