From 92a585c1811e192a85bb3f8abe956c86143da182 Mon Sep 17 00:00:00 2001 From: ozgen Date: Fri, 6 Mar 2026 14:28:37 +0100 Subject: [PATCH 1/2] change: align modify_agent_control_scan_config with new XML shape wrapping values under serializing nested serializing encoding update_to_latest as 1 / 0 handling scheduler_cron_time as a list with is_list="1" --- gvm/protocols/gmp/_gmpnext.py | 2 +- gvm/protocols/gmp/requests/next/_agents.py | 179 ++++- ...est_modify_agent_controller_scan_config.py | 735 ++++++++++++------ .../entities/agents/test_modify_agents.py | 2 +- .../entities/agents/test_sync_agents.py | 0 5 files changed, 630 insertions(+), 288 deletions(-) create mode 100644 tests/protocols/gmpnext/entities/agents/test_sync_agents.py diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index 661ed2466..ebac5468a 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -166,7 +166,7 @@ def modify_agent_control_scan_config( return self._send_request_and_transform_response( Agents.modify_agent_control_scan_config( agent_control_id=agent_control_id, - config=config, + config_defaults=config, ) ) diff --git a/gvm/protocols/gmp/requests/next/_agents.py b/gvm/protocols/gmp/requests/next/_agents.py index a14e1b81d..c73064290 100644 --- a/gvm/protocols/gmp/requests/next/_agents.py +++ b/gvm/protocols/gmp/requests/next/_agents.py @@ -4,7 +4,7 @@ from typing import Any, Mapping, Optional, Sequence -from gvm.errors import RequiredArgument +from gvm.errors import InvalidArgumentType, RequiredArgument from gvm.protocols.core import Request from gvm.protocols.gmp.requests._entity_id import EntityID from gvm.utils import to_bool @@ -12,7 +12,6 @@ class Agents: - @staticmethod def _add_element(element, name: str, value: Any) -> None: """ @@ -24,8 +23,14 @@ def _add_element(element, name: str, value: Any) -> None: value: Value to set as the text of the sub-element. If None, the element will not be created. """ - if value is not None: - element.add_element(name, str(value)) + if value is None: + return + if isinstance(value, bool): + value = "1" if value else "0" + else: + value = str(value) + + element.add_element(name, value) @classmethod def _validate_agent_config( @@ -67,7 +72,11 @@ def valid_value(d: Mapping[str, Any], key: str, path: str) -> Any: valid_value(se, "indexer_dir_depth", "agent_script_executor.") sched = se.get("scheduler_cron_time") - if isinstance(sched, Sequence) and not isinstance(sched, (str, bytes)): + if isinstance(sched, str): + items = [sched] + elif isinstance(sched, Sequence) and not isinstance( + sched, (str, bytes) + ): items = [str(x) for x in sched] else: items = [] @@ -83,7 +92,58 @@ def valid_value(d: Mapping[str, Any], key: str, path: str) -> Any: valid_value(hb, "miss_until_inactive", "heartbeat.") @classmethod - def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None: + def _validate_config_defaults( + cls, config_defaults: Mapping[str, Any], *, caller: str + ) -> None: + """Ensure agent config defaults structure is valid.""" + + def valid_map(d: Any, key: str, path: str) -> Mapping[str, Any]: + if not isinstance(d, Mapping): + raise RequiredArgument( + function=caller, + argument=path.rstrip("."), + ) + v = d.get(key) + if not isinstance(v, Mapping): + raise RequiredArgument( + function=caller, + argument=f"{path}{key}", + ) + return v + + def valid_bool(d: Mapping[str, Any], key: str, path: str) -> bool: + v = d.get(key) + if not isinstance(v, bool): + raise InvalidArgumentType( + function=caller, + argument=f"{path}{key}", + arg_type="bool", + ) + return v + + agent_defaults = valid_map( + config_defaults, "agent_defaults", "config_defaults." + ) + cls._validate_agent_config(agent_defaults, caller=caller) + + agent_control_defaults = valid_map( + config_defaults, + "agent_control_defaults", + "config_defaults.", + ) + valid_bool( + agent_control_defaults, + "update_to_latest", + "config_defaults.agent_control_defaults.", + ) + + @classmethod + def _append_agent_config( + cls, + parent, + config: Mapping[str, Any], + wrapper_tag: Optional[str] = "config", + ) -> None: """ Append an agent configuration block to the given XML parent element. @@ -110,12 +170,18 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None: } Args: - parent: The XML parent element to which the `` element + parent: The XML parent element to which the wrapper element should be appended. config: Mapping containing the agent configuration fields to serialize. + wrapper_tag: Optional wrapper element name. If None, fields are + appended directly to parent. """ - xml_config = parent.add_element("config") + xml_config = ( + parent.add_element(wrapper_tag) + if wrapper_tag is not None + else parent + ) # agent_control.retry ac = config["agent_control"] @@ -145,9 +211,18 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None: xml_se, "indexer_dir_depth", se.get("indexer_dir_depth") ) sched = se.get("scheduler_cron_time") - xml_sched = xml_se.add_element("scheduler_cron_time") - for item in sched: - xml_sched.add_element("item", str(item)) + if isinstance(sched, str): + sched_items = [sched] + else: + sched_items = list(sched or []) + + if sched_items: + xml_sched = xml_se.add_element( + "scheduler_cron_time", + attrs={"is_list": "1"}, + ) + for item in sched_items: + xml_sched.add_element("item", str(item)) # heartbeat hb = config["heartbeat"] @@ -159,6 +234,29 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None: xml_hb, "miss_until_inactive", hb.get("miss_until_inactive") ) + @classmethod + def _append_config_defaults( + cls, parent, config_defaults: Mapping[str, Any] + ) -> None: + xml_defaults = parent.add_element("config_defaults") + + cls._append_agent_config( + xml_defaults, + config_defaults["agent_defaults"], + wrapper_tag="agent_defaults", + ) + + control_defaults = config_defaults.get("agent_control_defaults") + if control_defaults: + xml_control_defaults = xml_defaults.add_element( + "agent_control_defaults" + ) + cls._add_element( + xml_control_defaults, + "update_to_latest", + control_defaults.get("update_to_latest"), + ) + @classmethod def get_agents( cls, @@ -273,32 +371,37 @@ def delete_agents(cls, agent_ids: list[EntityID]) -> Request: def modify_agent_control_scan_config( cls, agent_control_id: EntityID, - config: Mapping[str, Any], + config_defaults: Mapping[str, Any], ) -> Request: """ Modify agent control scan config. Args: agent_control_id: The agent control UUID. - config: Nested config, e.g.: + config_defaults: Nested config, e.g.: { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], # str or list[str] - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": False, + }, } """ if not agent_control_id: @@ -306,21 +409,19 @@ def modify_agent_control_scan_config( function=cls.modify_agent_control_scan_config.__name__, argument="agent_control_id", ) - if not config: + if not config_defaults: raise RequiredArgument( function=cls.modify_agent_control_scan_config.__name__, - argument="config", + argument="config_defaults", ) - cls._validate_agent_config( - config, caller=cls.modify_agent_control_scan_config.__name__ + cls._validate_config_defaults( + config_defaults, + caller=cls.modify_agent_control_scan_config.__name__, ) - cmd = XmlCommand( - "modify_agent_control_scan_config", - ) + cmd = XmlCommand("modify_agent_control_scan_config") cmd.set_attribute("agent_control_id", str(agent_control_id)) - - cls._append_agent_config(cmd, config) + cls._append_config_defaults(cmd, config_defaults) return cmd diff --git a/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py b/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py index 22bc54c85..aaa19924a 100644 --- a/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py +++ b/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py @@ -7,31 +7,43 @@ class GmpModifyAgentControllerScanConfigTestMixin: def test_modify_agent_control_scan_config_full(self): - cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } + config = { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 1, + "bulk_throttle_time_in_ms": 100, + "indexer_dir_depth": 10, + "scheduler_cron_time": [ + "0 23 * * *", + "0 22 * * *", + ], + }, + "heartbeat": { + "interval_in_seconds": 600, + "miss_until_inactive": 1, + }, }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "agent_control_defaults": { + "update_to_latest": True, }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } self.gmp.modify_agent_control_scan_config( - "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", - config=cfg, + "3b4be213-281f-49ee-b457-5a5f34f71510", + config=config, ) self.connection.send.has_been_called_with( - b'' - b"" + b'' + b"" + b"" b"" b"" b"6" @@ -40,55 +52,35 @@ def test_modify_agent_control_scan_config_full(self): b"" b"" b"" - b"2" - b"300" - b"100" - b"" - b"0 */12 * * *" + b"1" + b"100" + b"10" + b'' + b"0 23 * * *" + b"0 22 * * *" b"" b"" b"" - b"300" + b"600" b"1" b"" - b"" + b"" + b"" + b"1" + b"" + b"" b"" ) def test_modify_agent_control_scan_config_with_missing_element_raises(self): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - # max_jitter_in_seconds is missing - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, - } - - with self.assertRaises(RequiredArgument): - self.gmp.modify_agent_control_scan_config( - "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", - config=cfg, - ) - - def test_modify_agent_control_scan_config_missing_id_raises(self): - with self.assertRaises(RequiredArgument): - self.gmp.modify_agent_control_scan_config( - "", # missing id - config={ + "config_defaults": { + "agent_defaults": { "agent_control": { "retry": { "attempts": 6, "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, + # max_jitter_in_seconds is missing } }, "agent_script_executor": { @@ -102,6 +94,48 @@ def test_modify_agent_control_scan_config_missing_id_raises(self): "miss_until_inactive": 1, }, }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } + } + + with self.assertRaises(RequiredArgument): + self.gmp.modify_agent_control_scan_config( + "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", + config=cfg, + ) + + def test_modify_agent_control_scan_config_missing_id_raises(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_agent_control_scan_config( + "", # missing id + config={ + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } + }, ) def test_modify_agent_control_scan_config_missing_config_raises(self): @@ -121,14 +155,24 @@ def test_modify_agent_control_scan_config_agent_control_not_mapping_raises( self, ): cfg = { - "agent_control": "oops-not-a-mapping", - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": "oops-not-a-mapping", + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -137,20 +181,30 @@ def test_modify_agent_control_scan_config_agent_control_not_mapping_raises( def test_modify_agent_control_scan_config_scheduler_empty_list_raises(self): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": [], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -161,20 +215,30 @@ def test_modify_agent_control_scan_config_scheduler_with_empty_item_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["", " "], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["", " "], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -185,14 +249,24 @@ def test_modify_agent_control_scan_config_missing_agent_control_raises( self, ): cfg = { - # "agent_control": missing - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + # "agent_control": missing + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -201,16 +275,26 @@ def test_modify_agent_control_scan_config_missing_agent_control_raises( def test_modify_agent_control_scan_config_missing_retry_block_raises(self): cfg = { - "agent_control": { - # "retry": {} - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + # "retry": {} + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -221,20 +305,30 @@ def test_modify_agent_control_scan_config_missing_retry_attempts_raises( self, ): cfg = { - "agent_control": { - "retry": { - # "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + # "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -243,20 +337,30 @@ def test_modify_agent_control_scan_config_missing_retry_attempts_raises( def test_modify_agent_control_scan_config_missing_retry_delay_raises(self): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - # "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + # "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -267,20 +371,30 @@ def test_modify_agent_control_scan_config_missing_retry_max_jitter_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - # "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + # "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -291,15 +405,25 @@ def test_modify_agent_control_scan_config_missing_agent_script_executor_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - # "agent_script_executor": missing - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + # "agent_script_executor": missing, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -308,20 +432,30 @@ def test_modify_agent_control_scan_config_missing_agent_script_executor_raises( def test_modify_agent_control_scan_config_missing_bulk_size_raises(self): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - # "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + # "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -332,20 +466,30 @@ def test_modify_agent_control_scan_config_missing_bulk_throttle_time_in_ms_raise self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - # "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + # "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -356,20 +500,30 @@ def test_modify_agent_control_scan_config_missing_indexer_dir_depth_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - # "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + # "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -380,20 +534,27 @@ def test_modify_agent_control_scan_config_missing_heartbeat_block_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - # "heartbeat": missing + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + # "heartbeat": missing, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -404,23 +565,30 @@ def test_modify_agent_control_scan_config_missing_heartbeat_interval_raises( self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - # "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + # "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -431,23 +599,96 @@ def test_modify_agent_control_scan_config_missing_heartbeat_miss_until_inactive_ self, ): cfg = { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - # "miss_until_inactive": 1, - }, + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + # "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, + } + } + with self.assertRaises(RequiredArgument): + self.gmp.modify_agent_control_scan_config( + "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", config=cfg + ) + + def test_modify_agent_control_scan_config_agent_control_defaults_raises( + self, + ): + cfg = { + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + # "agent_control_defaults": missing, + } + } + with self.assertRaises(RequiredArgument): + self.gmp.modify_agent_control_scan_config( + "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", config=cfg + ) + + def test_modify_agent_control_scan_config_missing_update_to_latest_raises( + self, + ): + cfg = { + "config_defaults": { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": ["0 */12 * * *"], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + # "update_to_latest": True, + }, + } } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( diff --git a/tests/protocols/gmpnext/entities/agents/test_modify_agents.py b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py index cb0213500..3fc5bc1cc 100644 --- a/tests/protocols/gmpnext/entities/agents/test_modify_agents.py +++ b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py @@ -82,7 +82,7 @@ def test_modify_agents_with_full_config_and_comment(self): b"2" b"300" b"100" - b"" + b'' b"0 */12 * * *" b"" b"" diff --git a/tests/protocols/gmpnext/entities/agents/test_sync_agents.py b/tests/protocols/gmpnext/entities/agents/test_sync_agents.py new file mode 100644 index 000000000..e69de29bb From f700154e1c552846a8235f4a87de03c250805680 Mon Sep 17 00:00:00 2001 From: ozgen Date: Fri, 6 Mar 2026 14:41:41 +0100 Subject: [PATCH 2/2] add: add sync_agents command --- gvm/protocols/gmp/_gmpnext.py | 4 ++++ gvm/protocols/gmp/requests/next/_agents.py | 5 +++++ tests/protocols/gmpnext/entities/agents/test_sync_agents.py | 5 +++++ tests/protocols/gmpnext/entities/test_agents.py | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index ebac5468a..8c9301183 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -303,6 +303,10 @@ def clone_agent_group( AgentGroups.clone_agent_group(agent_group_id) ) + def sync_agents(self) -> T: + """Trigger agents synchronization from all agent controllers.""" + return self._send_request_and_transform_response(Agents.sync_agents()) + def create_credential_store_credential( self, name: str, diff --git a/gvm/protocols/gmp/requests/next/_agents.py b/gvm/protocols/gmp/requests/next/_agents.py index c73064290..e501dd796 100644 --- a/gvm/protocols/gmp/requests/next/_agents.py +++ b/gvm/protocols/gmp/requests/next/_agents.py @@ -425,3 +425,8 @@ def modify_agent_control_scan_config( cls._append_config_defaults(cmd, config_defaults) return cmd + + @classmethod + def sync_agents(cls) -> Request: + """Trigger agents synchronization from all agent controllers.""" + return XmlCommand("sync_agents") diff --git a/tests/protocols/gmpnext/entities/agents/test_sync_agents.py b/tests/protocols/gmpnext/entities/agents/test_sync_agents.py index e69de29bb..704ada1b2 100644 --- a/tests/protocols/gmpnext/entities/agents/test_sync_agents.py +++ b/tests/protocols/gmpnext/entities/agents/test_sync_agents.py @@ -0,0 +1,5 @@ +class GmpSyncAgentsTestMixin: + def test_sync_agents(self): + self.gmp.sync_agents() + + self.connection.send.has_been_called_with(b"") diff --git a/tests/protocols/gmpnext/entities/test_agents.py b/tests/protocols/gmpnext/entities/test_agents.py index 6c5aaacc5..16d983476 100644 --- a/tests/protocols/gmpnext/entities/test_agents.py +++ b/tests/protocols/gmpnext/entities/test_agents.py @@ -16,6 +16,7 @@ from .agents.test_modify_agents import ( GmpModifyAgentsTestMixin, ) +from .agents.test_sync_agents import GmpSyncAgentsTestMixin class GMPGetAgentsTestCase(GmpGetAgentsTestMixin, GMPTestCase): @@ -34,3 +35,7 @@ class GMPModifyAgentControllerScanConfigTestCase( GmpModifyAgentControllerScanConfigTestMixin, GMPTestCase ): pass + + +class GMPSyncAgentsTestCase(GmpSyncAgentsTestMixin, GMPTestCase): + pass