feat(cortex-xdr): endpoint_count counts agents below a target build (1.10.0)
agent_version_lt, compared number by number in the script, as the endpoints API does not filter on the agent version either. Folded into the unpushed 1.10.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LaiNEwG2W5Y9fMo4qXcesr
This commit is contained in:
@@ -135,16 +135,28 @@ def count(filters):
|
||||
return int(reply.get("total_count") or 0)
|
||||
|
||||
|
||||
def count_operational(filters, wanted):
|
||||
# The endpoints grid does not filter on operational_status, so the endpoints
|
||||
# the other filters match are read 100 at a time — the API's page ceiling —
|
||||
# and counted here. Costs one call per hundred endpoints.
|
||||
def version(v):
|
||||
# "8.5.0.12345" -> (8, 5, 0, 12345); a shorter target such as "8.5" reads as
|
||||
# 8.5.0.0, so it compares against every build of that release line.
|
||||
parts = []
|
||||
for x in str(v or "").strip().split("."):
|
||||
digits = "".join(c for c in x if c.isdigit())
|
||||
parts.append(int(digits) if digits else 0)
|
||||
while len(parts) < 4:
|
||||
parts.append(0)
|
||||
return tuple(parts)
|
||||
|
||||
|
||||
def count_local(filters, keep):
|
||||
# The endpoints grid filters neither on operational_status nor on the agent
|
||||
# version, so the endpoints the other filters match are read 100 at a time —
|
||||
# the API's page ceiling — and counted here. One call per hundred endpoints.
|
||||
n, start = 0, 0
|
||||
while True:
|
||||
request_data = {"search_from": start, "search_to": start + 100, "filters": filters}
|
||||
reply = post("/endpoints/get_endpoint/", request_data).get("reply") or {}
|
||||
page = reply.get("endpoints") or []
|
||||
n += sum(1 for e in page if str(e.get("operational_status") or "").upper() in wanted)
|
||||
n += sum(1 for e in page if keep(e))
|
||||
start += len(page)
|
||||
if not page or start >= int(reply.get("total_count") or 0):
|
||||
return n
|
||||
@@ -153,16 +165,27 @@ def count_operational(filters, wanted):
|
||||
def main():
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
filters = build_filters(inputs)
|
||||
result = {"filters": filters}
|
||||
tests = []
|
||||
wanted = {x.upper() for x in csv(inputs.get("operational_status"))}
|
||||
if wanted:
|
||||
result = {"count": count_operational(filters, wanted), "filters": filters,
|
||||
"operational_status": sorted(wanted)}
|
||||
tests.append(lambda e: str(e.get("operational_status") or "").upper() in wanted)
|
||||
result["operational_status"] = sorted(wanted)
|
||||
below = str(inputs.get("agent_version_lt") or "").strip()
|
||||
if below:
|
||||
target = version(below)
|
||||
# An endpoint that reports no version is not counted as outdated: it is
|
||||
# unknown, and calling it outdated would inflate the figure.
|
||||
tests.append(lambda e: bool(e.get("agent_version")) and version(e.get("agent_version")) < target)
|
||||
result["agent_version_lt"] = below
|
||||
if tests:
|
||||
result["count"] = count_local(filters, lambda e: all(t(e) for t in tests))
|
||||
else:
|
||||
result = {"count": count(filters), "filters": filters}
|
||||
result["count"] = count(filters)
|
||||
# The fleet total is the denominator the filtered count is read against: it
|
||||
# turns a bare number into a proportion, and makes a filter that matched the
|
||||
# whole estate — because it was too broad — visible instead of plausible.
|
||||
result["total"] = count([]) if filters or wanted else result["count"]
|
||||
result["total"] = count([]) if filters or tests else result["count"]
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user