Skip to content

Commit 276dbb6

Browse files
agaetepstephenfin
authored andcommitted
Add image metadef resource type association commands
'create', 'list', 'delete' Change-Id: I2c860427b0b2693076cfe57841f0e512ad1f6388
1 parent b740f2f commit 276dbb6

File tree

6 files changed

+385
-4
lines changed

6 files changed

+385
-4
lines changed

doc/source/cli/data/glance.csv

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ md-namespace-import,,Import a metadata definitions namespace from file or standa
2828
md-namespace-list,image metadef namespace list,List metadata definitions namespaces.
2929
md-namespace-objects-delete,,Delete all metadata definitions objects inside a specific namespace.
3030
md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace.
31-
md-namespace-resource-type-list,image metadef resource type list,List resource types associated to specific namespace.
31+
md-namespace-resource-type-list,image metadef resource type association list,List resource types associated to specific namespace.
3232
md-namespace-show,image metadef namespace show,Describe a specific metadata definitions namespace.
3333
md-namespace-tags-delete,,Delete all metadata definitions tags inside a specific namespace.
3434
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
4343
md-property-list,image metadef property list,List metadata definitions properties inside a specific namespace.
4444
md-property-show,image metadef property show,Describe a specific metadata definitions property inside a namespace.
4545
md-property-update,image metadef property set,Update metadata definitions property inside a namespace.
46-
md-resource-type-associate,,Associate resource type with a metadata definitions namespace.
47-
md-resource-type-deassociate,,Deassociate resource type with a metadata definitions namespace.
48-
md-resource-type-list,,List available resource type names.
46+
md-resource-type-associate,image metadef resource type association create,Associate resource type with a metadata definitions namespace.
47+
md-resource-type-deassociate,image metadef resource type association delete,Deassociate resource type with a metadata definitions namespace.
48+
md-resource-type-list,image metadef resource type list,List available resource type names.
4949
md-tag-create,,Add a new metadata definitions tag inside a namespace.
5050
md-tag-create-multiple,,Create new metadata definitions tags inside a namespace.
5151
md-tag-delete,,Delete a specific metadata definitions tag inside a namespace.
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import logging
14+
15+
from osc_lib.command import command
16+
from osc_lib import exceptions
17+
from osc_lib import utils
18+
19+
from openstackclient.i18n import _
20+
21+
LOG = logging.getLogger(__name__)
22+
23+
24+
def _get_columns(item):
25+
column_map = {}
26+
hidden_columns = ['location']
27+
return utils.get_osc_show_columns_for_sdk_resource(
28+
item, column_map, hidden_columns
29+
)
30+
31+
32+
class CreateMetadefResourceTypeAssociation(command.ShowOne):
33+
_description = _("Create metadef resource type association")
34+
35+
def get_parser(self, prog_name):
36+
parser = super().get_parser(prog_name)
37+
parser.add_argument(
38+
"namespace",
39+
metavar="<namespace>",
40+
help=_(
41+
"The name of the namespace you want to create the "
42+
"resource type association in"
43+
),
44+
)
45+
parser.add_argument(
46+
"name",
47+
metavar="<name>",
48+
help=_("A name of the new resource type"),
49+
)
50+
parser.add_argument(
51+
"--properties-target",
52+
metavar="<properties_target>",
53+
help=_(
54+
"Some resource types allow more than one "
55+
"key/value pair per instance."
56+
),
57+
)
58+
return parser
59+
60+
def take_action(self, parsed_args):
61+
image_client = self.app.client_manager.image
62+
kwargs = {}
63+
64+
kwargs['namespace'] = parsed_args.namespace
65+
kwargs['name'] = parsed_args.name
66+
67+
if parsed_args.properties_target:
68+
kwargs['properties_target'] = parsed_args.properties_target
69+
70+
obj = image_client.create_metadef_resource_type_association(
71+
parsed_args.namespace, **kwargs
72+
)
73+
74+
display_columns, columns = _get_columns(obj)
75+
data = utils.get_item_properties(obj, columns, formatters={})
76+
77+
return (display_columns, data)
78+
79+
80+
class DeleteMetadefResourceTypeAssociation(command.Command):
81+
_description = _("Delete metadef resource type association")
82+
83+
def get_parser(self, prog_name):
84+
parser = super().get_parser(prog_name)
85+
parser.add_argument(
86+
"metadef_namespace",
87+
metavar="<metadef_namespace>",
88+
help=_("The name of the namespace whose details you want to see"),
89+
)
90+
parser.add_argument(
91+
"name",
92+
metavar="<name>",
93+
nargs="+",
94+
help=_(
95+
"The name of the resource type(s) (repeat option to delete"
96+
"multiple metadef resource type associations)"
97+
),
98+
)
99+
parser.add_argument(
100+
"--force",
101+
dest='force',
102+
action='store_true',
103+
default=False,
104+
help=_(
105+
"Force delete the resource type association if the"
106+
"namespace is protected"
107+
),
108+
)
109+
return parser
110+
111+
def take_action(self, parsed_args):
112+
image_client = self.app.client_manager.image
113+
114+
result = 0
115+
for resource_type in parsed_args.name:
116+
try:
117+
metadef_namespace = image_client.get_metadef_namespace(
118+
parsed_args.metadef_namespace
119+
)
120+
121+
kwargs = {}
122+
is_initially_protected = (
123+
True if metadef_namespace.is_protected else False
124+
)
125+
if is_initially_protected and parsed_args.force:
126+
kwargs['is_protected'] = False
127+
128+
image_client.update_metadef_namespace(
129+
metadef_namespace.namespace, **kwargs
130+
)
131+
132+
try:
133+
image_client.delete_metadef_resource_type_association(
134+
resource_type, metadef_namespace, ignore_missing=False
135+
)
136+
finally:
137+
if is_initially_protected:
138+
kwargs['is_protected'] = True
139+
image_client.update_metadef_namespace(
140+
metadef_namespace.namespace, **kwargs
141+
)
142+
143+
except Exception as e:
144+
result += 1
145+
LOG.error(
146+
_(
147+
"Failed to delete resource type with name or "
148+
"ID '%(resource_type)s': %(e)s"
149+
),
150+
{'resource_type': resource_type, 'e': e},
151+
)
152+
153+
if result > 0:
154+
total = len(parsed_args.metadef_namespace)
155+
msg = _(
156+
"%(result)s of %(total)s resource type failed to delete."
157+
) % {'result': result, 'total': total}
158+
raise exceptions.CommandError(msg)
159+
160+
161+
class ListMetadefResourceTypeAssociations(command.Lister):
162+
_description = _("List metadef resource type associations")
163+
164+
def get_parser(self, prog_name):
165+
parser = super().get_parser(prog_name)
166+
parser.add_argument(
167+
"metadef_namespace",
168+
metavar="<metadef_namespace>",
169+
help=_("The name of the namespace whose details you want to see"),
170+
)
171+
return parser
172+
173+
def take_action(self, parsed_args):
174+
image_client = self.app.client_manager.image
175+
data = image_client.metadef_resource_type_associations(
176+
parsed_args.metadef_namespace,
177+
)
178+
columns = ['Name']
179+
column_headers = columns
180+
return (
181+
column_headers,
182+
(
183+
utils.get_item_properties(
184+
s,
185+
columns,
186+
)
187+
for s in data
188+
),
189+
)

