Skip to content

Commit ab374df

Browse files
Merge pull request #46 from Contrast-Security-OSS/AIML-77_misc_reporting_and_error_handling_improvements
AIML-77 misc reporting and error handling improvements
2 parents 5238a9e + 16fd153 commit ab374df

File tree

10 files changed

+1155
-29
lines changed

10 files changed

+1155
-29
lines changed

hooks/pre-commit

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/bin/bash
2+
3+
# Pre-commit hook to clean whitespace
4+
# This script will clean whitespace and include it in the commit
5+
6+
set -e
7+
8+
echo "🔍 Running pre-commit checks..."
9+
10+
# Colors for output
11+
RED='\033[0;31m'
12+
GREEN='\033[0;32m'
13+
YELLOW='\033[1;33m'
14+
BLUE='\033[0;34m'
15+
NC='\033[0m' # No Color
16+
17+
# Function to print colored output
18+
print_error() {
19+
echo -e "${RED}$1${NC}"
20+
}
21+
22+
print_success() {
23+
echo -e "${GREEN}$1${NC}"
24+
}
25+
26+
print_warning() {
27+
echo -e "${YELLOW}⚠️ $1${NC}"
28+
}
29+
30+
print_info() {
31+
echo -e "${BLUE}ℹ️ $1${NC}"
32+
}
33+
34+
# Check if we're in the right directory
35+
if [ ! -f "action.yml" ] || [ ! -d "src" ]; then
36+
print_error "This doesn't appear to be the contrast-ai-smartfix-action repository root"
37+
exit 1
38+
fi
39+
40+
# Clean trailing whitespace from staged files
41+
echo ""
42+
print_info "Cleaning trailing whitespace from staged files..."
43+
44+
# Get list of staged files
45+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
46+
47+
if [ -n "$STAGED_FILES" ]; then
48+
echo "📁 Processing staged files:"
49+
echo "$STAGED_FILES" | sed 's/^/ /'
50+
51+
WHITESPACE_CLEANED=false
52+
53+
# Apply sed command to each staged file
54+
for file in $STAGED_FILES; do
55+
if [ -f "$file" ]; then
56+
echo " 🧹 Cleaning whitespace in: $file"
57+
# Clean trailing whitespace
58+
sed -i '' 's/[[:space:]]*$//' "$file"
59+
# Re-stage the file with cleaned whitespace
60+
git add "$file"
61+
WHITESPACE_CLEANED=true
62+
fi
63+
done
64+
65+
if [ "$WHITESPACE_CLEANED" = true ]; then
66+
print_success "Whitespace cleanup completed and re-staged!"
67+
else
68+
print_info "No files needed whitespace cleanup"
69+
fi
70+
else
71+
print_info "No staged files detected for whitespace cleanup"
72+
fi
73+
74+
echo ""
75+
print_success "All pre-commit checks completed successfully!"
76+
echo "✨ Commit proceeding..."

hooks/pre-push

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash
2+
3+
# Pre-push hook to run Python linting
4+
# This script will block the push if linting fails
5+
6+
set -e
7+
8+
echo "🔍 Running pre-push checks..."
9+
10+
# Colors for output
11+
RED='\033[0;31m'
12+
GREEN='\033[0;32m'
13+
YELLOW='\033[1;33m'
14+
BLUE='\033[0;34m'
15+
NC='\033[0m' # No Color
16+
17+
# Function to print colored output
18+
print_error() {
19+
echo -e "${RED}$1${NC}"
20+
}
21+
22+
print_success() {
23+
echo -e "${GREEN}$1${NC}"
24+
}
25+
26+
print_warning() {
27+
echo -e "${YELLOW}⚠️ $1${NC}"
28+
}
29+
30+
print_info() {
31+
echo -e "${BLUE}ℹ️ $1${NC}"
32+
}
33+
34+
# Check if we're in the right directory
35+
if [ ! -f "action.yml" ] || [ ! -d "src" ]; then
36+
print_error "This doesn't appear to be the contrast-ai-smartfix-action repository root"
37+
exit 1
38+
fi
39+
40+
# Run Python linting
41+
echo ""
42+
print_info "Running Python linter..."
43+
44+
# Check if flake8 is installed, if not try to install it
45+
if ! command -v flake8 &> /dev/null; then
46+
print_warning "flake8 not found. Attempting to install..."
47+
if command -v pip &> /dev/null; then
48+
pip install flake8
49+
elif command -v pip3 &> /dev/null; then
50+
pip3 install flake8
51+
else
52+
print_error "Neither pip nor pip3 found. Please install flake8 manually:"
53+
print_error "pip install flake8"
54+
exit 1
55+
fi
56+
fi
57+
58+
# Find all Python files in src/ and test/ directories
59+
PYTHON_FILES=$(find src/ test/ -name "*.py" 2>/dev/null || true)
60+
61+
if [ -z "$PYTHON_FILES" ]; then
62+
print_warning "No Python files found in src/ or test/ directories"
63+
exit 0
64+
fi
65+
66+
echo "📁 Found Python files:"
67+
echo "$PYTHON_FILES" | sed 's/^/ /'
68+
69+
# Run flake8 on the Python files
70+
echo ""
71+
echo "🧹 Running flake8 linter..."
72+
73+
if flake8 $PYTHON_FILES; then
74+
print_success "All Python files passed linting!"
75+
echo ""
76+
print_success "Pre-push linting check completed successfully!"
77+
echo "🚀 Push proceeding..."
78+
exit 0
79+
else
80+
echo ""
81+
print_error "Linting failed! Push blocked."
82+
echo ""
83+
echo "💡 To fix linting issues:"
84+
echo " 1. Review the errors above"
85+
echo " 2. Fix the issues in your code"
86+
echo " 3. Commit your fixes"
87+
echo " 4. Try pushing again"
88+
echo ""
89+
echo "🔧 To skip all pre-push checks (not recommended):"
90+
echo " git push --no-verify"
91+
echo ""
92+
exit 1
93+
fi

