From db53fcb18785e4ab7fcd26ef232454d13f32e918 Mon Sep 17 00:00:00 2001 From: Alexander Kislitsky Date: Mon, 12 Sep 2016 17:38:44 +0300 Subject: [PATCH] Delete keys from resource values/overrides handled in fuel2 Keys deletion for values/overrides handled. Default cliff formatter used for values/overrides operations. Message on values/overrides set command added. Change-Id: Ie47ff6a1cb98764447d045816ea6472a0701dcc3 Closes-Bug: #1622600 --- setup.cfg | 4 ++ tuning_box/cli/resources.py | 96 +++++++++++++++++++++----- tuning_box/fuelclient.py | 8 +++ tuning_box/tests/cli/test_resources.py | 75 +++++++++++++++++--- 4 files changed, 155 insertions(+), 28 deletions(-) diff --git a/setup.cfg b/setup.cfg index f9663fe..e9e5d98 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,9 @@ nailgun.extensions = tuning_box.cli = get = tuning_box.cli.resources:Get set = tuning_box.cli.resources:Set + del = tuning_box.cli.resources:Delete override = tuning_box.cli.resources:Override + rm_override = tuning_box.cli.resources:DeleteOverride env_create = tuning_box.cli.environments:CreateEnvironment env_list = tuning_box.cli.environments:ListEnvironments env_show = tuning_box.cli.environments:ShowEnvironment @@ -70,7 +72,9 @@ tuning_box.cli = fuelclient = config_get = tuning_box.fuelclient:Get config_set = tuning_box.fuelclient:Set + config_del = tuning_box.fuelclient:Delete config_override = tuning_box.fuelclient:Override + config_rm_override = tuning_box.fuelclient:DeleteOverride config_env_create = tuning_box.fuelclient:CreateEnvironment config_env_list = tuning_box.fuelclient:ListEnvironments config_env_show = tuning_box.fuelclient:ShowEnvironment diff --git a/tuning_box/cli/resources.py b/tuning_box/cli/resources.py index 168831e..f8bb600 100644 --- a/tuning_box/cli/resources.py +++ b/tuning_box/cli/resources.py @@ -11,9 +11,13 @@ # under the License. import json +import six import yaml -from tuning_box.cli import base +from cliff import show +from fuelclient.cli import error as fc_error +from fuelclient.common import data_utils + from tuning_box.cli.base import BaseCommand from tuning_box.cli.base import level_converter @@ -22,20 +26,20 @@ class ResourcesCommand(BaseCommand): def get_parser(self, *args, **kwargs): parser = super(ResourcesCommand, self).get_parser(*args, **kwargs) parser.add_argument( - '--env', + '-e', '--env', type=int, required=True, help="ID of environment to get data from", ) parser.add_argument( - '--level', + '-l', '--level', type=level_converter, default=[], help=("Level to get data from. Should be in format " "parent_level=parent1,level=value2"), ) parser.add_argument( - '--resource', + '-r', '--resource', type=str, required=True, help="Name or ID of resource to get data from", @@ -51,53 +55,59 @@ class ResourcesCommand(BaseCommand): ) -class Get(base.FormattedCommand, ResourcesCommand): +class Get(show.ShowOne, ResourcesCommand): + def get_parser(self, *args, **kwargs): parser = super(Get, self).get_parser(*args, **kwargs) parser.add_argument( - '--key', + '-k', '--key', type=str, help="Name of key to get from the resource", ) return parser def take_action(self, parsed_args): - res = self.get_client().get( + response = self.get_client().get( self.get_resource_url(parsed_args), params='effective', ) key = parsed_args.key - if key is None: - return res - value = res[key] - if parsed_args.format != 'plain': - return {key: value} + if key is not None: + result = {key: response.get(key, {})} else: - return value + result = response + columns = sorted(result.keys()) + try: + data = data_utils.get_display_data_single(columns, result) + return columns, data + except fc_error.BadDataException: + return zip(*response.items()) class Set(ResourcesCommand): + url_last_part = 'values' + entity_name = 'ResourceValue' def get_parser(self, *args, **kwargs): parser = super(Set, self).get_parser(*args, **kwargs) parser.add_argument( - '--key', + '-k', '--key', type=str, help="Name of key to set in the resource", ) parser.add_argument( - '--value', + '-v', '--value', type=str, help="Value for a key to set in the resource", ) parser.add_argument( - '--type', + '-t', '--type', choices=('null', 'int', 'str', 'json', 'yaml', 'bool'), help="Type of value passed in --value", ) parser.add_argument( - '--format', + '-f', '--format', choices=('json', 'yaml'), help="Format of data passed to stdin", ) @@ -165,6 +175,14 @@ class Set(ResourcesCommand): return doc assert False, "Shouldn't get here" + def get_update_message(self, parsed_args): + if parsed_args.key is None: + message = '{0} was set\n'.format(self.entity_name) + else: + message = '{0} for key {1} was set\n'.format( + self.entity_name, parsed_args.key) + return message + def take_action(self, parsed_args): self.verify_arguments(parsed_args) value = self.get_value_to_set(parsed_args) @@ -176,8 +194,50 @@ class Set(ResourcesCommand): resource[parsed_args.key] = value else: resource = value - client.put(resource_url, resource) + result = client.put(resource_url, resource) + if result is None: + result = self.get_update_message(parsed_args) + self.app.stdout.write(six.text_type(result)) + + +class Delete(ResourcesCommand): + + url_last_part = 'values' + entity_name = 'ResourceValue' + + def get_parser(self, *args, **kwargs): + parser = super(Delete, self).get_parser(*args, **kwargs) + parser.add_argument( + '-k', '--key', + type=str, + help="Name of key to delete from the resource", + required=True + ) + return parser + + def get_deletion_message(self, parsed_args): + return '{0} for key {1} was deleted\n'.format( + self.entity_name, parsed_args.key) + + def get_resource_url(self, parsed_args, last_part='values'): + url = super(Delete, self).get_resource_url( + parsed_args, last_part=last_part) + return url + '/keys/delete' + + def take_action(self, parsed_args): + client = self.get_client() + resource_url = self.get_resource_url(parsed_args, self.url_last_part) + result = client.patch(resource_url, [[parsed_args.key]]) + if result is None: + result = self.get_deletion_message(parsed_args) + self.app.stdout.write(six.text_type(result)) class Override(Set): url_last_part = 'overrides' + entity_name = 'ResourceOverride' + + +class DeleteOverride(Delete): + url_last_part = 'overrides' + entity_name = 'ResourceOverride' diff --git a/tuning_box/fuelclient.py b/tuning_box/fuelclient.py index 1c89938..dfe268a 100644 --- a/tuning_box/fuelclient.py +++ b/tuning_box/fuelclient.py @@ -53,10 +53,18 @@ class Set(FuelBaseCommand, resources.Set): pass +class Delete(FuelBaseCommand, resources.Delete): + pass + + class Override(FuelBaseCommand, resources.Override): pass +class DeleteOverride(FuelBaseCommand, resources.DeleteOverride): + pass + + class CreateEnvironment(FuelBaseCommand, environments.CreateEnvironment): pass diff --git a/tuning_box/tests/cli/test_resources.py b/tuning_box/tests/cli/test_resources.py index 8eef773..815dcc0 100644 --- a/tuning_box/tests/cli/test_resources.py +++ b/tuning_box/tests/cli/test_resources.py @@ -50,13 +50,13 @@ class TestGet(testscenarios.WithScenarios, _BaseCLITest): for s in [ ('global,json', ( '/environments/1/resources/1/values?effective', - 'get --env 1 --resource 1', - '{"hello": "world"}', + 'get --env 1 --resource 1 --format=json', + '{\n "hello": "world"\n}', )), ('lowlevel,json', ( '/environments/1/lvl1/value1/resources/1/values?effective', - 'get --env 1 --level lvl1=value1 --resource 1', - '{"hello": "world"}', + 'get --env 1 --level lvl1=value1 --resource 1 --format=json', + '{\n "hello": "world"\n}', )), ('global,yaml', ( '/environments/1/resources/1/values?effective', @@ -68,21 +68,26 @@ class TestGet(testscenarios.WithScenarios, _BaseCLITest): 'get --env 1 --level lvl1=value1 --resource 1 --format yaml', 'hello: world\n', )), - ('key,plain', ( - '/environments/1/resources/1/values?effective', - 'get --env 1 --resource 1 --key hello --format plain', - 'world', - )), ('key,json', ( '/environments/1/resources/1/values?effective', 'get --env 1 --resource 1 --key hello --format json', - '{"hello": "world"}', + '{\n "hello": "world"\n}', )), ('key,yaml', ( '/environments/1/resources/1/values?effective', 'get --env 1 --resource 1 --key hello --format yaml', 'hello: world\n', )), + ('no_key,json', ( + '/environments/1/resources/1/values?effective', + 'get --env 1 --resource 1 --key no --format json', + '{\n "no": {}\n}', + )), + ('no_key,yaml', ( + '/environments/1/resources/1/values?effective', + 'get --env 1 --resource 1 --key no --format yaml', + "'no': {}\n", + )) ] ] @@ -147,6 +152,56 @@ class TestSet(testscenarios.WithScenarios, _BaseCLITest): self.assertEqual(self.expected_body, req_history[-1].json()) +class TestDelete(testscenarios.WithScenarios, _BaseCLITest): + scenarios = [ + (s[0], + dict(zip(('args', 'expected_body'), s[1]))) + for s in [ + ('k1', ('-k k1', "ResourceValue for key k1 was deleted\n")), + ('xx', ('-k xx', "ResourceValue for key xx was deleted\n")), + ] + ] + + args = None + expected_body = None + url_last_part = 'values' + cmd = 'del' + + def test_delete(self): + url = self.BASE_URL + '/environments/1/lvl1/value1/resources/1/' + \ + self.url_last_part + '/keys/delete' + self.req_mock.patch(url) + args = [self.cmd] + ("--env 1 --level lvl1=value1 --resource 1 " + + self.args).split() + self.cli.run(args) + self.assertEqual(self.expected_body, self.cli.stdout.getvalue()) + + class TestOverride(TestSet): url_last_part = 'overrides' cmd = 'override' + + +class TestDeleteOverride(testscenarios.WithScenarios, _BaseCLITest): + scenarios = [ + (s[0], + dict(zip(('args', 'expected_body'), s[1]))) + for s in [ + ('k1', ('-k k1', "ResourceOverride for key k1 was deleted\n")), + ('xx', ('-k xx', "ResourceOverride for key xx was deleted\n")), + ] + ] + + args = None + expected_body = None + url_last_part = 'overrides' + cmd = 'rm override' + + def test_delete(self): + url = self.BASE_URL + '/environments/1/lvl1/value1/resources/1/' + \ + self.url_last_part + '/keys/delete' + self.req_mock.patch(url) + args = [self.cmd] + ("--env 1 --level lvl1=value1 --resource 1 " + + self.args).split() + self.cli.run(args) + self.assertEqual(self.expected_body, self.cli.stdout.getvalue())