Compare commits
28 Commits
889cf660f6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fbe592421 | |||
| 6d36b4e958 | |||
| 90c6c80843 | |||
| c67343b7cf | |||
| 2bc7601177 | |||
| 2a74bbc436 | |||
| 983414f278 | |||
| 046810c1f6 | |||
| dcf257d887 | |||
| 5085d96628 | |||
| c358814fe7 | |||
| 815db080f5 | |||
| 6b76270cc6 | |||
| ff461464fd | |||
| 88407dd1b4 | |||
| 8eed7ed9fb | |||
| f34c87f0c8 | |||
| 0444e120fa | |||
| 585c06385c | |||
| 725e6921f4 | |||
| e973ccb673 | |||
| 62157e5dee | |||
| 91855fce4f | |||
| 6f51520fb5 | |||
| 80de5399a6 | |||
| fb560cc937 | |||
| 3bfcebdf21 | |||
| ae1effafcd |
@@ -141,6 +141,18 @@ issue_type_name:
|
|||||||
Riposte refuses an `options_from` command that is not `risk: read`: opening a
|
Riposte refuses an `options_from` command that is not `risk: read`: opening a
|
||||||
field must never change anything in the customer's tool.
|
field must never change anything in the customer's tool.
|
||||||
|
|
||||||
|
### Multi-line inputs
|
||||||
|
|
||||||
|
`format: multiline` (or `format: markdown`, which says what the text is) makes
|
||||||
|
the playbook editor show a resizable text area instead of a one-line field
|
||||||
|
(Riposte 0.322+). Use it for a mail body, a comment, a markdown description:
|
||||||
|
a one-line field turns the line breaks of a pasted text into spaces. Older
|
||||||
|
Riposte versions ignore `format` and keep the one-line field.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
description: { type: string, format: markdown, description: "Body in markdown" }
|
||||||
|
```
|
||||||
|
|
||||||
### `risk:` — the agent approval gate
|
### `risk:` — the agent approval gate
|
||||||
|
|
||||||
Every command declares a tier. It is what Riposte's agent approval gate reads:
|
Every command declares a tier. It is what Riposte's agent approval gate reads:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -135,14 +135,57 @@ def count(filters):
|
|||||||
return int(reply.get("total_count") or 0)
|
return int(reply.get("total_count") or 0)
|
||||||
|
|
||||||
|
|
||||||
|
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 keep(e))
|
||||||
|
start += len(page)
|
||||||
|
if not page or start >= int(reply.get("total_count") or 0):
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
filters = build_filters(inputs)
|
filters = build_filters(inputs)
|
||||||
result = {"count": count(filters), "filters": filters}
|
result = {"filters": filters}
|
||||||
|
tests = []
|
||||||
|
wanted = {x.upper() for x in csv(inputs.get("operational_status"))}
|
||||||
|
if 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)
|
||||||
# The fleet total is the denominator the filtered count is read against: it
|
# 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
|
# 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.
|
# whole estate — because it was too broad — visible instead of plausible.
|
||||||
result["total"] = count([]) if filters else result["count"]
|
result["total"] = count([]) if filters or tests else result["count"]
|
||||||
print(json.dumps(result))
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
name: "CrowdStrike NG-SIEM Detection"
|
||||||
|
color: "#c1121f"
|
||||||
|
icon: "shield"
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
|||||||
|
name: "CrowdStrike Next-Gen SIEM Detections → OCSF"
|
||||||
|
description: "Map of a Falcon Next-Gen SIEM detection (alerts/entities/alerts/v2 on products 'thirdparty' and 'ngsiem', results_path = resources) to OCSF. Read from real FortiGate IPS detections: a third-party detection carries no sensor process or file block, but names its source product and vendor, the source and target endpoints with ports and protocol, the host Falcon matched it to (enriched_entities) and the vendor's own signature id. It has no description: the incident's is built from the source product, the signature and the connection."
|
||||||
|
field_mappings:
|
||||||
|
title: "name"
|
||||||
|
# Plain path: the ingestion engine reads informational/low/medium/high/critical natively.
|
||||||
|
severity: "severity_name"
|
||||||
|
# Third-party detections carry no description of their own; say what fired,
|
||||||
|
# from which product, on which connection.
|
||||||
|
description: "jsonata: description ? description : $join(source_products, ', ') & ' — ' & name & (source_endpoint_address_ip4 ? ' — ' & source_endpoint_address_ip4 & (local_port ? ':' & local_port : '') & ' → ' & target_endpoint_address_ip4 & (remote_port ? ':' & remote_port : '') & (protocol ? ' (' & protocol & ')' : '') : '')"
|
||||||
|
# results_path = resources; source_path is JSONata over ONE alert object.
|
||||||
|
# Paths absent from a given alert are skipped at ingestion, so extra entries are safe.
|
||||||
|
ocsf:
|
||||||
|
# ── Finding / analytic ────────────────────────────────────────────
|
||||||
|
- { source_path: "composite_id", ocsf_field: "finding_info.uid" }
|
||||||
|
- { source_path: "name", ocsf_field: "finding_info.title" }
|
||||||
|
- { source_path: "description", ocsf_field: "finding_info.desc" }
|
||||||
|
- { source_path: "categorization", ocsf_field: "finding_info.types" }
|
||||||
|
- { source_path: "data_domains", ocsf_field: "finding_info.data_sources" }
|
||||||
|
- { source_path: "falcon_host_link", ocsf_field: "finding_info.src_url" }
|
||||||
|
- { source_path: "created_timestamp", ocsf_field: "finding_info.created_time" }
|
||||||
|
- { source_path: "updated_timestamp", ocsf_field: "finding_info.modified_time" }
|
||||||
|
# `time` is when the source product saw the event (timestamp), ahead of
|
||||||
|
# Falcon's own record of the detection (created_timestamp, ~20 s later).
|
||||||
|
- { source_path: "timestamp", ocsf_field: "time" }
|
||||||
|
# The vendor's signature id (FortiGate IPS 100012) identifies the rule;
|
||||||
|
# pattern_id is Falcon's generic third-party bucket (300000 for all of them).
|
||||||
|
- { source_path: "name", ocsf_field: "finding_info.analytic.name" }
|
||||||
|
- { source_path: "vendor_pattern_id", ocsf_field: "finding_info.analytic.uid" }
|
||||||
|
- { source_path: "confidence", ocsf_field: "confidence" }
|
||||||
|
- { source_path: "priority_value", ocsf_field: "risk_score" }
|
||||||
|
- { source_path: "status", ocsf_field: "status" }
|
||||||
|
# ── Source product — the log the detection came from ──────────────
|
||||||
|
- { source_path: "source_products[0]", ocsf_field: "metadata.product.name" }
|
||||||
|
- { source_path: "source_vendors[0]", ocsf_field: "metadata.product.vendor_name" }
|
||||||
|
- { source_path: "product", ocsf_field: "metadata.product.feature.name" }
|
||||||
|
- { source_path: "cid", ocsf_field: "cloud.account.uid" }
|
||||||
|
# ── Host Falcon matched the detection to ──────────────────────────
|
||||||
|
- { source_path: "agent_id", ocsf_field: "device.uid" }
|
||||||
|
- { source_path: "enriched_entities.hostnames[0]", ocsf_field: "device.hostname" }
|
||||||
|
# ── Connection ────────────────────────────────────────────────────
|
||||||
|
- { source_path: "source_endpoint_address_ip4 ? source_endpoint_address_ip4 : source_ips[0]", ocsf_field: "src_endpoint.ip" }
|
||||||
|
- { source_path: "source_hosts[0]", ocsf_field: "src_endpoint.hostname" }
|
||||||
|
- { source_path: "$number(local_port)", ocsf_field: "src_endpoint.port" }
|
||||||
|
- { source_path: "target_endpoint_address_ip4 ? target_endpoint_address_ip4 : destination_ips[0]", ocsf_field: "dst_endpoint.ip" }
|
||||||
|
- { source_path: "$number(remote_port)", ocsf_field: "dst_endpoint.port" }
|
||||||
|
- { source_path: "protocol", ocsf_field: "connection_info.protocol_name" }
|
||||||
|
- { source_path: "users[0]", ocsf_field: "actor.user.name" }
|
||||||
|
# ── MITRE ATT&CK (correlation rules that declare it) ──────────────
|
||||||
|
- { source_path: "tactic", ocsf_field: "attacks.tactic.name" }
|
||||||
|
- { source_path: "tactic_id", ocsf_field: "attacks.tactic.uid" }
|
||||||
|
- { source_path: "technique", ocsf_field: "attacks.technique.name" }
|
||||||
|
- { source_path: "technique_id", ocsf_field: "attacks.technique.uid" }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import datetime, json, os, re, sys, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
@@ -61,6 +61,44 @@ RANGE_FILTERS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# A bound on first_seen / last_seen can also be relative to now ("24h", "7d",
|
||||||
|
# "30m"), so a scheduled playbook counts the sensors seen in the last day without
|
||||||
|
# computing a date. It is turned into the UTC instant here.
|
||||||
|
RELATIVE = re.compile(r"^\s*(\d+)\s*([mhd])\s*$", re.I)
|
||||||
|
UNITS = {"m": "minutes", "h": "hours", "d": "days"}
|
||||||
|
|
||||||
|
|
||||||
|
def instant(v):
|
||||||
|
m = RELATIVE.match(str(v))
|
||||||
|
if not m:
|
||||||
|
return v
|
||||||
|
delta = datetime.timedelta(**{UNITS[m.group(2).lower()]: int(m.group(1))})
|
||||||
|
return (datetime.datetime.now(datetime.timezone.utc) - delta).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
# Sensor version bounds. FQL compares agent_version as text, where 7.9 sorts
|
||||||
|
# above 7.18, so these are compared here, number by number, over the devices the
|
||||||
|
# other filters keep.
|
||||||
|
VERSION_BOUNDS = {
|
||||||
|
"agent_version_lt": lambda v, t: v < t,
|
||||||
|
"agent_version_lte": lambda v, t: v <= t,
|
||||||
|
"agent_version_gt": lambda v, t: v > t,
|
||||||
|
"agent_version_gte": lambda v, t: v >= t,
|
||||||
|
}
|
||||||
|
SCROLL = 5000
|
||||||
|
DETAILS = 1000
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version(v):
|
||||||
|
parts = re.findall(r"\d+", str(v or ""))
|
||||||
|
return tuple(int(x) for x in parts) if parts else None
|
||||||
|
|
||||||
|
|
||||||
|
def pad(a, b):
|
||||||
|
n = max(len(a), len(b))
|
||||||
|
return a + (0,) * (n - len(a)), b + (0,) * (n - len(b))
|
||||||
|
|
||||||
|
|
||||||
def literal(v):
|
def literal(v):
|
||||||
# FQL quotes strings with single quotes, so a value carrying one would end
|
# FQL quotes strings with single quotes, so a value carrying one would end
|
||||||
# the literal early and change what the query means.
|
# the literal early and change what the query means.
|
||||||
@@ -82,7 +120,7 @@ def build_fql(inputs):
|
|||||||
for name, prop, operator in RANGE_FILTERS:
|
for name, prop, operator in RANGE_FILTERS:
|
||||||
value = inputs.get(name)
|
value = inputs.get(name)
|
||||||
if value:
|
if value:
|
||||||
clauses.append(prop + ":" + operator + literal(value))
|
clauses.append(prop + ":" + operator + literal(instant(value)))
|
||||||
# Escape hatch for any property this command does not name explicitly: raw
|
# Escape hatch for any property this command does not name explicitly: raw
|
||||||
# FQL, ANDed with the rest.
|
# FQL, ANDed with the rest.
|
||||||
raw = str(inputs.get("filter") or "").strip()
|
raw = str(inputs.get("filter") or "").strip()
|
||||||
@@ -110,9 +148,59 @@ def count(tok, fql):
|
|||||||
return int(total)
|
return int(total)
|
||||||
|
|
||||||
|
|
||||||
|
def version_bounds(inputs):
|
||||||
|
bounds = {}
|
||||||
|
for field in VERSION_BOUNDS:
|
||||||
|
value = str(inputs.get(field) or "").strip()
|
||||||
|
if value:
|
||||||
|
target = parse_version(value)
|
||||||
|
if target is None:
|
||||||
|
raise ValueError(field + " must be a version such as 7.18.18803, got: " + repr(value))
|
||||||
|
bounds[field] = target
|
||||||
|
return bounds
|
||||||
|
|
||||||
|
|
||||||
|
def count_by_version(tok, fql, bounds):
|
||||||
|
"""Every device ID the filter keeps, then their sensor versions, compared
|
||||||
|
here. A device with no readable version is left out and counted apart."""
|
||||||
|
ids, offset = [], None
|
||||||
|
while True:
|
||||||
|
params = [("limit", SCROLL)]
|
||||||
|
if fql:
|
||||||
|
params.insert(0, ("filter", fql))
|
||||||
|
if offset:
|
||||||
|
params.append(("offset", offset))
|
||||||
|
reply = call("GET", "/devices/queries/devices-scroll/v1", tok, params=params)
|
||||||
|
page = reply.get("resources") or []
|
||||||
|
ids.extend(page)
|
||||||
|
pagination = (reply.get("meta") or {}).get("pagination") or {}
|
||||||
|
offset = pagination.get("offset")
|
||||||
|
if not page or not offset or len(ids) >= int(pagination.get("total") or 0):
|
||||||
|
break
|
||||||
|
matched, unreadable = 0, 0
|
||||||
|
for i in range(0, len(ids), DETAILS):
|
||||||
|
reply = call("POST", "/devices/entities/devices/v2", tok, body={"ids": ids[i:i + DETAILS]})
|
||||||
|
for device in reply.get("resources") or []:
|
||||||
|
version = parse_version(device.get("agent_version"))
|
||||||
|
if version is None:
|
||||||
|
unreadable += 1
|
||||||
|
elif all(VERSION_BOUNDS[f](*pad(version, t)) for f, t in bounds.items()):
|
||||||
|
matched += 1
|
||||||
|
return matched, len(ids), unreadable
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
tok = token()
|
tok = token()
|
||||||
fql = build_fql(INPUTS)
|
fql = build_fql(INPUTS)
|
||||||
|
bounds = version_bounds(INPUTS)
|
||||||
|
if bounds:
|
||||||
|
matched, scanned, unreadable = count_by_version(tok, fql, bounds)
|
||||||
|
result = {"count": matched, "filter": fql,
|
||||||
|
"agent_version_bounds": {f: INPUTS[f] for f in bounds},
|
||||||
|
"scanned": scanned, "unreadable_version": unreadable}
|
||||||
|
result["total"] = count(tok, "")
|
||||||
|
print(json.dumps(result))
|
||||||
|
return
|
||||||
result = {"count": count(tok, fql), "filter": fql}
|
result = {"count": count(tok, fql), "filter": fql}
|
||||||
# The estate total is the denominator the filtered count is read against: it
|
# The estate 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
|
# turns a bare number into a proportion, and makes a filter that matched the
|
||||||
|
|||||||
@@ -62,8 +62,23 @@ def main():
|
|||||||
action_params.append({"name": "append_comment", "value": str(INPUTS["comment"])})
|
action_params.append({"name": "append_comment", "value": str(INPUTS["comment"])})
|
||||||
if INPUTS.get("assign_to_uuid"):
|
if INPUTS.get("assign_to_uuid"):
|
||||||
action_params.append({"name": "assign_to_uuid", "value": str(INPUTS["assign_to_uuid"])})
|
action_params.append({"name": "assign_to_uuid", "value": str(INPUTS["assign_to_uuid"])})
|
||||||
|
if not action_params:
|
||||||
|
raise ValueError("nothing to change: give a status, a verdict, a comment or an assignee")
|
||||||
body = {"action_parameters": action_params, "composite_ids": ids}
|
body = {"action_parameters": action_params, "composite_ids": ids}
|
||||||
result = call("PATCH", "/alerts/entities/alerts/v3", tok, body=body)
|
result = call("PATCH", "/alerts/entities/alerts/v3", tok, body=body)
|
||||||
|
# Falcon answers 200 to an update it refused: the reasons sit in errors and
|
||||||
|
# meta.writes.resources_affected stays at 0. Passing that on as a success
|
||||||
|
# left an incident closed in Riposte and still open in the console.
|
||||||
|
affected = ((result.get("meta") or {}).get("writes") or {}).get("resources_affected")
|
||||||
|
if result.get("errors") or affected == 0:
|
||||||
|
print(json.dumps({
|
||||||
|
"error": "Falcon updated no alert" if affected == 0 else "Falcon refused part of the update",
|
||||||
|
"resources_affected": affected,
|
||||||
|
"errors": result.get("errors") or [],
|
||||||
|
# The PATCH takes composite ids (<cid>:<id>), not the alert's bare id.
|
||||||
|
"composite_ids": ids,
|
||||||
|
}))
|
||||||
|
sys.exit(1)
|
||||||
print(json.dumps(result))
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ def main():
|
|||||||
result = call("POST", "/alerts/entities/alerts/v2", tok, body={"composite_ids": ids})
|
result = call("POST", "/alerts/entities/alerts/v2", tok, body={"composite_ids": ids})
|
||||||
else:
|
else:
|
||||||
result = {"resources": []}
|
result = {"resources": []}
|
||||||
|
# An empty page says nothing on its own: keep what the query answered — the
|
||||||
|
# FQL actually sent, Falcon's total for it and any errors — so a run with no
|
||||||
|
# alerts reads as "matched nothing" rather than as a silent failure.
|
||||||
|
result["query"] = {
|
||||||
|
"filter": fql,
|
||||||
|
"total": ((found.get("meta") or {}).get("pagination") or {}).get("total"),
|
||||||
|
"errors": found.get("errors") or [],
|
||||||
|
}
|
||||||
print(json.dumps(result))
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def token():
|
||||||
|
data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode()
|
||||||
|
req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST")
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||||||
|
return json.loads(r.read()).get("access_token", "")
|
||||||
|
|
||||||
|
|
||||||
|
def call(method, path, tok, params=None, body=None):
|
||||||
|
url = BASE + path
|
||||||
|
if params:
|
||||||
|
# params is a list of (key, value) tuples to allow repeated keys (e.g. ids)
|
||||||
|
url += "?" + urllib.parse.urlencode(params)
|
||||||
|
data = json.dumps(body).encode() if body is not None else None
|
||||||
|
headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"}
|
||||||
|
if data is not None:
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
with urllib.request.urlopen(req, timeout=90) as r:
|
||||||
|
raw = r.read()
|
||||||
|
return json.loads(raw) if raw else {}
|
||||||
|
|
||||||
|
|
||||||
|
def csv(v):
|
||||||
|
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tok = token()
|
||||||
|
# Next-Gen SIEM files its detections in the same unified Alerts API as the
|
||||||
|
# EDR, under two products: 'thirdparty' for the alerts it raises from ingested
|
||||||
|
# third-party logs (a FortiGate IPS hit, say) and 'ngsiem' for those of the
|
||||||
|
# tenant's own correlation rules. The product clause is always kept, so a
|
||||||
|
# filter narrows these detections and never widens past them.
|
||||||
|
products = [p.strip().strip("'\"") for p in str(INPUTS.get("products") or "thirdparty,ngsiem").split(",") if p.strip()]
|
||||||
|
# ',' is OR in FQL; parenthesised so the group binds before the '+' ANDs.
|
||||||
|
fql = "(" + ",".join("product:'" + p.replace("'", "\\'") + "'" for p in products) + ")"
|
||||||
|
extra = str(INPUTS.get("filter") or "").strip()
|
||||||
|
if extra:
|
||||||
|
fql += "+(" + extra + ")"
|
||||||
|
# The alert rule's watermark lands in created_from, folded into the FQL as
|
||||||
|
# created_timestamp (see search_detections for why it is not the filter input).
|
||||||
|
since = str(INPUTS.get("created_from") or "").strip().strip("'\"")
|
||||||
|
if since:
|
||||||
|
fql += "+created_timestamp:>'" + since + "'"
|
||||||
|
limit = INPUTS.get("limit", 50)
|
||||||
|
params = [("filter", fql), ("limit", limit), ("sort", "created_timestamp.desc")]
|
||||||
|
found = call("GET", "/alerts/queries/alerts/v2", tok, params=params)
|
||||||
|
ids = found.get("resources", []) or []
|
||||||
|
if ids:
|
||||||
|
result = call("POST", "/alerts/entities/alerts/v2", tok, body={"composite_ids": ids})
|
||||||
|
else:
|
||||||
|
result = {"resources": []}
|
||||||
|
# An empty page says nothing on its own: keep what the query answered — the
|
||||||
|
# FQL actually sent, Falcon's total for it and any errors — so a run with no
|
||||||
|
# alerts reads as "matched nothing" rather than as a silent failure.
|
||||||
|
result["query"] = {
|
||||||
|
"filter": fql,
|
||||||
|
"total": ((found.get("meta") or {}).get("pagination") or {}).get("total"),
|
||||||
|
"errors": found.get("errors") or [],
|
||||||
|
}
|
||||||
|
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)
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
def csv(v):
|
def csv(v):
|
||||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def csv(v):
|
def csv(v):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
FIELD_TYPES = {
|
FIELD_TYPES = {
|
||||||
"name": 0, "path": 1, "username": 3, "commandline": 4,
|
"name": 0, "path": 1, "username": 3, "commandline": 4,
|
||||||
@@ -8,12 +8,31 @@ FIELD_TYPES = {
|
|||||||
OPERATORS = {"eq": 0, "not_eq": 1, "contains": 2, "not_contains": 3}
|
OPERATORS = {"eq": 0, "not_eq": 1, "contains": 2, "not_contains": 3}
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, re, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
@@ -51,6 +70,28 @@ RANGE_FILTERS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Version bounds. The API matches a version only exactly, and a range lookup it
|
||||||
|
# does not support would be dropped without a word — so these are compared here,
|
||||||
|
# number by number (4.10 is above 4.9, which a string comparison denies).
|
||||||
|
VERSION_BOUNDS = {
|
||||||
|
"version_lt": lambda v, t: v < t,
|
||||||
|
"version_lte": lambda v, t: v <= t,
|
||||||
|
"version_gt": lambda v, t: v > t,
|
||||||
|
"version_gte": lambda v, t: v >= t,
|
||||||
|
}
|
||||||
|
PAGE = 500
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version(v):
|
||||||
|
parts = re.findall(r"\d+", str(v or ""))
|
||||||
|
return tuple(int(x) for x in parts) if parts else None
|
||||||
|
|
||||||
|
|
||||||
|
def pad(a, b):
|
||||||
|
n = max(len(a), len(b))
|
||||||
|
return a + (0,) * (n - len(a)), b + (0,) * (n - len(b))
|
||||||
|
|
||||||
|
|
||||||
def to_bool(v):
|
def to_bool(v):
|
||||||
if isinstance(v, bool):
|
if isinstance(v, bool):
|
||||||
return v
|
return v
|
||||||
@@ -67,7 +108,13 @@ def main():
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
# === per-command logic ===
|
# === per-command logic ===
|
||||||
filters = {}
|
# An instance bound to one tenant of a shared, multi-tenant manager carries it
|
||||||
|
# in its config: it scopes the count, and the fleet total beside it, unless the
|
||||||
|
# block names a tenant itself.
|
||||||
|
scope = {}
|
||||||
|
if not str(inputs.get("tenant") or "").strip() and str(secrets.get("tenant") or "").strip():
|
||||||
|
scope["tenant"] = str(secrets["tenant"]).strip()
|
||||||
|
filters = dict(scope)
|
||||||
for field in EXACT_FILTERS:
|
for field in EXACT_FILTERS:
|
||||||
value = inputs.get(field)
|
value = inputs.get(field)
|
||||||
if value is None or value == "":
|
if value is None or value == "":
|
||||||
@@ -84,17 +131,48 @@ def main():
|
|||||||
if len(kv) == 2 and kv[0].strip():
|
if len(kv) == 2 and kv[0].strip():
|
||||||
filters[kv[0].strip()] = kv[1]
|
filters[kv[0].strip()] = kv[1]
|
||||||
|
|
||||||
|
bounds = {}
|
||||||
|
for field in VERSION_BOUNDS:
|
||||||
|
value = str(inputs.get(field) or "").strip()
|
||||||
|
if value:
|
||||||
|
target = parse_version(value)
|
||||||
|
if target is None:
|
||||||
|
raise ValueError(field + " must be a version such as 4.10.2, got: " + repr(value))
|
||||||
|
bounds[field] = target
|
||||||
|
|
||||||
# The agent list is a DRF paginated collection: `count` is the total number
|
# 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.
|
# of matches, independent of the page size. limit=1 keeps the payload tiny.
|
||||||
agents = base + "/api/data/endpoint/Agent/"
|
agents = base + "/api/data/endpoint/Agent/"
|
||||||
|
if bounds:
|
||||||
|
# A version bound reads every agent the other filters keep and counts
|
||||||
|
# here. An agent with no readable version is left out and counted apart,
|
||||||
|
# rather than guessed above or below the bound.
|
||||||
|
count, scanned, unreadable, offset = 0, 0, 0, 0
|
||||||
|
while True:
|
||||||
|
page = request("GET", agents + qs(dict(filters, limit=PAGE, offset=offset)), headers)
|
||||||
|
rows = page.get("results") or []
|
||||||
|
for agent in rows:
|
||||||
|
scanned += 1
|
||||||
|
version = parse_version(agent.get("version"))
|
||||||
|
if version is None:
|
||||||
|
unreadable += 1
|
||||||
|
continue
|
||||||
|
if all(VERSION_BOUNDS[f](*pad(version, t)) for f, t in bounds.items()):
|
||||||
|
count += 1
|
||||||
|
offset += len(rows)
|
||||||
|
if not rows or offset >= page.get("count", 0):
|
||||||
|
break
|
||||||
|
result = {"count": count, "filters": dict(filters, **{f: inputs[f] for f in bounds}),
|
||||||
|
"scanned": scanned, "unreadable_version": unreadable}
|
||||||
|
else:
|
||||||
matched = request("GET", agents + qs(dict(filters, limit=1, offset=0)), headers)
|
matched = request("GET", agents + qs(dict(filters, limit=1, offset=0)), headers)
|
||||||
result = {"count": matched.get("count", 0), "filters": filters}
|
result = {"count": matched.get("count", 0), "filters": filters}
|
||||||
|
|
||||||
# The API silently drops a query parameter it does not recognise as a
|
# 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.
|
# filter, which yields the unfiltered total dressed up as a filtered count.
|
||||||
# Returning the fleet total alongside makes that failure visible.
|
# Returning the fleet total alongside makes that failure visible.
|
||||||
if filters:
|
if filters != scope or bounds:
|
||||||
everything = request("GET", agents + qs({"limit": 1, "offset": 0}), headers)
|
everything = request("GET", agents + qs(dict(scope, limit=1, offset=0)), headers)
|
||||||
result["total"] = everything.get("count", 0)
|
result["total"] = everything.get("count", 0)
|
||||||
else:
|
else:
|
||||||
result["total"] = result["count"]
|
result["total"] = result["count"]
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
SEVERITIES = ["low", "medium", "high", "critical"]
|
SEVERITIES = ["low", "medium", "high", "critical"]
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers):
|
def request(method, url, headers):
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, headers=headers, method=method)
|
req = urllib.request.Request(url, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=90) as resp:
|
with urllib.request.urlopen(req, timeout=90) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
SEVERITIES = ["low", "medium", "high", "critical"]
|
SEVERITIES = ["low", "medium", "high", "critical"]
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers):
|
def request(method, url, headers):
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, headers=headers, method=method)
|
req = urllib.request.Request(url, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=90) as resp:
|
with urllib.request.urlopen(req, timeout=90) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
FIELD_TYPES = {
|
FIELD_TYPES = {
|
||||||
"name": 0, "path": 1, "username": 3, "commandline": 4,
|
"name": 0, "path": 1, "username": 3, "commandline": 4,
|
||||||
@@ -8,12 +8,31 @@ FIELD_TYPES = {
|
|||||||
OPERATORS = {"eq": 0, "not_eq": 1, "contains": 2, "not_contains": 3}
|
OPERATORS = {"eq": 0, "not_eq": 1, "contains": 2, "not_contains": 3}
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def retryable(method, status):
|
||||||
|
# HarfangLab answers a 500 now and then on a call that passes when sent again.
|
||||||
|
# A read is safe to resend; a write only on an answer that says the action was
|
||||||
|
# not run (rate limit, gateway down), so an isolation or a job is never sent twice.
|
||||||
|
if status in (429, 502, 503):
|
||||||
|
return True
|
||||||
|
return method == "GET" and status in (500, 504)
|
||||||
|
|
||||||
|
|
||||||
def request(method, url, headers, body=None):
|
def request(method, url, headers, body=None):
|
||||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||||
|
for attempt in range(RETRIES + 1):
|
||||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
raw = resp.read()
|
raw = resp.read()
|
||||||
return json.loads(raw) if raw else {}
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if attempt == RETRIES or not retryable(method, e.code):
|
||||||
|
raise
|
||||||
|
e.close()
|
||||||
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
|
||||||
def qs(params):
|
def qs(params):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user