Skip to content

Commit 651a7e9

Browse files
committed
TS-39583: Add API call to mark remediation as failed for initial build failures and QA agent failures
Added notify_remediation_failed function to contrast_api.py with support for: - INITIAL_BUILD_FAILURE when build fails before PR creation - EXCEEDED_QA_ATTEMPTS when QA agent exhausts retry attempts This will help prevent remediations from being locked in a failed state for extended periods.
1 parent 6ff489b commit 651a7e9

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

src/contrast_api.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,66 @@ def notify_remediation_pr_closed(remediation_id: str, contrast_host: str, contra
303303
except json.JSONDecodeError:
304304
print(f"Error decoding JSON response when notifying Remediation service about closed PR for remediation {remediation_id}.", file=sys.stderr)
305305
return False
306+
307+
def notify_remediation_failed(remediation_id: str, failure_category: str, contrast_host: str, contrast_org_id: str, contrast_app_id: str, contrast_auth_key: str, contrast_api_key: str) -> bool:
308+
"""Notifies the Remediation backend service that a remediation has failed.
309+
310+
Args:
311+
remediation_id: The ID of the remediation.
312+
failure_category: The category of failure (e.g., "INITIAL_BUILD_FAILURE").
313+
contrast_host: The Contrast Security host URL.
314+
contrast_org_id: The organization ID.
315+
contrast_app_id: The application ID.
316+
contrast_auth_key: The Contrast authorization key.
317+
contrast_api_key: The Contrast API key.
318+
319+
Returns:
320+
bool: True if the notification was successful, False otherwise.
321+
"""
322+
debug_print(f"--- Notifying Remediation service about failed remediation {remediation_id} with category {failure_category} ---")
323+
api_url = f"https://{normalize_host(contrast_host)}/api/v4/aiml-remediation/organizations/{contrast_org_id}/applications/{contrast_app_id}/remediations/{remediation_id}/failed"
324+
325+
headers = {
326+
"Authorization": contrast_auth_key,
327+
"API-Key": contrast_api_key,
328+
"Content-Type": "application/json",
329+
"Accept": "application/json",
330+
"User-Agent": config.USER_AGENT
331+
}
332+
333+
payload = {
334+
"failureCategory": failure_category
335+
}
336+
337+
try:
338+
debug_print(f"Making PUT request to: {api_url}")
339+
debug_print(f"Payload: {json.dumps(payload)}")
340+
response = requests.put(api_url, headers=headers, json=payload)
341+
response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
342+
343+
debug_print(f"Remediation failed notification API response status code: {response.status_code}")
344+
345+
if response.status_code == 204:
346+
debug_print(f"Successfully notified Remediation service API about failed remediation {remediation_id}")
347+
return True
348+
else:
349+
error_message = "Unknown error"
350+
try:
351+
response_json = response.json()
352+
if "messages" in response_json and response_json["messages"]:
353+
error_message = response_json["messages"][0]
354+
except:
355+
error_message = response.text
356+
357+
print(f"Failed to notify Remediation service about failed remediation {remediation_id}. Error: {error_message}", file=sys.stderr)
358+
return False
359+
360+
except requests.exceptions.HTTPError as e:
361+
print(f"HTTP error notifying Remediation service about failed remediation {remediation_id}: {e.response.status_code} - {e.response.text}", file=sys.stderr)
362+
return False
363+
except requests.exceptions.RequestException as e:
364+
print(f"Request error notifying Remediation service about failed remediation {remediation_id}: {e}", file=sys.stderr)
365+
return False
366+
except json.JSONDecodeError:
367+
print(f"Error decoding JSON response when notifying Remediation service about failed remediation {remediation_id}.", file=sys.stderr)
368+
return False

src/main.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,23 @@ def main():
160160
error_analysis = extract_build_errors(prefix_build_output)
161161
print("\n❌ Build is broken ❌ -- No fix attempted.")
162162
print(f"Build output:\n{error_analysis}")
163+
164+
# Notify the Remediation service about the failed build
165+
remediation_notified = contrast_api.notify_remediation_failed(
166+
remediation_id=remediation_id,
167+
failure_category="INITIAL_BUILD_FAILURE",
168+
contrast_host=config.CONTRAST_HOST,
169+
contrast_org_id=config.CONTRAST_ORG_ID,
170+
contrast_app_id=config.CONTRAST_APP_ID,
171+
contrast_auth_key=config.CONTRAST_AUTHORIZATION_KEY,
172+
contrast_api_key=config.CONTRAST_API_KEY
173+
)
174+
175+
if remediation_notified:
176+
print(f"Successfully notified Remediation service about failed build for remediation {remediation_id}.", flush=True)
177+
else:
178+
print(f"Warning: Failed to notify Remediation service about failed build for remediation {remediation_id}.", flush=True)
179+
163180
git_handler.cleanup_branch(new_branch_name)
164181
sys.exit(1) # Exit if the build is broken, no point in proceeding
165182

@@ -221,10 +238,34 @@ def main():
221238
# Skip PR creation if QA was run and the build is failing
222239
# or if the QA agent encountered an error (detected by checking qa_summary_log entries)
223240
if (used_build_command and not build_success) or any(s.startswith("Error during QA agent execution:") for s in qa_summary_log):
241+
failure_category = ""
242+
224243
if any(s.startswith("Error during QA agent execution:") for s in qa_summary_log):
225244
print("\n--- Skipping PR creation as QA Agent encountered an error ---")
245+
failure_category = "QA_AGENT_FAILURE"
226246
else:
227247
print("\n--- Skipping PR creation as QA Agent failed to fix build issues ---")
248+
# Check if we've exhausted all retry attempts
249+
if len(qa_summary_log) >= max_qa_attempts_setting:
250+
failure_category = "EXCEEDED_QA_ATTEMPTS"
251+
252+
# Notify the Remediation service about the failed remediation if we have a failure category
253+
if failure_category:
254+
remediation_notified = contrast_api.notify_remediation_failed(
255+
remediation_id=remediation_id,
256+
failure_category=failure_category,
257+
contrast_host=config.CONTRAST_HOST,
258+
contrast_org_id=config.CONTRAST_ORG_ID,
259+
contrast_app_id=config.CONTRAST_APP_ID,
260+
contrast_auth_key=config.CONTRAST_AUTHORIZATION_KEY,
261+
contrast_api_key=config.CONTRAST_API_KEY
262+
)
263+
264+
if remediation_notified:
265+
print(f"Successfully notified Remediation service about {failure_category} for remediation {remediation_id}.", flush=True)
266+
else:
267+
print(f"Warning: Failed to notify Remediation service about {failure_category} for remediation {remediation_id}.", flush=True)
268+
228269
git_handler.cleanup_branch(new_branch_name)
229270
continue # Move to the next vulnerability
230271

0 commit comments

Comments
 (0)