Merge "Add SDK support for adding/removing metadef tags"
This commit is contained in:
@@ -1042,9 +1042,8 @@ class Proxy(proxy.Proxy):
|
||||
"""Add a tag to an image
|
||||
|
||||
:param image: The value can be the ID of a image or a
|
||||
:class:`~openstack.image.v2.image.Image` instance
|
||||
that the member will be created for.
|
||||
:param str tag: The tag to be added
|
||||
:class:`~openstack.image.v2.image.Image` instance.
|
||||
:param tag: The tag to be added.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
@@ -1052,12 +1051,11 @@ class Proxy(proxy.Proxy):
|
||||
image.add_tag(self, tag)
|
||||
|
||||
def remove_tag(self, image, tag):
|
||||
"""Remove a tag to an image
|
||||
"""Remove a tag from an image
|
||||
|
||||
:param image: The value can be the ID of a image or a
|
||||
:class:`~openstack.image.v2.image.Image` instance
|
||||
that the member will be created for.
|
||||
:param str tag: The tag to be removed
|
||||
:class:`~openstack.image.v2.image.Image` instance.
|
||||
:param tag: The tag to be removed.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
@@ -1278,6 +1276,50 @@ class Proxy(proxy.Proxy):
|
||||
**attrs,
|
||||
)
|
||||
|
||||
def add_tag_to_metadef_namespace(self, namespace, tag):
|
||||
"""Add a tag to a metadef namespace
|
||||
|
||||
:param metadef_namespace: Either the name of a metadef namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance.
|
||||
:param str tag: The tag to be added.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
namespace = self._get_resource(
|
||||
_metadef_namespace.MetadefNamespace, namespace
|
||||
)
|
||||
namespace.add_tag(self, tag)
|
||||
|
||||
def remove_tag_from_metadef_namespace(self, namespace, tag):
|
||||
"""Remove a tag from a metadef namespace
|
||||
|
||||
:param metadef_namespace: Either the name of a metadef namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance.
|
||||
:param str tag: The tag to be removed.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
namespace = self._get_resource(
|
||||
_metadef_namespace.MetadefNamespace, namespace
|
||||
)
|
||||
namespace.remove_tag(self, tag)
|
||||
|
||||
def remove_tags_from_metadef_namespace(self, namespace):
|
||||
"""Remove all tags from a metadef namespace
|
||||
|
||||
:param metadef_namespace: Either the name of a metadef namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
namespace = self._get_resource(
|
||||
_metadef_namespace.MetadefNamespace, namespace
|
||||
)
|
||||
namespace.remove_all_tags(self)
|
||||
|
||||
# ====== METADEF OBJECT ======
|
||||
def create_metadef_object(self, namespace, **attrs):
|
||||
"""Create a new object from namespace
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import typing_extensions as ty_ext
|
||||
|
||||
from openstack.common import tag
|
||||
from openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
|
||||
class MetadefNamespace(resource.Resource):
|
||||
class MetadefNamespace(resource.Resource, tag.TagMixin):
|
||||
resources_key = 'namespaces'
|
||||
base_path = '/metadefs/namespaces'
|
||||
|
||||
@@ -98,3 +102,50 @@ class MetadefNamespace(resource.Resource):
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'objects')
|
||||
return self._delete_all(session, url)
|
||||
|
||||
# NOTE(mrjoshi): This method is re-implemented as we require a ``POST``
|
||||
# call while the original method does a ``PUT`` call.
|
||||
def add_tag(self, session: resource.AdapterT, tag: str) -> ty_ext.Self:
|
||||
"""Adds a single tag to the resource.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param tag: The tag as a string.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'tags', tag)
|
||||
session = self._get_session(session)
|
||||
response = session.post(url)
|
||||
exceptions.raise_from_response(response)
|
||||
# we do not want to update tags directly
|
||||
tags = self.tags
|
||||
tags.append(tag)
|
||||
self._body.attributes.update({'tags': tags})
|
||||
return self
|
||||
|
||||
# NOTE(mrjoshi): This method is re-implemented to add support for the
|
||||
# 'append' option. This method uses a ``POST`` call rather than the
|
||||
# standard ``PUT`` call.
|
||||
def set_tags(
|
||||
self, session: resource.AdapterT, tags: list[str], append: bool = False
|
||||
) -> ty_ext.Self:
|
||||
"""Sets/Replaces all tags on the resource.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param list tags: List with tags to be set on the resource
|
||||
:param append: If set to true, adds new tags to existing tags,
|
||||
else overwrites the existing tags with new ones.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'tags')
|
||||
session = self._get_session(session)
|
||||
|
||||
headers = {'X-OpenStack-Append': 'False'}
|
||||
if append:
|
||||
headers['X-Openstack-Append'] = 'True'
|
||||
|
||||
response = session.post(
|
||||
url, headers=headers, json={'tags': [{'name': x} for x in tags]}
|
||||
)
|
||||
exceptions.raise_from_response(response)
|
||||
|
||||
self._body.attributes.update({'tags': tags})
|
||||
|
||||
return self
|
||||
|
||||
@@ -90,3 +90,48 @@ class TestMetadefNamespace(base.BaseImageTest):
|
||||
metadef_namespace_description,
|
||||
metadef_namespace.description,
|
||||
)
|
||||
|
||||
def test_tags(self):
|
||||
# add tag
|
||||
metadef_namespace = self.operator_cloud.image.get_metadef_namespace(
|
||||
self.metadef_namespace.namespace
|
||||
)
|
||||
metadef_namespace.add_tag(self.operator_cloud.image, 't1')
|
||||
metadef_namespace.add_tag(self.operator_cloud.image, 't2')
|
||||
|
||||
# list tags
|
||||
metadef_namespace.fetch_tags(self.operator_cloud.image)
|
||||
md_tags = [tag['name'] for tag in metadef_namespace.tags]
|
||||
self.assertIn('t1', md_tags)
|
||||
self.assertIn('t2', md_tags)
|
||||
|
||||
# remove tag
|
||||
metadef_namespace.remove_tag(self.operator_cloud.image, 't1')
|
||||
metadef_namespace = self.operator_cloud.image.get_metadef_namespace(
|
||||
self.metadef_namespace.namespace
|
||||
)
|
||||
md_tags = [tag['name'] for tag in metadef_namespace.tags]
|
||||
self.assertIn('t2', md_tags)
|
||||
self.assertNotIn('t1', md_tags)
|
||||
|
||||
# add tags without append
|
||||
metadef_namespace.set_tags(self.operator_cloud.image, ["t1", "t2"])
|
||||
metadef_namespace.fetch_tags(self.operator_cloud.image)
|
||||
md_tags = [tag['name'] for tag in metadef_namespace.tags]
|
||||
self.assertIn('t1', md_tags)
|
||||
self.assertIn('t2', md_tags)
|
||||
|
||||
# add tags with append
|
||||
metadef_namespace.set_tags(
|
||||
self.operator_cloud.image, ["t3", "t4"], append=True
|
||||
)
|
||||
metadef_namespace.fetch_tags(self.operator_cloud.image)
|
||||
md_tags = [tag['name'] for tag in metadef_namespace.tags]
|
||||
self.assertIn('t1', md_tags)
|
||||
self.assertIn('t2', md_tags)
|
||||
self.assertIn('t3', md_tags)
|
||||
self.assertIn('t4', md_tags)
|
||||
|
||||
# remove all tags
|
||||
metadef_namespace.remove_all_tags(self.operator_cloud.image)
|
||||
self.assertEqual([], metadef_namespace.tags)
|
||||
|
||||
@@ -18,6 +18,7 @@ from keystoneauth1 import adapter
|
||||
from openstack import exceptions
|
||||
from openstack.image.v2 import metadef_namespace
|
||||
from openstack.tests.unit import base
|
||||
from openstack.tests.unit.test_resource import FakeResponse
|
||||
|
||||
|
||||
EXAMPLE = {
|
||||
@@ -97,3 +98,55 @@ class TestMetadefNamespace(base.TestCase):
|
||||
session.delete.assert_called_with(
|
||||
'metadefs/namespaces/OS::Cinder::Volumetype/objects'
|
||||
)
|
||||
|
||||
|
||||
class TestMetadefNamespaceTags(base.TestCase):
|
||||
# The tests in this class are very similar to those provided by
|
||||
# TestTagMixin. The main differences are:
|
||||
# - test_add_tag uses a ``PUT`` call instead of a ``POST`` call
|
||||
# - test_set_tag uses a ``PUT`` call instead of a ``POST`` call
|
||||
# - test_set_tag uses an optional ``X-OpenStack-Append`` header
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.base_path = 'metadefs/namespaces'
|
||||
self.response = FakeResponse({})
|
||||
|
||||
self.session = mock.Mock(spec=adapter.Adapter)
|
||||
self.session.post = mock.Mock(return_value=self.response)
|
||||
|
||||
def test_add_tag(self):
|
||||
res = metadef_namespace.MetadefNamespace(**EXAMPLE)
|
||||
sess = self.session
|
||||
|
||||
# Set some initial value to check add
|
||||
res.tags = ['blue', 'green']
|
||||
|
||||
result = res.add_tag(sess, 'lila')
|
||||
# Check tags attribute is updated
|
||||
self.assertEqual(['blue', 'green', 'lila'], res.tags)
|
||||
# Check the passed resource is returned
|
||||
self.assertEqual(res, result)
|
||||
url = self.base_path + '/' + res.id + '/tags/lila'
|
||||
sess.post.assert_called_once_with(url)
|
||||
|
||||
def test_set_tags(self):
|
||||
res = metadef_namespace.MetadefNamespace(**EXAMPLE)
|
||||
sess = self.session
|
||||
|
||||
# Set some initial value to check rewrite
|
||||
res.tags = ['blue_old', 'green_old']
|
||||
|
||||
result = res.set_tags(sess, ['blue', 'green'])
|
||||
# Check tags attribute is updated
|
||||
self.assertEqual(['blue', 'green'], res.tags)
|
||||
# Check the passed resource is returned
|
||||
self.assertEqual(res, result)
|
||||
url = self.base_path + '/' + res.id + '/tags'
|
||||
headers = {'X-OpenStack-Append': 'False'}
|
||||
jsonargs = {
|
||||
'tags': [
|
||||
{'name': 'blue'},
|
||||
{'name': 'green'},
|
||||
]
|
||||
}
|
||||
sess.post.assert_called_once_with(url, headers=headers, json=jsonargs)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for Image Metadef Tags to create, remove
|
||||
create-multiple, update tags.
|
||||
Reference in New Issue
Block a user