import json, os, sys, urllib.request, urllib.parse, urllib.error def request(method, url, headers, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None req = urllib.request.Request(url, data=data, headers=headers, method=method) with urllib.request.urlopen(req, timeout=60) as resp: raw = resp.read() return json.loads(raw) if raw else {} def qs(params): clean = {} for k, v in params.items(): if v is None or v == "" or v == []: continue if isinstance(v, bool): v = "true" if v else "false" clean[k] = v return ("?" + urllib.parse.urlencode(clean)) if clean else "" def csv(v): return [x.strip() for x in str(v or "").split(",") if x.strip()] # Agent fields HarfangLab exposes as exact-match query filters on # /api/data/endpoint/Agent/. Input name == API filter name. EXACT_FILTERS = [ "hostname", "domainname", "domain", "ipaddress", "external_ipaddress", "ostype", "osproducttype", "osversion", "producttype", "version", "status", "isolation_state", "tenant", ] # Convenience bounds mapped onto Django REST Framework range lookups. RANGE_FILTERS = { "lastseen_after": "lastseen__gte", "lastseen_before": "lastseen__lte", "firstseen_after": "firstseen__gte", "firstseen_before": "firstseen__lte", } def to_bool(v): if isinstance(v, bool): return v return str(v).strip().lower() in ("true", "1", "yes") def main(): secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) base = secrets.get("url", "").rstrip("/") headers = { "Authorization": "Token " + secrets.get("api_token", ""), "Accept": "application/json", "Content-Type": "application/json", } # === per-command logic === filters = {} for field in EXACT_FILTERS: value = inputs.get(field) if value is None or value == "": continue filters[field] = to_bool(value) if field == "isolation_state" else value for field, lookup in RANGE_FILTERS.items(): value = inputs.get(field) if value: filters[lookup] = value # Escape hatch for any filter this command does not name explicitly, # e.g. "groups__name=Serveurs&osmajor=10". for tok in str(inputs.get("filters") or "").split("&"): kv = tok.split("=", 1) if len(kv) == 2 and kv[0].strip(): filters[kv[0].strip()] = kv[1] # The agent list is a DRF paginated collection: `count` is the total number # of matches, independent of the page size. limit=1 keeps the payload tiny. agents = base + "/api/data/endpoint/Agent/" matched = request("GET", agents + qs(dict(filters, limit=1, offset=0)), headers) result = {"count": matched.get("count", 0), "filters": filters} # The API silently drops a query parameter it does not recognise as a # filter, which yields the unfiltered total dressed up as a filtered count. # Returning the fleet total alongside makes that failure visible. if filters: everything = request("GET", agents + qs({"limit": 1, "offset": 0}), headers) result["total"] = everything.get("count", 0) else: result["total"] = result["count"] print(json.dumps(result)) try: main() except urllib.error.HTTPError as e: print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) sys.exit(1) except Exception as e: print(json.dumps({"error": str(e)})) sys.exit(1)