diff --git a/kfk/commons.py b/kfk/commons.py index 2390722..123728b 100644 --- a/kfk/commons.py +++ b/kfk/commons.py @@ -21,9 +21,11 @@ def delete_last_applied_configuration(resource_dict): del resource_dict["metadata"]["annotations"]["kubectl.kubernetes.io/last-applied-configuration"] -def add_resource_kv_config(config, dict_part): +def add_resource_kv_config(config, dict_part, *converters): if type(config) is tuple: for config_str in config: + for converter in converters: + config_str = converter(config_str) config_arr = get_kv_config_arr(config_str) dict_part[config_arr[0]] = convert_string_to_type(config_arr[1]) else: @@ -36,9 +38,11 @@ def get_kv_config_arr(config_str): return config_str.split('=') -def delete_resource_config(config, dict_part): +def delete_resource_config(config, dict_part, *converters): if type(config) is tuple: for config_str in config: + for converter in converters: + config_str = converter(config_str) if config_str in dict_part: del dict_part[config_str] else: diff --git a/kfk/configs_command.py b/kfk/configs_command.py index e3c5165..8ab9abf 100644 --- a/kfk/configs_command.py +++ b/kfk/configs_command.py @@ -2,6 +2,7 @@ from kfk.command import kfk from kfk import topics_command +from kfk import users_command from kfk.commons import print_missing_options_for_command @@ -11,12 +12,16 @@ @click.option('--delete-config', help='Config keys to remove', multiple=True) @click.option('--add-config', help='Key Value pairs of configs to add.', multiple=True) @click.option('--entity-name', help='Name of entity', required=True) -@click.option('--entity-type', help='Type of entity (topics/users/clusters)', required=True) +@click.option('--entity-type', help='Type of entity (topics/users/clusters)', + type=click.Choice(['topics', 'users'], case_sensitive=True)) @kfk.command() def configs(entity_type, entity_name, add_config, delete_config, describe, cluster, namespace): """Add/Remove entity config for a topic, client, user or broker""" if len(add_config) > 0 or len(delete_config) > 0 or (describe is not None): if entity_type == "topics": topics_command.alter(entity_name, None, None, add_config, delete_config, cluster, namespace) + elif entity_type == "users": + users_command.alter(entity_name, None, None, False, False, tuple(), None, None, None, None, None, + add_config, delete_config, cluster, namespace) else: print_missing_options_for_command("configs") diff --git a/kfk/users_command.py b/kfk/users_command.py index 7907db9..b1cb615 100644 --- a/kfk/users_command.py +++ b/kfk/users_command.py @@ -9,6 +9,7 @@ print_resource_found_msg from kfk.config import * from kfk.kubectl_command_builder import Kubectl +from kfk.utils import snake_to_camel_case @click.option('-n', '--namespace', help='Namespace to use', required=True) @@ -137,11 +138,11 @@ def alter(user, authentication_type, authorization_type, add_acl, delete_acl, op if len(quota_tuple) > 0: if user_dict["spec"].get("quotas") is None: user_dict["spec"]["quotas"] = {} - add_resource_kv_config(quota_tuple, user_dict["spec"]["quotas"]) + add_resource_kv_config(quota_tuple, user_dict["spec"]["quotas"], snake_to_camel_case) if len(delete_quota_tuple) > 0: if user_dict["spec"].get("quotas") is not None: - delete_resource_config(delete_quota_tuple, user_dict["spec"]["quotas"]) + delete_resource_config(delete_quota_tuple, user_dict["spec"]["quotas"], snake_to_camel_case) user_yaml = yaml.dump(user_dict) user_temp_file = create_temp_file(user_yaml) diff --git a/kfk/utils.py b/kfk/utils.py index 933aebe..19f3bb6 100644 --- a/kfk/utils.py +++ b/kfk/utils.py @@ -30,3 +30,8 @@ def convert_string_to_boolean(str_val): return True else: return False + + +def snake_to_camel_case(snake_str): + components = snake_str.split('_') + return components[0] + ''.join(x.title() for x in components[1:]) diff --git a/tests/test_configs_command.py b/tests/test_configs_command.py index 7c0ee71..0414fdf 100644 --- a/tests/test_configs_command.py +++ b/tests/test_configs_command.py @@ -11,13 +11,19 @@ def setUp(self): self.cluster = "my-cluster" self.namespace = "kafka" self.topic = "my-topic" + self.user = "my-user" def test_no_option(self): result = self.runner.invoke(kfk, ['configs', '--entity-type', 'topics', '--entity-name', self.topic, '-c', - self.cluster, '-n', self.namespace]) + self.cluster, '-n', self.namespace]) assert result.exit_code == 0 assert "Missing options: kfk configs" in result.output + def test_wrong_entity_type(self): + result = self.runner.invoke(kfk, ['configs', '--entity-type', 'foos', '--entity-name', self.topic, '-c', + self.cluster, '-n', self.namespace]) + assert result.exit_code == 2 + @mock.patch('kfk.topics_command.create_temp_file') @mock.patch('kfk.commons.get_resource_yaml') @mock.patch('kfk.topics_command.resource_exists') @@ -60,3 +66,88 @@ def test_add_two_topic_configs(self, mock_os, mock_resource_exists, mock_get_res expected_topic_yaml = file.read() result_topic_yaml = mock_create_temp_file.call_args[0][0] assert expected_topic_yaml == result_topic_yaml + + @mock.patch('kfk.topics_command.create_temp_file') + @mock.patch('kfk.commons.get_resource_yaml') + @mock.patch('kfk.topics_command.resource_exists') + @mock.patch('kfk.topics_command.os') + def test_delete_one_topic_config(self, mock_os, mock_resource_exists, mock_get_resource_yaml, mock_create_temp_file): + mock_resource_exists.return_value = True + + with open(r'files/yaml/topic_with_two_configs.yaml') as file: + topic_yaml = file.read() + mock_get_resource_yaml.return_value = topic_yaml + + result = self.runner.invoke(kfk, + ['configs', '--delete-config', 'cleanup.policy', '--entity-type', 'topics', + '--entity-name', self.topic, '-c', self.cluster, '-n', self.namespace]) + assert result.exit_code == 0 + + with open(r'files/yaml/topic_with_one_config.yaml') as file: + expected_topic_yaml = file.read() + result_topic_yaml = mock_create_temp_file.call_args[0][0] + assert expected_topic_yaml == result_topic_yaml + + @mock.patch('kfk.users_command.create_temp_file') + @mock.patch('kfk.commons.get_resource_yaml') + @mock.patch('kfk.users_command.resource_exists') + @mock.patch('kfk.users_command.os') + def test_add_one_user_config(self, mock_os, mock_resource_exists, mock_get_resource_yaml, mock_create_temp_file): + mock_resource_exists.return_value = True + + with open(r'files/yaml/user_with_authentication.yaml') as file: + user_yaml = file.read() + mock_get_resource_yaml.return_value = user_yaml + + result = self.runner.invoke(kfk, + ['configs', '--add-config', 'request_percentage=55', '--entity-type', 'users', + '--entity-name', self.user, '-c', self.cluster, '-n', self.namespace]) + assert result.exit_code == 0 + + with open(r'files/yaml/user_with_one_quota.yaml') as file: + expected_user_yaml = file.read() + result_user_yaml = mock_create_temp_file.call_args[0][0] + assert expected_user_yaml == result_user_yaml + + @mock.patch('kfk.users_command.create_temp_file') + @mock.patch('kfk.commons.get_resource_yaml') + @mock.patch('kfk.users_command.resource_exists') + @mock.patch('kfk.users_command.os') + def test_add_two_user_configs(self, mock_os, mock_resource_exists, mock_get_resource_yaml, mock_create_temp_file): + mock_resource_exists.return_value = True + + with open(r'files/yaml/user_with_authentication.yaml') as file: + user_yaml = file.read() + mock_get_resource_yaml.return_value = user_yaml + + result = self.runner.invoke(kfk, + ['configs', '--add-config', 'request_percentage=55', '--add-config', + 'consumer_byte_rate=2097152', '--entity-type', 'users', + '--entity-name', self.user, '-c', self.cluster, '-n', self.namespace]) + assert result.exit_code == 0 + + with open(r'files/yaml/user_with_two_quotas.yaml') as file: + expected_user_yaml = file.read() + result_user_yaml = mock_create_temp_file.call_args[0][0] + assert expected_user_yaml == result_user_yaml + + @mock.patch('kfk.users_command.create_temp_file') + @mock.patch('kfk.commons.get_resource_yaml') + @mock.patch('kfk.users_command.resource_exists') + @mock.patch('kfk.users_command.os') + def test_delete_one_user_config(self, mock_os, mock_resource_exists, mock_get_resource_yaml, mock_create_temp_file): + mock_resource_exists.return_value = True + + with open(r'files/yaml/user_with_two_quotas.yaml') as file: + user_yaml = file.read() + mock_get_resource_yaml.return_value = user_yaml + + result = self.runner.invoke(kfk, + ['configs', '--delete-config', 'consumer_byte_rate', '--entity-type', 'users', + '--entity-name', self.user, '-c', self.cluster, '-n', self.namespace]) + assert result.exit_code == 0 + + with open(r'files/yaml/user_with_one_quota.yaml') as file: + expected_user_yaml = file.read() + result_user_yaml = mock_create_temp_file.call_args[0][0] + assert expected_user_yaml == result_user_yaml diff --git a/tests/test_users_command.py b/tests/test_users_command.py index 5359b3b..669fea4 100644 --- a/tests/test_users_command.py +++ b/tests/test_users_command.py @@ -241,7 +241,7 @@ def test_alter_user_for_quota(self, mock_os, mock_resource_exists, mock_get_reso result = self.runner.invoke(kfk, ['users', '--alter', '--user', self.user, '--authentication-type', - 'scram-sha-512', '--quota', 'requestPercentage=55', + 'scram-sha-512', '--quota', 'request_percentage=55', '-c', self.cluster, '-n', self.namespace]) assert result.exit_code == 0 @@ -263,8 +263,8 @@ def test_alter_user_for_two_quotas(self, mock_os, mock_resource_exists, mock_get result = self.runner.invoke(kfk, ['users', '--alter', '--user', self.user, '--authentication-type', - 'scram-sha-512', '--quota', 'requestPercentage=55', - '--quota', 'consumerByteRate=2097152', + 'scram-sha-512', '--quota', 'request_percentage=55', + '--quota', 'consumer_byte_rate=2097152', '-c', self.cluster, '-n', self.namespace]) assert result.exit_code == 0 @@ -286,7 +286,7 @@ def test_alter_user_with_two_quotas_delete_one_quota(self, mock_os, mock_resourc result = self.runner.invoke(kfk, ['users', '--alter', '--user', self.user, '--delete-quota', - 'consumerByteRate', + 'consumer_byte_rate', '-c', self.cluster, '-n', self.namespace]) assert result.exit_code == 0 diff --git a/tests/test_utils.py b/tests/test_utils.py index d296508..b7ea8c1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,4 @@ -from kfk.utils import convert_string_to_type +from kfk.utils import convert_string_to_type, snake_to_camel_case from unittest import TestCase @@ -20,3 +20,8 @@ def test_convert_string_to_boolean(self): def test_convert_string_to_str(self): val_str = "test" self.assertEqual(convert_string_to_type(val_str), "test") + + def test_snake_to_camel_case(self): + val_str = "this_is_the_test_string" + self.assertEqual(snake_to_camel_case(val_str), "thisIsTheTestString") +