44
55from typing import Any , Mapping , Optional , Sequence
66
7- from gvm .errors import RequiredArgument
7+ from gvm .errors import InvalidArgumentType , RequiredArgument
88from gvm .protocols .core import Request
99from gvm .protocols .gmp .requests ._entity_id import EntityID
1010from gvm .utils import to_bool
1111from gvm .xml import XmlCommand
1212
1313
1414class Agents :
15-
1615 @staticmethod
1716 def _add_element (element , name : str , value : Any ) -> None :
1817 """
@@ -24,8 +23,14 @@ def _add_element(element, name: str, value: Any) -> None:
2423 value: Value to set as the text of the sub-element. If None, the
2524 element will not be created.
2625 """
27- if value is not None :
28- element .add_element (name , str (value ))
26+ if value is None :
27+ return
28+ if isinstance (value , bool ):
29+ value = "1" if value else "0"
30+ else :
31+ value = str (value )
32+
33+ element .add_element (name , value )
2934
3035 @classmethod
3136 def _validate_agent_config (
@@ -67,7 +72,11 @@ def valid_value(d: Mapping[str, Any], key: str, path: str) -> Any:
6772 valid_value (se , "indexer_dir_depth" , "agent_script_executor." )
6873
6974 sched = se .get ("scheduler_cron_time" )
70- if isinstance (sched , Sequence ) and not isinstance (sched , (str , bytes )):
75+ if isinstance (sched , str ):
76+ items = [sched ]
77+ elif isinstance (sched , Sequence ) and not isinstance (
78+ sched , (str , bytes )
79+ ):
7180 items = [str (x ) for x in sched ]
7281 else :
7382 items = []
@@ -83,7 +92,58 @@ def valid_value(d: Mapping[str, Any], key: str, path: str) -> Any:
8392 valid_value (hb , "miss_until_inactive" , "heartbeat." )
8493
8594 @classmethod
86- def _append_agent_config (cls , parent , config : Mapping [str , Any ]) -> None :
95+ def _validate_config_defaults (
96+ cls , config_defaults : Mapping [str , Any ], * , caller : str
97+ ) -> None :
98+ """Ensure agent config defaults structure is valid."""
99+
100+ def valid_map (d : Any , key : str , path : str ) -> Mapping [str , Any ]:
101+ if not isinstance (d , Mapping ):
102+ raise RequiredArgument (
103+ function = caller ,
104+ argument = path .rstrip ("." ),
105+ )
106+ v = d .get (key )
107+ if not isinstance (v , Mapping ):
108+ raise RequiredArgument (
109+ function = caller ,
110+ argument = f"{ path } { key } " ,
111+ )
112+ return v
113+
114+ def valid_bool (d : Mapping [str , Any ], key : str , path : str ) -> bool :
115+ v = d .get (key )
116+ if not isinstance (v , bool ):
117+ raise InvalidArgumentType (
118+ function = caller ,
119+ argument = f"{ path } { key } " ,
120+ arg_type = "bool" ,
121+ )
122+ return v
123+
124+ agent_defaults = valid_map (
125+ config_defaults , "agent_defaults" , "config_defaults."
126+ )
127+ cls ._validate_agent_config (agent_defaults , caller = caller )
128+
129+ agent_control_defaults = valid_map (
130+ config_defaults ,
131+ "agent_control_defaults" ,
132+ "config_defaults." ,
133+ )
134+ valid_bool (
135+ agent_control_defaults ,
136+ "update_to_latest" ,
137+ "config_defaults.agent_control_defaults." ,
138+ )
139+
140+ @classmethod
141+ def _append_agent_config (
142+ cls ,
143+ parent ,
144+ config : Mapping [str , Any ],
145+ wrapper_tag : Optional [str ] = "config" ,
146+ ) -> None :
87147 """
88148 Append an agent configuration block to the given XML parent element.
89149
@@ -110,12 +170,18 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
110170 }
111171
112172 Args:
113- parent: The XML parent element to which the `<config>` element
173+ parent: The XML parent element to which the wrapper element
114174 should be appended.
115175 config: Mapping containing the agent configuration fields to
116176 serialize.
177+ wrapper_tag: Optional wrapper element name. If None, fields are
178+ appended directly to parent.
117179 """
118- xml_config = parent .add_element ("config" )
180+ xml_config = (
181+ parent .add_element (wrapper_tag )
182+ if wrapper_tag is not None
183+ else parent
184+ )
119185
120186 # agent_control.retry
121187 ac = config ["agent_control" ]
@@ -145,9 +211,18 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
145211 xml_se , "indexer_dir_depth" , se .get ("indexer_dir_depth" )
146212 )
147213 sched = se .get ("scheduler_cron_time" )
148- xml_sched = xml_se .add_element ("scheduler_cron_time" )
149- for item in sched :
150- xml_sched .add_element ("item" , str (item ))
214+ if isinstance (sched , str ):
215+ sched_items = [sched ]
216+ else :
217+ sched_items = list (sched or [])
218+
219+ if sched_items :
220+ xml_sched = xml_se .add_element (
221+ "scheduler_cron_time" ,
222+ attrs = {"is_list" : "1" },
223+ )
224+ for item in sched_items :
225+ xml_sched .add_element ("item" , str (item ))
151226
152227 # heartbeat
153228 hb = config ["heartbeat" ]
@@ -159,6 +234,29 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
159234 xml_hb , "miss_until_inactive" , hb .get ("miss_until_inactive" )
160235 )
161236
237+ @classmethod
238+ def _append_config_defaults (
239+ cls , parent , config_defaults : Mapping [str , Any ]
240+ ) -> None :
241+ xml_defaults = parent .add_element ("config_defaults" )
242+
243+ cls ._append_agent_config (
244+ xml_defaults ,
245+ config_defaults ["agent_defaults" ],
246+ wrapper_tag = "agent_defaults" ,
247+ )
248+
249+ control_defaults = config_defaults .get ("agent_control_defaults" )
250+ if control_defaults :
251+ xml_control_defaults = xml_defaults .add_element (
252+ "agent_control_defaults"
253+ )
254+ cls ._add_element (
255+ xml_control_defaults ,
256+ "update_to_latest" ,
257+ control_defaults .get ("update_to_latest" ),
258+ )
259+
162260 @classmethod
163261 def get_agents (
164262 cls ,
@@ -273,54 +371,57 @@ def delete_agents(cls, agent_ids: list[EntityID]) -> Request:
273371 def modify_agent_control_scan_config (
274372 cls ,
275373 agent_control_id : EntityID ,
276- config : Mapping [str , Any ],
374+ config_defaults : Mapping [str , Any ],
277375 ) -> Request :
278376 """
279377 Modify agent control scan config.
280378
281379 Args:
282380 agent_control_id: The agent control UUID.
283- config : Nested config, e.g.:
381+ config_defaults : Nested config, e.g.:
284382 {
285- "agent_control": {
286- "retry": {
287- "attempts": 6,
288- "delay_in_seconds": 60,
289- "max_jitter_in_seconds": 10,
290- }
291- },
292- "agent_script_executor": {
293- "bulk_size": 2,
294- "bulk_throttle_time_in_ms": 300,
295- "indexer_dir_depth": 100,
296- "scheduler_cron_time": ["0 */12 * * *"], # str or list[str]
297- },
298- "heartbeat": {
299- "interval_in_seconds": 300,
300- "miss_until_inactive": 1,
301- },
383+ "agent_defaults": {
384+ "agent_control": {
385+ "retry": {
386+ "attempts": 6,
387+ "delay_in_seconds": 60,
388+ "max_jitter_in_seconds": 10,
389+ }
390+ },
391+ "agent_script_executor": {
392+ "bulk_size": 2,
393+ "bulk_throttle_time_in_ms": 300,
394+ "indexer_dir_depth": 100,
395+ "scheduler_cron_time": ["0 */12 * * *"],
396+ },
397+ "heartbeat": {
398+ "interval_in_seconds": 300,
399+ "miss_until_inactive": 1,
400+ },
401+ },
402+ "agent_control_defaults": {
403+ "update_to_latest": False,
404+ },
302405 }
303406 """
304407 if not agent_control_id :
305408 raise RequiredArgument (
306409 function = cls .modify_agent_control_scan_config .__name__ ,
307410 argument = "agent_control_id" ,
308411 )
309- if not config :
412+ if not config_defaults :
310413 raise RequiredArgument (
311414 function = cls .modify_agent_control_scan_config .__name__ ,
312- argument = "config " ,
415+ argument = "config_defaults " ,
313416 )
314417
315- cls ._validate_agent_config (
316- config , caller = cls .modify_agent_control_scan_config .__name__
418+ cls ._validate_config_defaults (
419+ config_defaults ,
420+ caller = cls .modify_agent_control_scan_config .__name__ ,
317421 )
318422
319- cmd = XmlCommand (
320- "modify_agent_control_scan_config" ,
321- )
423+ cmd = XmlCommand ("modify_agent_control_scan_config" )
322424 cmd .set_attribute ("agent_control_id" , str (agent_control_id ))
323-
324- cls ._append_agent_config (cmd , config )
425+ cls ._append_config_defaults (cmd , config_defaults )
325426
326427 return cmd
0 commit comments