Add project tags to keystoneclient
Adds the client functionality for the following project tag calls: - Create a project tag on a project - Check if a project tag exists on a project - List project tags on a project - Modify project tags on a project - Delete a specific project tag on a project - Delete all project tags on a project Co-Authored-By: Jess Egler <jess.egler@gmail.com> Co-Authored-By: Rohan Arora <ra271w@att.com> Co-Authored-By: Tin Lam <tin@irrational.io> Partially Implements: bp project-tags Change-Id: I486b2969ae0aa2638842d842fb8b0955cc086d25
This commit is contained in:
		@@ -356,6 +356,13 @@ class CrudManager(Manager):
 | 
				
			|||||||
        if params is None:
 | 
					        if params is None:
 | 
				
			||||||
            return ''
 | 
					            return ''
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
 | 
					            # NOTE(spilla) Since the manager cannot take in a hyphen as a
 | 
				
			||||||
 | 
					            # key in the kwarg, it is passed in with a _.  This needs to be
 | 
				
			||||||
 | 
					            # replaced with a proper hyphen for the URL to work properly.
 | 
				
			||||||
 | 
					            tags_params = ('tags_any', 'not_tags', 'not_tags_any')
 | 
				
			||||||
 | 
					            for tag_param in tags_params:
 | 
				
			||||||
 | 
					                if tag_param in params:
 | 
				
			||||||
 | 
					                    params[tag_param.replace('_', '-')] = params.pop(tag_param)
 | 
				
			||||||
            return '?%s' % urllib.parse.urlencode(params, doseq=True)
 | 
					            return '?%s' % urllib.parse.urlencode(params, doseq=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def build_key_only_query(self, params_list):
 | 
					    def build_key_only_query(self, params_list):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,9 +71,10 @@ class Domain(Base):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Project(Base):
 | 
					class Project(Base):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, client, domain_id=None, parent=None):
 | 
					    def __init__(self, client, domain_id=None, parent=None, tags=None):
 | 
				
			||||||
        super(Project, self).__init__(client, domain_id)
 | 
					        super(Project, self).__init__(client, domain_id)
 | 
				
			||||||
        self.parent = parent
 | 
					        self.parent = parent
 | 
				
			||||||
 | 
					        self.tags = tags if tags else []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        super(Project, self).setUp()
 | 
					        super(Project, self).setUp()
 | 
				
			||||||
