Skip to content

Commit d9c207d

Browse files
committed
Add validation for agent config and raise descriptive errors
1 parent 5f6e87e commit d9c207d

File tree

3 files changed

+88
-78
lines changed

3 files changed

+88
-78
lines changed

gvm/protocols/gmp/requests/next/_agents.py

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,76 @@
77
from gvm.errors import RequiredArgument
88
from gvm.protocols.core import Request
99
from gvm.protocols.gmp.requests._entity_id import EntityID
10-
from gvm.utils import to_bool
10+
from gvm.utils import SupportsStr, to_bool
1111
from gvm.xml import XmlCommand
1212

1313

1414
class Agents:
1515

1616
@staticmethod
17-
def _add_el(parent, name: str, value) -> None:
17+
def _add_element(element, name: str, value: Optional[SupportsStr]) -> None:
1818
"""
1919
Helper to add a sub-element with a value if the value is not None.
2020
2121
Args:
22-
parent: The XML parent element to which the new element is added.
22+
element: The XML parent element to which the new element is added.
2323
name: Name of the sub-element to create.
2424
value: Value to set as the text of the sub-element. If None, the
2525
element will not be created.
2626
"""
2727
if value is not None:
28-
parent.add_element(name, str(value))
28+
element.add_element(name, str(value))
29+
30+
@classmethod
31+
def _validate_agent_config(
32+
cls, config: Mapping[str, Any], *, caller: str
33+
) -> None:
34+
"""Ensure all required fields exist and are non-empty."""
35+
36+
def valid(d: Mapping[str, Any], key: str, path: str):
37+
if (
38+
not isinstance(d, Mapping)
39+
or d.get(key) is None
40+
or d.get(key) == ""
41+
):
42+
raise RequiredArgument(
43+
function=caller, argument=f"config.{path}{key}"
44+
)
45+
46+
# agent_control.retry
47+
ac = config.get("agent_control")
48+
valid(config, "agent_control", "")
49+
retry = ac.get("retry") if isinstance(ac, Mapping) else None
50+
valid(ac, "retry", "agent_control.")
51+
valid(retry, "attempts", "agent_control.retry.")
52+
valid(retry, "delay_in_seconds", "agent_control.retry.")
53+
valid(retry, "max_jitter_in_seconds", "agent_control.retry.")
54+
55+
# agent_script_executor
56+
se = config.get("agent_script_executor")
57+
valid(config, "agent_script_executor", "")
58+
valid(se, "bulk_size", "agent_script_executor.")
59+
valid(se, "bulk_throttle_time_in_ms", "agent_script_executor.")
60+
valid(se, "indexer_dir_depth", "agent_script_executor.")
61+
62+
sched = (
63+
se.get("scheduler_cron_time") if isinstance(se, Mapping) else None
64+
)
65+
if isinstance(sched, (list, tuple, set)):
66+
items = list(sched)
67+
else:
68+
items = []
69+
if not items or any(str(x).strip() == "" for x in items):
70+
raise RequiredArgument(
71+
function=caller,
72+
argument="config.agent_script_executor.scheduler_cron_time",
73+
)
74+
75+
# heartbeat
76+
hb = config.get("heartbeat")
77+
valid(config, "heartbeat", "")
78+
valid(hb, "interval_in_seconds", "heartbeat.")
79+
valid(hb, "miss_until_inactive", "heartbeat.")
2980