openstackclient/tests/unit/image/v2/fakes.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,44 @@ def create_one_metadef_object(attrs=None):
381381
# Overwrite default attributes if there are some attributes set
382382
metadef_objects_list.update(attrs)
383383
return metadef_object.MetadefObject(**metadef_objects_list)
384+
385+
386+
def create_one_resource_type_association(attrs=None):
387+
"""Create a fake MetadefResourceTypeAssociation.
388+
389+
:param attrs: A dictionary with all attributes of
390+
metadef_resource_type_association member
391+
:type attrs: dict
392+
:return: A fake MetadefResourceTypeAssociation object
393+
:rtype: A `metadef_resource_type_association.
394+
MetadefResourceTypeAssociation`
395+
"""
396+
attrs = attrs or {}
397+
398+
metadef_resource_type_association_info = {
399+
'namespace_name': 'OS::Compute::Quota',
400+
'name': 'OS::Nova::Flavor',
401+
}
402+
403+
metadef_resource_type_association_info.update(attrs)
404+
return metadef_resource_type.MetadefResourceTypeAssociation(
405+
**metadef_resource_type_association_info
406+
)
407+
408+
409+
def create_resource_type_associations(attrs=None, count=2):
410+
"""Create mutiple fake resource type associations/
411+
412+
:param attrs: A dictionary with all attributes of
413+
metadef_resource_type_association member
414+
:type attrs: dict
415+
:return: A list of fake MetadefResourceTypeAssociation objects
416+
:rtype: list
417+
"""
418+
resource_type_associations = []
419+
for n in range(0, count):
420+
resource_type_associations.append(
421+
create_one_resource_type_association(attrs)
422+
)
423+
424+
return resource_type_associations
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
from openstackclient.image.v2 import metadef_resource_type_association
14+
from openstackclient.tests.unit.image.v2 import fakes as resource_type_fakes
15+
16+
17+
class TestMetadefResourceTypeAssociationCreate(
18+
resource_type_fakes.TestImagev2
19+
):
20+
resource_type_association = (
21+
resource_type_fakes.create_one_resource_type_association()
22+
)
23+
24+
columns = (
25+
'created_at',
26+
'id',
27+
'name',
28+
'prefix',
29+
'properties_target',
30+
'updated_at',
31+
)
32+
33+
data = (
34+
resource_type_association.created_at,
35+
resource_type_association.id,
36+
resource_type_association.name,
37+
resource_type_association.prefix,
38+
resource_type_association.properties_target,
39+
resource_type_association.updated_at,
40+
)
41+
42+
def setUp(self):
43+
super().setUp()
44+
45+
self.image_client.create_metadef_resource_type_association.return_value = (
46+
self.resource_type_association
47+
)
48+
self.cmd = metadef_resource_type_association.CreateMetadefResourceTypeAssociation(
49+
self.app, None
50+
)
51+
52+
def test_resource_type_association_create(self):
53+
arglist = [
54+
self.resource_type_association.namespace_name,
55+
self.resource_type_association.name,
56+
]
57+
58+
verifylist = []
59+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
60+
61+
columns, data = self.cmd.take_action(parsed_args)
62+
63+
self.assertEqual(self.columns, columns)
64+
self.assertEqual(self.data, data)
65+
66+
67+
class TestMetadefResourceTypeAssociationDelete(
68+
resource_type_fakes.TestImagev2
69+
):
70+
resource_type_association = (
71+
resource_type_fakes.create_one_resource_type_association()
72+
)
73+
74+
def setUp(self):
75+
super().setUp()
76+
77+
self.image_client.delete_metadef_resource_type_association.return_value = (
78+
self.resource_type_association
79+
)
80+
self.cmd = metadef_resource_type_association.DeleteMetadefResourceTypeAssociation(
81+
self.app, None
82+
)
83+
84+
def test_resource_type_association_delete(self):
85+
arglist = [
86+
self.resource_type_association.namespace_name,
87+
self.resource_type_association.name,
88+
]
89+
90+
verifylist = []
91+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
92+
93+
result = self.cmd.take_action(parsed_args)
94+
95+
self.assertIsNone(result)
96+
97+
98+
class TestMetadefResourceTypeAssociationList(resource_type_fakes.TestImagev2):
99+
resource_type_associations = (
100+
resource_type_fakes.create_resource_type_associations()
101+
)
102+
103+
columns = ['Name']
104+
105+
datalist = [
106+
(resource_type_association.name,)
107+
for resource_type_association in resource_type_associations
108+
]
109+
110+
def setUp(self):
111+
super().setUp()
112+
113+
self.image_client.metadef_resource_type_associations.side_effect = [
114+
self.resource_type_associations,
115+
[],
116+
]
117+
118+
self.cmd = metadef_resource_type_association.ListMetadefResourceTypeAssociations(
119+
self.app, None
120+
)
121+
122+
def test_resource_type_association_list(self):
123+
arglist = [
124+
self.resource_type_associations[0].namespace_name,
125+
]
126+
parsed_args = self.check_parser(self.cmd, arglist, [])
127+
128+
columns, data = self.cmd.take_action(parsed_args)
129+
130+
self.assertEqual(self.columns, columns)
131+
self.assertCountEqual(self.datalist, data)

0 commit comments

Comments
 (0)