Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(agent): Propagate non-job request exceptions to foreground #2382

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
112 changes: 49 additions & 63 deletions press/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import frappe
import requests
from frappe.utils.password import get_decrypted_password
from requests.exceptions import HTTPError

from press.utils import get_mariadb_root_password, log_error, sanitize_config

Expand Down Expand Up @@ -744,69 +745,54 @@
def post(self, path, data=None, raises=True):
return self.request("POST", path, data, raises=raises)

def _make_req(self, method, path, data, files, agent_job_id):
password = get_decrypted_password(self.server_type, self.server, "agent_password")
headers = {"Authorization": f"bearer {password}", "X-Agent-Job-Id": agent_job_id}
url = f"https://{self.server}:{self.port}/agent/{path}"
intermediate_ca = frappe.db.get_value("Press Settings", "Press Settings", "backbone_intermediate_ca")
if frappe.conf.developer_mode and intermediate_ca:
root_ca = frappe.db.get_value("Certificate Authority", intermediate_ca, "parent_authority")
verify = frappe.get_doc("Certificate Authority", root_ca).certificate_file

Check warning on line 755 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L754-L755

Added lines #L754 - L755 were not covered by tests
else:
verify = True
if files:
file_objects = {

Check warning on line 759 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L759

Added line #L759 was not covered by tests
key: value
if isinstance(value, _io.BufferedReader)
else frappe.get_doc("File", {"file_url": url}).get_content()
for key, value in files.items()
}
file_objects["json"] = json.dumps(data).encode()
return requests.request(method, url, headers=headers, files=file_objects, verify=verify)

Check warning on line 766 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L765-L766

Added lines #L765 - L766 were not covered by tests
return requests.request(method, url, headers=headers, json=data, verify=verify, timeout=(10, 30))

def request(self, method, path, data=None, files=None, agent_job=None, raises=True):
self.raise_if_past_requests_have_failed()
self.response = None
agent_job_id = agent_job.name if agent_job else None
headers = None
url = None

response = json_response = None
try:
url = f"https://{self.server}:{self.port}/agent/{path}"
password = get_decrypted_password(self.server_type, self.server, "agent_password")
headers = {"Authorization": f"bearer {password}", "X-Agent-Job-Id": agent_job_id}
intermediate_ca = frappe.db.get_value(
"Press Settings", "Press Settings", "backbone_intermediate_ca"
)
if frappe.conf.developer_mode and intermediate_ca:
root_ca = frappe.db.get_value("Certificate Authority", intermediate_ca, "parent_authority")
verify = frappe.get_doc("Certificate Authority", root_ca).certificate_file
else:
verify = True
if files:
file_objects = {
key: value
if isinstance(value, _io.BufferedReader)
else frappe.get_doc("File", {"file_url": url}).get_content()
for key, value in files.items()
}
file_objects["json"] = json.dumps(data).encode()
self.response = requests.request(
method, url, headers=headers, files=file_objects, verify=verify
)
else:
self.response = requests.request(
method, url, headers=headers, json=data, verify=verify, timeout=(10, 30)
)
json_response = None
try:
json_response = self.response.json()
if raises:
self.response.raise_for_status()
return json_response
except Exception:
self.handle_request_failure(agent_job, self.response)
log_error(
title="Agent Request Result Exception",
method=method,
url=url,
data=data,
files=files,
headers=headers,
result=json_response or self.response.text,
doc=agent_job,
agent_job_id = agent_job.name if agent_job else None
response = self._make_req(method, path, data, files, agent_job_id)
json_response = response.json()
if raises and response.status_code >= 400:
output = "\n\n".join([json_response.get("output", ""), json_response.get("traceback", "")])
if output == "\n\n":
output = json.dumps(json_response, indent=2, sort_keys=True)
raise HTTPError(

Check warning on line 780 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L777-L780

Added lines #L777 - L780 were not covered by tests
f"{response.status_code} {response.reason}\n\n{output}",
response=response,
)
return json_response
except (HTTPError, TypeError, ValueError):
self.handle_request_failure(agent_job, response)
log_error(

Check warning on line 787 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L786-L787

Added lines #L786 - L787 were not covered by tests
title="Agent Request Result Exception",
result=json_response or getattr(response, "text", None),
)
except Exception as exc:
self.handle_exception(agent_job, exc)
self.log_request_failure(exc)
self.handle_exception(agent_job, exc)
log_error(
title="Agent Request Exception",
method=method,
url=url,
data=data,
files=files,
headers=headers,
doc=agent_job,
)

def raise_if_past_requests_have_failed(self):
Expand Down Expand Up @@ -851,27 +837,27 @@
def should_skip_requests(self):
return bool(frappe.db.count("Agent Request Failure", {"server": self.server}))

def handle_request_failure(self, agent_job, result: "Response"):
def handle_request_failure(self, agent_job, result: Response | None):
if not agent_job:
return
raise

Check warning on line 842 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L842

Added line #L842 was not covered by tests

reason = None
reason = status_code = None

Check warning on line 844 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L844

Added line #L844 was not covered by tests
with suppress(TypeError, ValueError):
reason = json.dumps(result.json(), indent=4, sort_keys=True)
reason = json.dumps(result.json(), indent=4, sort_keys=True) if result else None

Check warning on line 846 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L846

Added line #L846 was not covered by tests

message = f"""
Status Code: {getattr(result, 'status_code', 'Unknown')}\n
Response: {reason or getattr(result, 'text', 'Unknown')}
Status Code: {status_code or "Unknown"}\n
Response: {reason or getattr(result, "text", "Unknown")}
"""
self.log_failure_reason(agent_job, message)
agent_job.flags.status_code = result.status_code
agent_job.flags.status_code = status_code

Check warning on line 853 in press/agent.py

View check run for this annotation

Codecov / codecov/patch

press/agent.py#L853

Added line #L853 was not covered by tests

def handle_exception(self, agent_job, exception):
self.log_failure_reason(agent_job, exception)

def log_failure_reason(self, agent_job=None, message=None):
if not agent_job:
return
raise

agent_job.traceback = message
agent_job.output = message
Expand Down
Loading