3081
@classmethod
3182
def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
@@ -67,11 +118,11 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
67118
retry = ac["retry"]
68119
xml_ac = xml_config.add_element("agent_control")
69120
xml_retry = xml_ac.add_element("retry")
70-
cls._add_el(xml_retry, "attempts", retry.get("attempts"))
71-
cls._add_el(
121+
cls._add_element(xml_retry, "attempts", retry.get("attempts"))
122+
cls._add_element(
72123
xml_retry, "delay_in_seconds", retry.get("delay_in_seconds")
73124
)
74-
cls._add_el(
125+
cls._add_element(
75126
xml_retry,
76127
"max_jitter_in_seconds",
77128
retry.get("max_jitter_in_seconds"),
@@ -80,26 +131,27 @@ def _append_agent_config(cls, parent, config: Mapping[str, Any]) -> None:
80131
# agent_script_executor
81132
se = config["agent_script_executor"]
82133
xml_se = xml_config.add_element("agent_script_executor")
83-
cls._add_el(xml_se, "bulk_size", se.get("bulk_size"))
84-
cls._add_el(
134+
cls._add_element(xml_se, "bulk_size", se.get("bulk_size"))
135+
cls._add_element(
85136
xml_se,
86137
"bulk_throttle_time_in_ms",
87138
se.get("bulk_throttle_time_in_ms"),
88139
)
89-
cls._add_el(xml_se, "indexer_dir_depth", se.get("indexer_dir_depth"))
140+
cls._add_element(
141+
xml_se, "indexer_dir_depth", se.get("indexer_dir_depth")
142+
)
90143
sched = se.get("scheduler_cron_time")
91-
if sched:
92-
xml_sched = xml_se.add_element("scheduler_cron_time")
93-
for item in sched:
94-
xml_sched.add_element("item", str(item))
144+
xml_sched = xml_se.add_element("scheduler_cron_time")
145+
for item in sched:
146+
xml_sched.add_element("item", str(item))
95147

96148
# heartbeat
97149
hb = config["heartbeat"]
98150
xml_hb = xml_config.add_element("heartbeat")
99-
cls._add_el(
151+
cls._add_element(
100152
xml_hb, "interval_in_seconds", hb.get("interval_in_seconds")
101153
)
102-
cls._add_el(
154+
cls._add_element(
103155
xml_hb, "miss_until_inactive", hb.get("miss_until_inactive")
104156
)
105157

@@ -178,6 +230,9 @@ def modify_agents(
178230
cmd.add_element("authorized", to_bool(authorized))
179231

180232
if config is not None:
233+
cls._validate_agent_config(
234+
config, caller=cls.modify_agents.__name__
235+
)
181236
cls._append_agent_config(cmd, config)
182237

183238
if comment:
@@ -248,6 +303,10 @@ def modify_agent_control_scan_config(
248303
argument="config",
249304
)
250305

306+
cls._validate_agent_config(
307+
config, caller=cls.modify_agent_control_scan_config.__name__
308+
)
309+
251310
cmd = XmlCommand(
252311
"modify_agent_control_scan_config",
253312
)

tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_modify_agent_control_scan_config_full(self):
5555
b"</modify_agent_control_scan_config>"
5656
)
5757

58-
def test_modify_agent_control_scan_config_with_missing_element(self):
58+
def test_modify_agent_control_scan_config_with_missing_element_raises(self):
5959
cfg = {
6060
"agent_control": {
6161
"retry": {
@@ -73,35 +73,11 @@ def test_modify_agent_control_scan_config_with_missing_element(self):
7373
"heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1},
7474
}
7575

76-
self.gmp.modify_agent_control_scan_config(
77-
"fb6451bf-ec5a-45a8-8bab-5cf4b862e51b",
78-
config=cfg,
79-
)
80-
81-
self.connection.send.has_been_called_with(
82-
b'<modify_agent_control_scan_config agent_control_id="fb6451bf-ec5a-45a8-8bab-5cf4b862e51b">'
83-
b"<config>"
84-
b"<agent_control>"
85-
b"<retry>"
86-
b"<attempts>6</attempts>"
87-
b"<delay_in_seconds>60</delay_in_seconds>"
88-
b"</retry>"
89-
b"</agent_control>"
90-
b"<agent_script_executor>"
91-
b"<bulk_size>2</bulk_size>"
92-
b"<bulk_throttle_time_in_ms>300</bulk_throttle_time_in_ms>"
93-
b"<indexer_dir_depth>100</indexer_dir_depth>"
94-
b"<scheduler_cron_time>"
95-
b"<item>0 */12 * * *</item>"
96-
b"</scheduler_cron_time>"
97-
b"</agent_script_executor>"
98-
b"<heartbeat>"
99-
b"<interval_in_seconds>300</interval_in_seconds>"
100-
b"<miss_until_inactive>1</miss_until_inactive>"
101-
b"</heartbeat>"
102-
b"</config>"
103-
b"</modify_agent_control_scan_config>"
104-
)
76+
with self.assertRaises(RequiredArgument):
77+
self.gmp.modify_agent_control_scan_config(
78+
"fb6451bf-ec5a-45a8-8bab-5cf4b862e51b",
79+
config=cfg,
80+
)
10581

10682
def test_modify_agent_control_scan_config_missing_id_raises(self):
10783
with self.assertRaises(RequiredArgument):

tests/protocols/gmpnext/entities/agents/test_modify_agents.py

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -99,38 +99,13 @@ def test_modify_agents_with_full_config_with_missing_element(self):
9999
"heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1},
100100
}
101101

102-
self.gmp.modify_agents(
103-
agent_ids=["agent-123", "agent-456"],
104-
authorized=True,
105-
config=cfg,
106-
comment="Updated agents",
107-
)
108-
109-
self.connection.send.has_been_called_with(
110-
b"<modify_agents>"
111-
b'<agents><agent id="agent-123"/><agent id="agent-456"/></agents>'
112-
b"<authorized>1</authorized>"
113-
b"<config>"
114-
b"<agent_control>"
115-
b"<retry>"
116-
b"<attempts>6</attempts>"
117-
b"<delay_in_seconds>60</delay_in_seconds>"
118-
b"<max_jitter_in_seconds>10</max_jitter_in_seconds>"
119-
b"</retry>"
120-
b"</agent_control>"
121-
b"<agent_script_executor>"
122-
b"<bulk_size>2</bulk_size>"
123-
b"<bulk_throttle_time_in_ms>300</bulk_throttle_time_in_ms>"
124-
b"<indexer_dir_depth>100</indexer_dir_depth>"
125-
b"</agent_script_executor>"
126-
b"<heartbeat>"
127-
b"<interval_in_seconds>300</interval_in_seconds>"
128-
b"<miss_until_inactive>1</miss_until_inactive>"
129-
b"</heartbeat>"
130-
b"</config>"
131-
b"<comment>Updated agents</comment>"
132-
b"</modify_agents>"
133-
)
102+
with self.assertRaises(RequiredArgument):
103+
self.gmp.modify_agents(
104+
agent_ids=["agent-123", "agent-456"],
105+
authorized=True,
106+
config=cfg,
107+
comment="Updated agents",
108+
)
134109

135110
def test_modify_agents_without_ids_raises(self):
136111
with self.assertRaises(RequiredArgument):

0 commit comments

Comments
 (0)