diff --git a/glareclient/osc/v1/artifacts.py b/glareclient/osc/v1/artifacts.py index 128c973..159ae30 100644 --- a/glareclient/osc/v1/artifacts.py +++ b/glareclient/osc/v1/artifacts.py @@ -508,6 +508,92 @@ class PublishArtifact(command.Lister): return print_artifact(client, data, parsed_args.type_name) +class AddTag(command.Lister): + """Add tag to artifact""" + + def get_parser(self, prog_name): + parser = super(AddTag, self).get_parser(prog_name) + parser.add_argument( + 'type_name', + metavar='', + action=TypeMapperAction, + help='Name of artifact type.', + ) + parser.add_argument( + 'name', + metavar='', + help='Name or id of the artifact to publish.', + ) + parser.add_argument( + 'tag', + metavar='', + help='Value of the tag to add to the artifact.', + ) + parser.add_argument( + '--artifact-version', '-V', + metavar='', + default='latest', + help='Version of the artifact.', + ) + parser.add_argument( + '--id', '-i', + action='store_true', + help='Flag indicates to use artifact id instead of its name.', + ) + return parser + + def take_action(self, parsed_args): + LOG.debug('take_action({0})'.format(parsed_args)) + client = self.app.client_manager.artifact + af_id = get_artifact_id(client, parsed_args) + data = client.artifacts.add_tag( + af_id, tag_value=parsed_args.tag, type_name=parsed_args.type_name) + return print_artifact(client, data, parsed_args.type_name) + + +class RemoveTag(command.Lister): + """Remove tag from the artifact""" + + def get_parser(self, prog_name): + parser = super(RemoveTag, self).get_parser(prog_name) + parser.add_argument( + 'type_name', + metavar='', + action=TypeMapperAction, + help='Name of artifact type.', + ) + parser.add_argument( + 'name', + metavar='', + help='Name or id of the artifact to publish.', + ) + parser.add_argument( + 'tag', + metavar='', + help='Value of the tag to remove from the artifact.', + ) + parser.add_argument( + '--artifact-version', '-V', + metavar='', + default='latest', + help='Version of the artifact.', + ) + parser.add_argument( + '--id', '-i', + action='store_true', + help='Flag indicates to use artifact id instead of its name.', + ) + return parser + + def take_action(self, parsed_args): + LOG.debug('take_action({0})'.format(parsed_args)) + client = self.app.client_manager.artifact + af_id = get_artifact_id(client, parsed_args) + data = client.artifacts.remove_tag( + af_id, tag_value=parsed_args.tag, type_name=parsed_args.type_name) + return print_artifact(client, data, parsed_args.type_name) + + class TypeList(command.Lister): """List of type names""" diff --git a/glareclient/tests/unit/osc/v1/fakes.py b/glareclient/tests/unit/osc/v1/fakes.py index c265caa..e58f977 100644 --- a/glareclient/tests/unit/osc/v1/fakes.py +++ b/glareclient/tests/unit/osc/v1/fakes.py @@ -89,6 +89,8 @@ class TestArtifacts(utils.TestCommand): self.app.client_manager.artifact.artifacts.list = mock_list self.app.client_manager.artifact.artifacts.get = mock_get self.app.client_manager.artifact.artifacts.get_by_name = mock_get + self.app.client_manager.artifact.artifacts.add_tag = mock_g_servs + self.app.client_manager.artifact.artifacts.remove_tag = mock_g_servs self.app.client_manager.artifact.artifacts.create = mock_g_servs self.app.client_manager.artifact.artifacts.update = mock_g_servs self.app.client_manager.artifact.artifacts.delete = mock_g_servs diff --git a/glareclient/tests/unit/osc/v1/test_artifacts.py b/glareclient/tests/unit/osc/v1/test_artifacts.py index 89a154a..c094356 100644 --- a/glareclient/tests/unit/osc/v1/test_artifacts.py +++ b/glareclient/tests/unit/osc/v1/test_artifacts.py @@ -423,6 +423,58 @@ class TestReactivateArtifacts(TestArtifacts): self.assertEqual(self.COLUMNS, name_fields) +class TestAddTag(TestArtifacts): + + def setUp(self): + super(TestAddTag, self).setUp() + self.artifact_mock.call.return_value = \ + api_art.Controller(self.http, type_name='images') + + # Command to test + self.cmd = osc_art.AddTag(self.app, None) + + def test_artifact_add_tag(self): + arglist = ['images', + 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', + '123'] + verify = [('type_name', 'images'), + ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), + ('id', True), + ('tag', '123')] + parsed_args = self.check_parser(self.cmd, arglist, verify) + columns, data = self.cmd.take_action(parsed_args) + + name_fields = set([column[0] for column in data]) + # Check that columns are correct + self.assertEqual(self.COLUMNS, name_fields) + + +class TestRemoveTag(TestArtifacts): + + def setUp(self): + super(TestRemoveTag, self).setUp() + self.artifact_mock.call.return_value = \ + api_art.Controller(self.http, type_name='images') + + # Command to test + self.cmd = osc_art.RemoveTag(self.app, None) + + def test_artifact_add_tag(self): + arglist = ['images', + 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', + '123'] + verify = [('type_name', 'images'), + ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), + ('id', True), + ('tag', '123')] + parsed_args = self.check_parser(self.cmd, arglist, verify) + columns, data = self.cmd.take_action(parsed_args) + + name_fields = set([column[0] for column in data]) + # Check that columns are correct + self.assertEqual(self.COLUMNS, name_fields) + + class TestPublishArtifacts(TestArtifacts): def setUp(self): diff --git a/glareclient/tests/unit/v1/fixtures.py b/glareclient/tests/unit/v1/fixtures.py index 2a8c08d..5f25162 100644 --- a/glareclient/tests/unit/v1/fixtures.py +++ b/glareclient/tests/unit/v1/fixtures.py @@ -243,4 +243,19 @@ data_fixtures = { ]}, ), }, + '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c': { + 'PATCH': ( + {}, + '' + ), + 'GET': ( + {}, + { + 'name': 'art_1', + 'id': '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + 'version': '0.0.0', + 'tags': ["a", "b", "c"] + } + ) + }, } diff --git a/glareclient/tests/unit/v1/test_artifacts.py b/glareclient/tests/unit/v1/test_artifacts.py index cfb39ff..7dcbeb8 100644 --- a/glareclient/tests/unit/v1/test_artifacts.py +++ b/glareclient/tests/unit/v1/test_artifacts.py @@ -412,3 +412,55 @@ class TestController(testtools.TestCase): 'http://fake_url')] self.assertEqual(expect_call, self.api.calls) self.assertIsNone(data) + + def test_add_tag(self): + art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' + data = self.controller.add_tag( + art_id, tag_value="123", type_name='images') + expect_call = [ + ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {}, None), + ('PATCH', + '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {'Content-Type': 'application/json-patch+json'}, + [{'op': 'add', + 'path': '/tags', + 'value': ['a', 'b', 'c', '123']}])] + self.assertEqual(expect_call, self.api.calls) + self.assertIsNotNone(data) + + def test_add_existing_tag(self): + art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' + data = self.controller.add_tag( + art_id, tag_value="a", type_name='images') + expect_call = [ + ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {}, None)] + self.assertEqual(expect_call, self.api.calls) + self.assertIsNotNone(data) + + def test_remove_tag(self): + art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' + data = self.controller.remove_tag( + art_id, tag_value="a", type_name='images') + expect_call = [ + ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {}, None), + ('PATCH', + '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {'Content-Type': 'application/json-patch+json'}, + [{'op': 'add', + 'path': '/tags', + 'value': ['b', 'c']}])] + self.assertEqual(expect_call, self.api.calls) + self.assertIsNotNone(data) + + def test_remove_nonexisting_tag(self): + art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' + data = self.controller.remove_tag( + art_id, tag_value="123", type_name='images') + expect_call = [ + ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', + {}, None)] + self.assertEqual(expect_call, self.api.calls) + self.assertIsNotNone(data) diff --git a/glareclient/v1/artifacts.py b/glareclient/v1/artifacts.py index f1a814a..22320d4 100644 --- a/glareclient/v1/artifacts.py +++ b/glareclient/v1/artifacts.py @@ -274,3 +274,33 @@ class Controller(object): url = '/schemas/%s' % type_name resp, body = self.http_client.get(url) return body['schemas'][type_name] + + def add_tag(self, artifact_id, tag_value, type_name=None): + """Add tag to artifact. + + :param artifact_id: ID of the artifact to add a tag + :param tag_value: value of the tag to add + """ + type_name = self._check_type_name(type_name) + url = '/artifacts/%s/%s' % (type_name, artifact_id) + resp, body = self.http_client.get(url) + tags = body['tags'] + if tag_value in tags: + return body + tags.append(tag_value) + return self.update(artifact_id, type_name, tags=tags) + + def remove_tag(self, artifact_id, tag_value, type_name=None): + """Remove tag from artifact. + + :param artifact_id: ID of the artifact to remove a tag + :param tag_value: value of the tag to remove + """ + type_name = self._check_type_name(type_name) + url = '/artifacts/%s/%s' % (type_name, artifact_id) + resp, body = self.http_client.get(url) + tags = body['tags'] + if tag_value not in tags: + return body + tags.remove(tag_value) + return self.update(artifact_id, type_name, tags=tags) diff --git a/setup.cfg b/setup.cfg index 32806f8..bfb8d93 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,6 +49,8 @@ openstack.artifact.v1 = artifact_download = glareclient.osc.v1.blobs:DownloadBlob artifact_type-list = glareclient.osc.v1.artifacts:TypeList artifact_schema = glareclient.osc.v1.artifacts:TypeSchema + artifact_add_tag = glareclient.osc.v1.artifacts:AddTag + artifact_remove_tag = glareclient.osc.v1.artifacts:RemoveTag [build_sphinx] source-dir = doc/source