389-ds-base/SOURCES/0009-Issue-5798-Fix-dsconf-config-multi-valued-attr-opera.patch
2025-02-05 09:44:11 +00:00

687 lines
26 KiB
Diff

From 569628ce405f214a7e35deb96b4e6c22519a4268 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Wed, 4 Dec 2024 22:29:11 -0500
Subject: [PATCH] Issue 5798 - Fix dsconf config multi-valued attr operations
(#6426)
Description:
Fix add operation to properly handle multi-valued attributes
so they persist after server restart.
Add support for multiple values at once via dsconf config.
Handle delete operation in a more flexible way.
Refactor attribute handling code for better error handling and consistency.
*Add CI test suite
Fixes: https://github.com/389ds/389-ds-base/issues/5798
Reviewed by: @progier389 (Thanks!)
---
.../tests/suites/clu/dsconf_config_test.py | 347 ++++++++++++++++++
src/lib389/lib389/_mapped_object.py | 55 +--
src/lib389/lib389/cli_conf/config.py | 172 +++++----
3 files changed, 481 insertions(+), 93 deletions(-)
create mode 100644 dirsrvtests/tests/suites/clu/dsconf_config_test.py
diff --git a/dirsrvtests/tests/suites/clu/dsconf_config_test.py b/dirsrvtests/tests/suites/clu/dsconf_config_test.py
new file mode 100644
index 000000000..d67679adf
--- /dev/null
+++ b/dirsrvtests/tests/suites/clu/dsconf_config_test.py
@@ -0,0 +1,347 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2024 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+import subprocess
+import logging
+import pytest
+from lib389._constants import DN_DM
+from lib389.topologies import topology_st
+
+pytestmark = pytest.mark.tier1
+
+log = logging.getLogger(__name__)
+
+HAPROXY_IPS = {
+ 'single': '192.168.1.1',
+ 'multiple': ['10.0.0.1', '172.16.0.1', '192.168.2.1']
+}
+
+REFERRALS = {
+ 'single': 'ldap://primary.example.com',
+ 'multiple': [
+ 'ldap://server1.example.com',
+ 'ldap://server2.example.com',
+ 'ldap://server3.example.com'
+ ]
+}
+
+TEST_ATTRS = [
+ ('nsslapd-haproxy-trusted-ip', HAPROXY_IPS),
+ ('nsslapd-referral', REFERRALS)
+]
+
+
+def execute_dsconf_command(dsconf_cmd, subcommands):
+ """Execute dsconf command and return output and return code"""
+
+ cmdline = dsconf_cmd + subcommands
+ proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
+ out, _ = proc.communicate()
+ return out.decode('utf-8'), proc.returncode
+
+
+def get_dsconf_base_cmd(topology):
+ """Return base dsconf command list"""
+
+ return ['/usr/sbin/dsconf', topology.standalone.serverid,
+ '-D', DN_DM, '-w', 'password']
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_add(topology_st, attr_name, values_dict):
+ """Test adding a single value to an attribute
+
+ :id: ffc912a6-c188-413d-9c35-7f4b3774d946
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add a single value to the specified attribute
+ 2. Verify the value was added correctly
+ :expectedresults:
+ 1. Command should execute successfully
+ 2. Added value should be present in the configuration
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ value = values_dict['single']
+ test_attr = f"{attr_name}={value}"
+
+ # Add single value
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add', test_attr])
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_replace(topology_st, attr_name, values_dict):
+ """Test replacing a single value in configuration attributes
+
+ :id: 112e3e5e-8db8-4974-9ea4-ed789c2d02f2
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add initial value to the specified attribute
+ 2. Replace the value with a new one
+ 3. Verify the replacement was successful
+ :expectedresults:
+ 1. Initial value should be added successfully
+ 2. Replace command should execute successfully
+ 3. New value should be present and old value should be absent
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add initial value
+ value = values_dict['single']
+ test_attr = f"{attr_name}={value}"
+ execute_dsconf_command(dsconf_cmd, ['config', 'add', test_attr])
+
+ # Replace with new value
+ new_value = values_dict['multiple'][0]
+ replace_attr = f"{attr_name}={new_value}"
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'replace', replace_attr])
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert new_value in output
+ assert value not in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_add(topology_st, attr_name, values_dict):
+ """Test adding multiple values in a single batch command
+
+ :id: 4c34c7f8-16cc-4ab6-938a-967537be5470
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute in a single command
+ 2. Verify all values were added correctly
+ :expectedresults:
+ 1. Batch add command should execute successfully
+ 2. All added values should be present in the configuration
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values in one command
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+ assert rc == 0
+
+ # Verify all values
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_replace(topology_st, attr_name, values_dict):
+ """Test replacing with multiple values in a single batch command
+
+ :id: 05cf28b8-000e-4856-a10b-7e1df012737d
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add initial single value
+ 2. Replace with multiple values in a single command
+ 3. Verify the replacement was successful
+ :expectedresults:
+ 1. Initial value should be added successfully
+ 2. Batch replace command should execute successfully
+ 3. All new values should be present and initial value should be absent
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add initial value
+ initial_value = values_dict['single']
+ execute_dsconf_command(dsconf_cmd, ['config', 'add', f"{attr_name}={initial_value}"])
+
+ # Replace with multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'replace'] + attr_values)
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert initial_value not in output
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_specific_delete(topology_st, attr_name, values_dict):
+ """Test deleting specific values from multi-valued attribute
+
+ :id: bb325c9a-eae8-438a-b577-bd63540b91cb
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute
+ 2. Delete a specific value
+ 3. Verify the deletion was successful
+ :expectedresults:
+ 1. Multiple values should be added successfully
+ 2. Specific delete command should execute successfully
+ 3. Deleted value should be absent while others remain present
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+
+ # Delete middle value
+ delete_value = values_dict['multiple'][1]
+ output, rc = execute_dsconf_command(dsconf_cmd,
+ ['config', 'delete', f"{attr_name}={delete_value}"])
+ assert rc == 0
+
+ # Verify remaining values
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert delete_value not in output
+ assert values_dict['multiple'][0] in output
+ assert values_dict['multiple'][2] in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_delete(topology_st, attr_name, values_dict):
+ """Test deleting multiple values in a single batch command
+
+ :id: 4b105824-b060-4f83-97d7-001a01dba1a5
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values to the attribute
+ 2. Delete multiple values in a single command
+ 3. Verify the batch deletion was successful
+ :expectedresults:
+ 1. Multiple values should be added successfully
+ 2. Batch delete command should execute successfully
+ 3. Deleted values should be absent while others remain present
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add all values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+
+ # Delete multiple values in one command
+ delete_values = [f"{attr_name}={val}" for val in values_dict['multiple'][:2]]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'delete'] + delete_values)
+ assert rc == 0
+
+ # Verify
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert values_dict['multiple'][2] in output
+ assert values_dict['multiple'][0] not in output
+ assert values_dict['multiple'][1] not in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_single_value_persists_after_restart(topology_st, attr_name, values_dict):
+ """Test single value persists after server restart
+
+ :id: be1a7e3d-a9ca-48a1-a3bc-062990d4f3e9
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add single value to the attribute
+ 2. Verify the value is present
+ 3. Restart the server
+ 4. Verify the value persists after restart
+ :expectedresults:
+ 1. Value should be added successfully
+ 2. Value should be present before restart
+ 3. Server should restart successfully
+ 4. Value should still be present after restart
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add single value
+ value = values_dict['single']
+ output, rc = execute_dsconf_command(dsconf_cmd,
+ ['config', 'add', f"{attr_name}={value}"])
+ assert rc == 0
+
+ # Verify before restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ # Restart the server
+ topology_st.standalone.restart(timeout=10)
+
+ # Verify after restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
+
+
+@pytest.mark.parametrize("attr_name,values_dict", TEST_ATTRS)
+def test_multi_value_batch_persists_after_restart(topology_st, attr_name, values_dict):
+ """Test multiple values added in batch persist after server restart
+
+ :id: fd0435e2-90b1-465a-8968-d3a375c8fb22
+ :setup: Standalone DS instance
+ :steps:
+ 1. Add multiple values in a single batch command
+ 2. Verify all values are present
+ 3. Restart the server
+ 4. Verify all values persist after restart
+ :expectedresults:
+ 1. Batch add command should execute successfully
+ 2. All values should be present before restart
+ 3. Server should restart successfully
+ 4. All values should still be present after restart
+ """
+ dsconf_cmd = get_dsconf_base_cmd(topology_st)
+
+ try:
+ # Add multiple values
+ attr_values = [f"{attr_name}={val}" for val in values_dict['multiple']]
+ output, rc = execute_dsconf_command(dsconf_cmd, ['config', 'add'] + attr_values)
+ assert rc == 0
+
+ # Verify before restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ # Restart the server
+ topology_st.standalone.restart(timeout=10)
+
+ # Verify after restart
+ output, _ = execute_dsconf_command(dsconf_cmd, ['config', 'get', attr_name])
+ for value in values_dict['multiple']:
+ assert value in output
+
+ finally:
+ execute_dsconf_command(dsconf_cmd, ['config', 'delete', attr_name])
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index 9799b8839..af5aab71e 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -321,25 +321,31 @@ class DSLdapObject(DSLogging, DSLint):
This is useful for configuration changes that require
atomic operation, and ease of use.
- An example of usage is add_many((key, value), (key, value))
+ An example of usage is add_many((key, value), (key, [value1, value2]))
No wrapping list is needed for the arguments.
- :param *args: tuples of key,value to replace.
- :type *args: (str, str)
+ :param *args: tuples of key,value to add. Value can be a single value
+ or a collection (list, tuple, set) of values.
+ :type *args: (str, str) or (str, list/tuple/set)
"""
-
mods = []
for arg in args:
- if isinstance(arg[1], list) or isinstance(arg[1], tuple):
- value = ensure_list_bytes(arg[1])
+ key, value = arg
+ if isinstance(value, (list, tuple, set)):
+ value = ensure_list_bytes(list(value))
else:
- value = [ensure_bytes(arg[1])]
- mods.append((ldap.MOD_ADD, ensure_str(arg[0]), value))
- return _modify_ext_s(self._instance,self._dn, mods, serverctrls=self._server_controls,
- clientctrls=self._client_controls, escapehatch='i am sure')
+ value = [ensure_bytes(value)]
+
+ mods.append((ldap.MOD_ADD, ensure_str(key), value))
+
+ return _modify_ext_s(self._instance,
+ self._dn,
+ mods,
+ serverctrls=self._server_controls,
+ clientctrls=self._client_controls,
+ escapehatch='i am sure')
- # Basically what it means;
def replace(self, key, value):
"""Replace an attribute with a value
@@ -355,23 +361,30 @@ class DSLdapObject(DSLogging, DSLint):
This is useful for configuration changes that require
atomic operation, and ease of use.
- An example of usage is replace_many((key, value), (key, value))
+ An example of usage is replace_many((key, value), (key, [value1, value2]))
No wrapping list is needed for the arguments.
- :param *args: tuples of key,value to replace.
- :type *args: (str, str)
+ :param *args: tuples of key,value to replace. Value can be a single value
+ or a collection (list, tuple, set) of values.
+ :type *args: (str, str) or (str, list/tuple/set)
"""
-
mods = []
for arg in args:
- if isinstance(arg[1], list) or isinstance(arg[1], tuple):
- value = ensure_list_bytes(arg[1])
+ key, value = arg
+ if isinstance(value, (list, tuple, set)):
+ value = ensure_list_bytes(list(value))
else:
- value = [ensure_bytes(arg[1])]
- mods.append((ldap.MOD_REPLACE, ensure_str(arg[0]), value))
- return _modify_ext_s(self._instance,self._dn, mods, serverctrls=self._server_controls,
- clientctrls=self._client_controls, escapehatch='i am sure')
+ value = [ensure_bytes(value)]
+
+ mods.append((ldap.MOD_REPLACE, ensure_str(key), value))
+
+ return _modify_ext_s(self._instance,
+ self._dn,
+ mods,
+ serverctrls=self._server_controls,
+ clientctrls=self._client_controls,
+ escapehatch='i am sure')
# This needs to work on key + val, and key
def remove(self, key, value):
diff --git a/src/lib389/lib389/cli_conf/config.py b/src/lib389/lib389/cli_conf/config.py
index fb4c68f8b..6f4eddc8c 100644
--- a/src/lib389/lib389/cli_conf/config.py
+++ b/src/lib389/lib389/cli_conf/config.py
@@ -1,5 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2023 Red Hat, Inc.
+# Copyright (C) 2024 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -12,12 +12,9 @@ from lib389.config import Config
from lib389.cli_base import (
_generic_get_entry,
_generic_get_attr,
- _generic_replace_attr,
CustomHelpFormatter
)
-OpType = Enum("OpType", "add delete")
-
def _config_display_ldapimaprootdn_warning(log, args):
"""If we update the rootdn we need to update the ldapi settings too"""
@@ -28,44 +25,48 @@ def _config_display_ldapimaprootdn_warning(log, args):
"For LDAPI configuration, \"nsslapd-rootdn\" is used instead.")
-def _config_get_existing_attrs(conf, args, op_type):
- """Get the existing attribute from the server and return them in a dict
- so we can add them back after the operation is done.
-
- For op_type == OpType.delete, we delete them from the server so we can add
- back only those that are not specified in the command line.
- (i.e delete nsslapd-haproxy-trusted-ip="192.168.0.1", but
- nsslapd-haproxy-trusted-ip has 192.168.0.1 and 192.168.0.2 values.
- So we want only 192.168.0.1 to be deleted in the end)
- """
-
- existing_attrs = {}
- if args and args.attr:
- for attr in args.attr:
- if "=" in attr:
- [attr_name, val] = attr.split("=", 1)
- # We should process only multi-valued attributes this way
- if attr_name.lower() == "nsslapd-haproxy-trusted-ip" or \
- attr_name.lower() == "nsslapd-referral":
- if attr_name not in existing_attrs.keys():
- existing_attrs[attr_name] = conf.get_attr_vals_utf8(attr_name)
- existing_attrs[attr_name] = [x for x in existing_attrs[attr_name] if x != val]
-
- if op_type == OpType.add:
- if existing_attrs[attr_name] == []:
- del existing_attrs[attr_name]
-
- if op_type == OpType.delete:
- conf.remove_all(attr_name)
- else:
- if op_type == OpType.delete:
- conf.remove_all(attr)
- else:
- raise ValueError(f"You must specify a value to add for the attribute ({attr_name})")
- return existing_attrs
- else:
- # Missing value
- raise ValueError(f"Missing attribute to {op_type.name}")
+def _format_values_for_display(values):
+ """Format a set of values for display"""
+
+ if not values:
+ return ""
+ if len(values) == 1:
+ return f"'{next(iter(values))}'"
+ return ', '.join(f"'{value}'" for value in sorted(values))
+
+
+def _parse_attr_value_pairs(attrs, allow_no_value=False):
+ """Parse attribute value pairs from a list of strings in the format 'attr=value'"""
+
+ attr_values = {}
+ attrs_without_values = set()
+
+ for attr in attrs:
+ if "=" in attr:
+ attr_name, val = attr.split("=", 1)
+ if attr_name not in attr_values:
+ attr_values[attr_name] = set()
+ attr_values[attr_name].add(val)
+ elif allow_no_value:
+ attrs_without_values.add(attr)
+ else:
+ raise ValueError(f"Invalid attribute format: {attr}. Must be in format 'attr=value'")
+
+ return attr_values, attrs_without_values
+
+
+def _handle_attribute_operation(conf, operation_type, attr_values, log):
+ """Handle attribute operations (add, replace) with consistent error handling"""
+
+ if operation_type == "add":
+ conf.add_many(*[(k, v) for k, v in attr_values.items()])
+ elif operation_type == "replace":
+ conf.replace_many(*[(k, v) for k, v in attr_values.items()])
+
+ for attr_name, values in attr_values.items():
+ formatted_values = _format_values_for_display(values)
+ operation_past = "added" if operation_type == "add" else f"{operation_type}d"
+ log.info(f"Successfully {operation_past} value(s) for '{attr_name}': {formatted_values}")
def config_get(inst, basedn, log, args):
@@ -77,49 +78,76 @@ def config_get(inst, basedn, log, args):
def config_replace_attr(inst, basedn, log, args):
- _generic_replace_attr(inst, basedn, log.getChild('config_replace_attr'), Config, args)
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to replace")
+ conf = Config(inst, basedn)
+ attr_values, _ = _parse_attr_value_pairs(args.attr)
+ _handle_attribute_operation(conf, "replace", attr_values, log)
_config_display_ldapimaprootdn_warning(log, args)
def config_add_attr(inst, basedn, log, args):
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to add")
+
conf = Config(inst, basedn)
- final_mods = []
-
- existing_attrs = _config_get_existing_attrs(conf, args, OpType.add)
-
- if args and args.attr:
- for attr in args.attr:
- if "=" in attr:
- [attr_name, val] = attr.split("=", 1)
- if attr_name in existing_attrs:
- for v in existing_attrs[attr_name]:
- final_mods.append((attr_name, v))
- final_mods.append((attr_name, val))
- try:
- conf.add_many(*set(final_mods))
- except ldap.TYPE_OR_VALUE_EXISTS:
- pass
- else:
- raise ValueError(f"You must specify a value to add for the attribute ({attr_name})")
- else:
- # Missing value
- raise ValueError("Missing attribute to add")
+ attr_values, _ = _parse_attr_value_pairs(args.attr)
+ # Validate no values already exist
+ for attr_name, values in attr_values.items():
+ existing_vals = set(conf.get_attr_vals_utf8(attr_name) or [])
+ duplicate_vals = values & existing_vals
+ if duplicate_vals:
+ raise ldap.ALREADY_EXISTS(
+ f"Values {duplicate_vals} already exist for attribute '{attr_name}'")
+
+ _handle_attribute_operation(conf, "add", attr_values, log)
_config_display_ldapimaprootdn_warning(log, args)
def config_del_attr(inst, basedn, log, args):
- conf = Config(inst, basedn)
- final_mods = []
+ if not args or not args.attr:
+ raise ValueError("Missing attribute to delete")
- existing_attrs = _config_get_existing_attrs(conf, args, OpType.delete)
+ conf = Config(inst, basedn)
+ attr_values, attrs_to_remove = _parse_attr_value_pairs(args.attr, allow_no_value=True)
+
+ # Handle complete attribute removal
+ for attr in attrs_to_remove:
+ try:
+ if conf.get_attr_vals_utf8(attr):
+ conf.remove_all(attr)
+ log.info(f"Removed attribute '{attr}' completely")
+ else:
+ log.warning(f"Attribute '{attr}' does not exist - skipping")
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.warning(f"Attribute '{attr}' does not exist - skipping")
+
+ # Validate and handle value-specific deletion
+ if attr_values:
+ for attr_name, values in attr_values.items():
+ try:
+ existing_values = set(conf.get_attr_vals_utf8(attr_name) or [])
+ values_to_delete = values & existing_values
+ values_not_found = values - existing_values
+
+ if values_not_found:
+ formatted_values = _format_values_for_display(values_not_found)
+ log.warning(f"Values {formatted_values} do not exist for attribute '{attr_name}' - skipping")
+
+ if values_to_delete:
+ remaining_values = existing_values - values_to_delete
+ if not remaining_values:
+ conf.remove_all(attr_name)
+ else:
+ conf.replace_many((attr_name, remaining_values))
+ formatted_values = _format_values_for_display(values_to_delete)
+ log.info(f"Successfully deleted values {formatted_values} from '{attr_name}'")
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.warning(f"Attribute '{attr_name}' does not exist - skipping")
- # Then add the attributes back all except the one we need to remove
- for attr_name in existing_attrs.keys():
- for val in existing_attrs[attr_name]:
- final_mods.append((attr_name, val))
- conf.add_many(*set(final_mods))
+ _config_display_ldapimaprootdn_warning(log, args)
def create_parser(subparsers):
--
2.48.0