@@ -81,7 +82,8 @@ class Project(Base):
 | 
				
			|||||||
        self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
 | 
					        self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
 | 
				
			||||||
                    'domain': self.domain_id,
 | 
					                    'domain': self.domain_id,
 | 
				
			||||||
                    'enabled': True,
 | 
					                    'enabled': True,
 | 
				
			||||||
                    'parent': self.parent}
 | 
					                    'parent': self.parent,
 | 
				
			||||||
 | 
					                    'tags': self.tags}
 | 
				
			||||||
        self.entity = self.client.projects.create(**self.ref)
 | 
					        self.entity = self.client.projects.create(**self.ref)
 | 
				
			||||||
        self.addCleanup(self.client.projects.delete, self.entity)
 | 
					        self.addCleanup(self.client.projects.delete, self.entity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,7 @@ class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.test_project = fixtures.Project(self.client, self.test_domain.id)
 | 
					        self.test_project = fixtures.Project(self.client, self.test_domain.id)
 | 
				
			||||||
        self.useFixture(self.test_project)
 | 
					        self.useFixture(self.test_project)
 | 
				
			||||||
 | 
					        self.special_tag = '~`!@#$%^&*()-_+=<>.? \'"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_create_subproject(self):
 | 
					    def test_create_subproject(self):
 | 
				
			||||||
        project_ref = {
 | 
					        project_ref = {
 | 
				
			||||||
@@ -188,3 +189,257 @@ class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin):
 | 
				
			|||||||
        self.assertRaises(http.NotFound,
 | 
					        self.assertRaises(http.NotFound,
 | 
				
			||||||
                          self.client.projects.get,
 | 
					                          self.client.projects.get,
 | 
				
			||||||
                          project.id)
 | 
					                          project.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_list_projects_with_tag_filters(self):
 | 
				
			||||||
 | 
					        project_one = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1'])
 | 
				
			||||||
 | 
					        project_two = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1', 'tag2'])
 | 
				
			||||||
 | 
					        project_three = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag2', 'tag3'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.useFixture(project_one)
 | 
				
			||||||
 | 
					        self.useFixture(project_two)
 | 
				
			||||||
 | 
					        self.useFixture(project_three)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = self.client.projects.list(tags='tag1')
 | 
				
			||||||
 | 
					        project_ids = []
 | 
				
			||||||
 | 
					        for project in projects:
 | 
				
			||||||
 | 
					            project_ids.append(project.id)
 | 
				
			||||||
 | 
					        self.assertIn(project_one.id, project_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = self.client.projects.list(tags_any='tag1')
 | 
				
			||||||
 | 
					        project_ids = []
 | 
				
			||||||
 | 
					        for project in projects:
 | 
				
			||||||
 | 
					            project_ids.append(project.id)
 | 
				
			||||||
 | 
					        self.assertIn(project_one.id, project_ids)
 | 
				
			||||||
 | 
					        self.assertIn(project_two.id, project_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = self.client.projects.list(not_tags='tag1')
 | 
				
			||||||
 | 
					        project_ids = []
 | 
				
			||||||
 | 
					        for project in projects:
 | 
				
			||||||
 | 
					            project_ids.append(project.id)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_one.id, project_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = self.client.projects.list(not_tags_any='tag1,tag2')
 | 
				
			||||||
 | 
					        project_ids = []
 | 
				
			||||||
 | 
					        for project in projects:
 | 
				
			||||||
 | 
					            project_ids.append(project.id)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_one.id, project_ids)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_two.id, project_ids)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_three.id, project_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = self.client.projects.list(tags='tag1,tag2')
 | 
				
			||||||
 | 
					        project_ids = []
 | 
				
			||||||
 | 
					        for project in projects:
 | 
				
			||||||
 | 
					            project_ids.append(project.id)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_one.id, project_ids)
 | 
				
			||||||
 | 
					        self.assertIn(project_two.id, project_ids)
 | 
				
			||||||
 | 
					        self.assertNotIn(project_three.id, project_ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_add_tag(self):
 | 
				
			||||||
 | 
					        project = fixtures.Project(self.client, self.test_domain.id)
 | 
				
			||||||
 | 
					        self.useFixture(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project.add_tag('tag1')
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual(['tag1'], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # verify there is an error when you try to add the same tag
 | 
				
			||||||
 | 
					        self.assertRaises(http.BadRequest,
 | 
				
			||||||
 | 
					                          project.add_tag,
 | 
				
			||||||
 | 
					                          'tag1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_update_tags(self):
 | 
				
			||||||
 | 
					        project = fixtures.Project(self.client, self.test_domain.id)
 | 
				
			||||||
 | 
					        self.useFixture(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project.update_tags(['tag1', 'tag2', self.special_tag])
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertIn('tag1', tags)
 | 
				
			||||||
 | 
					        self.assertIn('tag2', tags)
 | 
				
			||||||
 | 
					        self.assertIn(self.special_tag, tags)
 | 
				
			||||||
 | 
					        self.assertEqual(3, len(tags))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project.update_tags([])
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # cannot have duplicate tags in update
 | 
				
			||||||
 | 
					        self.assertRaises(http.BadRequest,
 | 
				
			||||||
 | 
					                          project.update_tags,
 | 
				
			||||||
 | 
					                          ['tag1', 'tag1'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_delete_tag(self):
 | 
				
			||||||
 | 
					        project = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1', self.special_tag])
 | 
				
			||||||
 | 
					        self.useFixture(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project.delete_tag('tag1')
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([self.special_tag], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project.delete_tag(self.special_tag)
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_delete_all_tags(self):
 | 
				
			||||||
 | 
					        project_one = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project_two = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1', 'tag2', self.special_tag])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project_three = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.useFixture(project_one)
 | 
				
			||||||
 | 
					        self.useFixture(project_two)
 | 
				
			||||||
 | 
					        self.useFixture(project_three)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result_one = project_one.delete_all_tags()
 | 
				
			||||||
 | 
					        tags_one = self.client.projects.get(project_one.id).tags
 | 
				
			||||||
 | 
					        tags_two = self.client.projects.get(project_two.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], result_one)
 | 
				
			||||||
 | 
					        self.assertEqual([], tags_one)
 | 
				
			||||||
 | 
					        self.assertIn('tag1', tags_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result_two = project_two.delete_all_tags()
 | 
				
			||||||
 | 
					        tags_two = self.client.projects.get(project_two.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], result_two)
 | 
				
			||||||
 | 
					        self.assertEqual([], tags_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result_three = project_three.delete_all_tags()
 | 
				
			||||||
 | 
					        tags_three = self.client.projects.get(project_three.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual([], result_three)
 | 
				
			||||||
 | 
					        self.assertEqual([], tags_three)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_list_tags(self):
 | 
				
			||||||
 | 
					        tags_one = ['tag1']
 | 
				
			||||||
 | 
					        project_one = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=tags_one)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags_two = ['tag1', 'tag2']
 | 
				
			||||||
 | 
					        project_two = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=tags_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags_three = []
 | 
				
			||||||
 | 
					        project_three = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=tags_three)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.useFixture(project_one)
 | 
				
			||||||
 | 
					        self.useFixture(project_two)
 | 
				
			||||||
 | 
					        self.useFixture(project_three)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result_one = project_one.list_tags()
 | 
				
			||||||
 | 
					        result_two = project_two.list_tags()
 | 
				
			||||||
 | 
					        result_three = project_three.list_tags()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for tag in tags_one:
 | 
				
			||||||
 | 
					            self.assertIn(tag, result_one)
 | 
				
			||||||
 | 
					        self.assertEqual(1, len(result_one))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for tag in tags_two:
 | 
				
			||||||
 | 
					            self.assertIn(tag, result_two)
 | 
				
			||||||
 | 
					        self.assertEqual(2, len(result_two))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for tag in tags_three:
 | 
				
			||||||
 | 
					            self.assertIn(tag, result_three)
 | 
				
			||||||
 | 
					        self.assertEqual(0, len(result_three))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_check_tag(self):
 | 
				
			||||||
 | 
					        project = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id,
 | 
				
			||||||
 | 
					            tags=['tag1'])
 | 
				
			||||||
 | 
					        self.useFixture(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags = self.client.projects.get(project.id).tags
 | 
				
			||||||
 | 
					        self.assertEqual(['tag1'], tags)
 | 
				
			||||||
 | 
					        self.assertTrue(project.check_tag('tag1'))
 | 
				
			||||||
 | 
					        self.assertFalse(project.check_tag('tag2'))
 | 
				
			||||||
 | 
					        self.assertFalse(project.check_tag(self.special_tag))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_add_invalid_tags(self):
 | 
				
			||||||
 | 
					        project_one = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.useFixture(project_one)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.add_tag,
 | 
				
			||||||
 | 
					                          ',')
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.add_tag,
 | 
				
			||||||
 | 
					                          '/')
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.add_tag,
 | 
				
			||||||
 | 
					                          '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_update_invalid_tags(self):
 | 
				
			||||||
 | 
					        tags_comma = ['tag1', ',']
 | 
				
			||||||
 | 
					        tags_slash = ['tag1', '/']
 | 
				
			||||||
 | 
					        tags_blank = ['tag1', '']
 | 
				
			||||||
 | 
					        project_one = fixtures.Project(
 | 
				
			||||||
 | 
					            self.client, self.test_domain.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.useFixture(project_one)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.update_tags,
 | 
				
			||||||
 | 
					                          tags_comma)
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.update_tags,
 | 
				
			||||||
 | 
					                          tags_slash)
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          project_one.update_tags,
 | 
				
			||||||
 | 
					                          tags_blank)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_project_invalid_tags(self):
 | 
				
			||||||
 | 
					        project_ref = {
 | 
				
			||||||
 | 
					            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'domain': self.test_domain.id,
 | 
				
			||||||
 | 
					            'enabled': True,
 | 
				
			||||||
 | 
					            'description': uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'tags': ','}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          self.client.projects.create,
 | 
				
			||||||
 | 
					                          **project_ref)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project_ref = {
 | 
				
			||||||
 | 
					            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'domain': self.test_domain.id,
 | 
				
			||||||
 | 
					            'enabled': True,
 | 
				
			||||||
 | 
					            'description': uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'tags': '/'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          self.client.projects.create,
 | 
				
			||||||
 | 
					                          **project_ref)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        project_ref = {
 | 
				
			||||||
 | 
					            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'domain': self.test_domain.id,
 | 
				
			||||||
 | 
					            'enabled': True,
 | 
				
			||||||
 | 
					            'description': uuid.uuid4().hex,
 | 
				
			||||||
 | 
					            'tags': ''}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.BadRequest,
 | 
				
			||||||
 | 
					                          self.client.projects.create,
 | 
				
			||||||
 | 
					                          **project_ref)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -312,3 +312,86 @@ class ProjectTests(utils.ClientTestCase, utils.CrudTests):
 | 
				
			|||||||
        # server, a different implementation might not fail this request.
 | 
					        # server, a different implementation might not fail this request.
 | 
				
			||||||
        self.assertRaises(ksa_exceptions.Forbidden, self.manager.update,
 | 
					        self.assertRaises(ksa_exceptions.Forbidden, self.manager.update,
 | 
				
			||||||
                          ref['id'], **utils.parameterize(req_ref))
 | 
					                          ref['id'], **utils.parameterize(req_ref))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_add_tag(self):
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					        tag_name = "blue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stub_url("PUT",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags", tag_name],
 | 
				
			||||||
 | 
					                      status_code=201)
 | 
				
			||||||
 | 
					        self.manager.add_tag(ref['id'], tag_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_update_tags(self):
 | 
				
			||||||
 | 
					        new_tags = ["blue", "orange"]
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stub_url("PUT",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags"],
 | 
				
			||||||
 | 
					                      json={"tags": new_tags},
 | 
				
			||||||
 | 
					                      status_code=200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret = self.manager.update_tags(ref['id'], new_tags)
 | 
				
			||||||
 | 
					        self.assertEqual(ret, new_tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_delete_tag(self):
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					        tag_name = "blue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stub_url("DELETE",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags", tag_name],
 | 
				
			||||||
 | 
					                      status_code=204)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.manager.delete_tag(ref['id'], tag_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_delete_all_tags(self):
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stub_url("PUT",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags"],
 | 
				
			||||||
 | 
					                      json={"tags": []},
 | 
				
			||||||
 | 
					                      status_code=200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret = self.manager.update_tags(ref['id'], [])
 | 
				
			||||||
 | 
					        self.assertEqual([], ret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_list_tags(self):
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					        tags = ["blue", "orange", "green"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stub_url("GET",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags"],
 | 
				
			||||||
 | 
					                      json={"tags": tags},
 | 
				
			||||||
 | 
					                      status_code=200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret_tags = self.manager.list_tags(ref['id'])
 | 
				
			||||||
 | 
					        self.assertEqual(tags, ret_tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_check_tag(self):
 | 
				
			||||||
 | 
					        ref = self.new_ref()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tag_name = "blue"
 | 
				
			||||||
 | 
					        self.stub_url("HEAD",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags", tag_name],
 | 
				
			||||||
 | 
					                      status_code=204)
 | 
				
			||||||
 | 
					        self.assertTrue(self.manager.check_tag(ref['id'], tag_name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        no_tag = "orange"
 | 
				
			||||||
 | 
					        self.stub_url("HEAD",
 | 
				
			||||||
 | 
					                      parts=[self.collection_key, ref['id'], "tags", no_tag],
 | 
				
			||||||
 | 
					                      status_code=404)
 | 
				
			||||||
 | 
					        self.assertFalse(self.manager.check_tag(ref['id'], no_tag))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _build_project_response(self, tags):
 | 
				
			||||||
 | 
					        project_id = uuid.uuid4().hex
 | 
				
			||||||
 | 
					        ret = {"projects": [
 | 
				
			||||||
 | 
					            {"is_domain": False,
 | 
				
			||||||
 | 
					             "description": "",
 | 
				
			||||||
 | 
					             "tags": tags,
 | 
				
			||||||
 | 
					             "enabled": True,
 | 
				
			||||||
 | 
					             "id": project_id,
 | 
				
			||||||
 | 
					             "parent_id": "default",
 | 
				
			||||||
 | 
					             "domain_id": "default",
 | 
				
			||||||
 | 
					             "name": project_id}
 | 
				
			||||||
 | 
					        ]}
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,8 @@
 | 
				
			|||||||
#    License for the specific language governing permissions and limitations
 | 
					#    License for the specific language governing permissions and limitations
 | 
				
			||||||
#    under the License.
 | 
					#    under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six.moves.urllib as urllib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from keystoneclient import base
 | 
					from keystoneclient import base
 | 
				
			||||||
from keystoneclient import exceptions
 | 
					from keystoneclient import exceptions
 | 
				
			||||||
from keystoneclient.i18n import _
 | 
					from keystoneclient.i18n import _
 | 
				
			||||||
@@ -52,6 +54,24 @@ class Project(base.Resource):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return retval
 | 
					        return retval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_tag(self, tag):
 | 
				
			||||||
 | 
					        self.manager.add_tag(self, tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_tags(self, tags):
 | 
				
			||||||
 | 
					        return self.manager.update_tags(self, tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_tag(self, tag):
 | 
				
			||||||
 | 
					        self.manager.delete_tag(self, tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_all_tags(self):
 | 
				
			||||||
 | 
					        return self.manager.update_tags(self, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list_tags(self):
 | 
				
			||||||
 | 
					        return self.manager.list_tags(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def check_tag(self, tag):
 | 
				
			||||||
 | 
					        return self.manager.check_tag(self, tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProjectManager(base.CrudManager):
 | 
					class ProjectManager(base.CrudManager):
 | 
				
			||||||
    """Manager class for manipulating Identity projects."""
 | 
					    """Manager class for manipulating Identity projects."""
 | 
				
			||||||
@@ -101,17 +121,24 @@ class ProjectManager(base.CrudManager):
 | 
				
			|||||||
                     assignments on.
 | 
					                     assignments on.
 | 
				
			||||||
        :type user: str or :class:`keystoneclient.v3.users.User`
 | 
					        :type user: str or :class:`keystoneclient.v3.users.User`
 | 
				
			||||||
        :param kwargs: any other attribute provided will filter projects on.
 | 
					        :param kwargs: any other attribute provided will filter projects on.
 | 
				
			||||||
 | 
					                       Project tags filter keyword: ``tags``, ``tags_any``,
 | 
				
			||||||
 | 
					                       ``not_tags``, and ``not_tags_any``. tag attribute type
 | 
				
			||||||
 | 
					                       string. Pass in a comma separated string to filter
 | 
				
			||||||
 | 
					                       with multiple tags.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :returns: a list of projects.
 | 
					        :returns: a list of projects.
 | 
				
			||||||
        :rtype: list of :class:`keystoneclient.v3.projects.Project`
 | 
					        :rtype: list of :class:`keystoneclient.v3.projects.Project`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        base_url = '/users/%s' % base.getid(user) if user else None
 | 
					        base_url = '/users/%s' % base.getid(user) if user else None
 | 
				
			||||||
        return super(ProjectManager, self).list(
 | 
					        projects = super(ProjectManager, self).list(
 | 
				
			||||||
            base_url=base_url,
 | 
					            base_url=base_url,
 | 
				
			||||||
            domain_id=base.getid(domain),
 | 
					            domain_id=base.getid(domain),
 | 
				
			||||||
            fallback_to_auth=True,
 | 
					            fallback_to_auth=True,
 | 
				
			||||||
            **kwargs)
 | 
					            **kwargs)
 | 
				
			||||||
 | 
					        for p in projects:
 | 
				
			||||||
 | 
					            p.tags = self._encode_tags(getattr(p, 'tags', []))
 | 
				
			||||||
 | 
					        return projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids,
 | 
					    def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids,
 | 
				
			||||||
                                                      parents_as_list):
 | 
					                                                      parents_as_list):
 | 
				
			||||||
@@ -174,7 +201,9 @@ class ProjectManager(base.CrudManager):
 | 
				
			|||||||
        query = self.build_key_only_query(query_params)
 | 
					        query = self.build_key_only_query(query_params)
 | 
				
			||||||
        dict_args = {'project_id': base.getid(project)}
 | 
					        dict_args = {'project_id': base.getid(project)}
 | 
				
			||||||
        url = self.build_url(dict_args_in_out=dict_args)
 | 
					        url = self.build_url(dict_args_in_out=dict_args)
 | 
				
			||||||
        return self._get(url + query, self.key)
 | 
					        p = self._get(url + query, self.key)
 | 
				
			||||||
 | 
					        p.tags = self._encode_tags(getattr(p, 'tags', []))
 | 
				
			||||||
 | 
					        return p
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update(self, project, name=None, domain=None, description=None,
 | 
					    def update(self, project, name=None, domain=None, description=None,
 | 
				
			||||||
               enabled=None, **kwargs):
 | 
					               enabled=None, **kwargs):
 | 
				
			||||||
@@ -213,3 +242,82 @@ class ProjectManager(base.CrudManager):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return super(ProjectManager, self).delete(
 | 
					        return super(ProjectManager, self).delete(
 | 
				
			||||||
            project_id=base.getid(project))
 | 
					            project_id=base.getid(project))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _encode_tags(self, tags):
 | 
				
			||||||
 | 
					        """Encode tags to non-unicode string in python2.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param tags: list of unicode tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: List of strings
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [str(t) for t in tags]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_tag(self, project, tag):
 | 
				
			||||||
 | 
					        """Add a tag to a project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param project: project to add a tag to.
 | 
				
			||||||
 | 
					        :param tag: str name of tag.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        url = "/projects/%s/tags/%s" % (base.getid(project),
 | 
				
			||||||
 | 
					                                        urllib.parse.quote(tag))
 | 
				
			||||||
 | 
					        self.client.put(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_tags(self, project, tags):
 | 
				
			||||||
 | 
					        """Update tag list of a project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Replaces current tag list with list specified in tags parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param project: project to update.
 | 
				
			||||||
 | 
					        :param tags: list of str tag names to add to the project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: list of tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        url = "/projects/%s/tags" % base.getid(project)
 | 
				
			||||||
 | 
					        for tag in tags:
 | 
				
			||||||
 | 
					            tag = urllib.parse.quote(tag)
 | 
				
			||||||
 | 
					        resp, body = self.client.put(url, body={"tags": tags})
 | 
				
			||||||
 | 
					        return body['tags']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_tag(self, project, tag):
 | 
				
			||||||
 | 
					        """Remove tag from project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param projectd: project to remove tag from.
 | 
				
			||||||
 | 
					        :param tag: str name of tag to remove from project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self._delete(
 | 
				
			||||||
 | 
					            "/projects/%s/tags/%s" % (base.getid(project),
 | 
				
			||||||
 | 
					                                      urllib.parse.quote(tag)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list_tags(self, project):
 | 
				
			||||||
 | 
					        """List tags associated with project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param project: project to list tags for.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: list of str tag names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        url = "/projects/%s/tags" % base.getid(project)
 | 
				
			||||||
 | 
					        resp, body = self.client.get(url)
 | 
				
			||||||
 | 
					        return self._encode_tags(body['tags'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def check_tag(self, project, tag):
 | 
				
			||||||
 | 
					        """Check if tag is associated with project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param project: project to check tags for.
 | 
				
			||||||
 | 
					        :param tag: str name of tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: true if tag is associated, false otherwise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        url = "/projects/%s/tags/%s" % (base.getid(project),
 | 
				
			||||||
 | 
					                                        urllib.parse.quote(tag))
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.client.head(url)
 | 
				
			||||||
 | 
					            # no errors means found the tag
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except exceptions.NotFound:
 | 
				
			||||||
 | 
					            # 404 means tag not in project
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								releasenotes/notes/project-tags-1f8a32d389951e7a.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					features:
 | 
				
			||||||
 | 
					  - |
 | 
				
			||||||
 | 
					    [`blueprint project-tags <https://blueprints.launchpad.net/keystone/+spec/project-tags>`_]
 | 
				
			||||||
 | 
					    The keystoneclient now supports project tags feature in keystone.  This
 | 
				
			||||||
 | 
					    allows operators to use the client to associate tags to a project,
 | 
				
			||||||
 | 
					    retrieve tags associated with a project, delete tags associated with a
 | 
				
			||||||
 | 
					    project, and filter projects based on tags.
 | 
				
			||||||
		Reference in New Issue
	
	Block a user