src/closed_handler.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from src import contrast_api
2626
from src.config import get_config # Using get_config function instead of direct import
2727
from src.utils import debug_log, extract_remediation_id_from_branch, extract_remediation_id_from_labels, log
28-
from src.git_handler import extract_issue_number_from_branch
28+
from src.git_handler import extract_issue_number_from_branch, get_pr_changed_files_count
2929
import src.telemetry_handler as telemetry_handler
3030

3131

@@ -119,10 +119,37 @@ def _extract_vulnerability_info(labels: list) -> str:
119119
return vuln_uuid
120120

121121

122-
def _notify_remediation_service(remediation_id: str):
122+
def _notify_remediation_service(remediation_id: str, pr_number: int = None):
123123
"""Notify the Remediation backend service about the closed PR."""
124124
log(f"Notifying Remediation service about closed PR for remediation {remediation_id}...")
125125
config = get_config()
126+
127+
# Check if PR has no changed files (for external agents like Copilot)
128+
if pr_number is not None:
129+
changed_files_count = get_pr_changed_files_count(pr_number)
130+
if changed_files_count == 0:
131+
# PR has no changes - report as failed remediation
132+
log(f"PR {pr_number} has no changed files. Reporting as failed remediation.")
133+
remediation_notified = contrast_api.notify_remediation_failed(
134+
remediation_id=remediation_id,
135+
failure_category="GENERATE_PR_FAILURE",
136+
contrast_host=config.CONTRAST_HOST,
137+
contrast_org_id=config.CONTRAST_ORG_ID,
138+
contrast_app_id=config.CONTRAST_APP_ID,
139+
contrast_auth_key=config.CONTRAST_AUTHORIZATION_KEY,
140+
contrast_api_key=config.CONTRAST_API_KEY
141+
)
142+
143+
if remediation_notified:
144+
log(f"Successfully notified Remediation service about failed remediation {remediation_id} (no changes).")
145+
else:
146+
log(f"Failed to notify Remediation service about failed remediation {remediation_id} (no changes).", is_error=True)
147+
return
148+
elif changed_files_count == -1:
149+
# Error getting count, fall back to standard closed notification
150+
log(f"Could not determine changed files count for PR {pr_number}. Proceeding with standard closed notification.")
151+
152+
# Standard PR closed notification
126153
remediation_notified = contrast_api.notify_remediation_pr_closed(
127154
remediation_id=remediation_id,
128155
contrast_host=config.CONTRAST_HOST,
@@ -159,7 +186,8 @@ def handle_closed_pr():
159186
telemetry_handler.update_telemetry("vulnInfo.vulnRule", "unknown")
160187

161188
# Notify the Remediation backend service
162-
_notify_remediation_service(remediation_id)
189+
pr_number = pull_request.get("number")
190+
_notify_remediation_service(remediation_id, pr_number)
163191

164192
# Complete telemetry and finish
165193
telemetry_handler.update_telemetry("additionalAttributes.prStatus", "CLOSED")

src/external_coding_agent.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,22 @@ def generate_fixes(self, vuln_uuid: str, remediation_id: str, vuln_title: str, i
163163
# Use git_handler to find if there's an existing issue with this label
164164
issue_number = git_handler.find_issue_with_label(vulnerability_label)
165165

166-
if issue_number:
167-
debug_log(f"Found existing GitHub issue #{issue_number} with label {vulnerability_label}")
168-
if not git_handler.reset_issue(issue_number, remediation_label):
169-
log(f"Failed to reset issue #{issue_number} with labels {vulnerability_label}, {remediation_label}", is_error=True)
170-
error_exit(remediation_id, FailureCategory.AGENT_FAILURE.value)
171-
else:
166+
if issue_number is None:
167+
# Check if this is because Issues are disabled
168+
if not git_handler.check_issues_enabled():
169+
log("GitHub Issues are disabled for this repository. External coding agent requires Issues to be enabled.", is_error=True)
170+
error_exit(remediation_id, FailureCategory.GIT_COMMAND_FAILURE.value)
171+
172172
debug_log(f"No GitHub issue found with label {vulnerability_label}")
173173
issue_number = git_handler.create_issue(issue_title, issue_body, vulnerability_label, remediation_label)
174174
if not issue_number:
175175
log(f"Failed to create issue with labels {vulnerability_label}, {remediation_label}", is_error=True)
176176
error_exit(remediation_id, FailureCategory.AGENT_FAILURE.value)
177+
else:
178+
debug_log(f"Found existing GitHub issue #{issue_number} with label {vulnerability_label}")
179+
if not git_handler.reset_issue(issue_number, remediation_label):
180+
log(f"Failed to reset issue #{issue_number} with labels {vulnerability_label}, {remediation_label}", is_error=True)
181+
error_exit(remediation_id, FailureCategory.AGENT_FAILURE.value)
177182

178183
telemetry_handler.update_telemetry("additionalAttributes.externalIssueNumber", issue_number)
179184

0 commit comments

Comments
 (0)