Initial documentation structure
This commit is contained in:
141
scripts/orchestrator/gitea/close_pr_with_feedback.py
Executable file
141
scripts/orchestrator/gitea/close_pr_with_feedback.py
Executable file
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Close PR with Feedback - Reject a PR and help AI learn
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
sys.path.append('/home/netops/orchestrator')
|
||||
from gitea_integration import GiteaIntegration
|
||||
from pr_feedback import PRFeedbackSystem
|
||||
|
||||
def close_pr_with_feedback(pr_number: int, reason: str, issues: List[str]):
|
||||
"""Close a PR and record feedback for AI learning"""
|
||||
|
||||
# Load config
|
||||
with open('/home/netops/orchestrator/config.yaml', 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# Initialize systems
|
||||
gitea = GiteaIntegration(config['gitea'])
|
||||
feedback_system = PRFeedbackSystem()
|
||||
|
||||
print(f"\n🚫 Closing PR #{pr_number} with feedback...")
|
||||
|
||||
# First, add a comment to the PR explaining why it's being closed
|
||||
comment = f"""## 🤖 AI Configuration Review - Rejected
|
||||
|
||||
**Reason**: {reason}
|
||||
|
||||
**Issues Found**:
|
||||
{chr(10).join(f'- {issue}' for issue in issues)}
|
||||
|
||||
This feedback has been recorded to improve future AI suggestions. The AI will learn from these issues and avoid them in future configurations.
|
||||
|
||||
### Specific Problems:
|
||||
- **Security**: The any/any/any permit rule is too permissive
|
||||
- **Best Practice**: Source addresses should be specific, not 'any'
|
||||
- **Risk**: This configuration could expose the network to threats
|
||||
|
||||
The AI will generate better suggestions next time based on this feedback.
|
||||
"""
|
||||
|
||||
# Post comment to PR (using Gitea API)
|
||||
api_url = f"{config['gitea']['url']}/api/v1/repos/{config['gitea']['repo']}/issues/{pr_number}/comments"
|
||||
headers = {
|
||||
'Authorization': f"token {config['gitea']['token']}",
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
comment_data = {"body": comment}
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, json=comment_data, headers=headers)
|
||||
if response.status_code in [200, 201]:
|
||||
print("✅ Added feedback comment to PR")
|
||||
else:
|
||||
print(f"⚠️ Could not add comment: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error adding comment: {e}")
|
||||
|
||||
# Record feedback for AI learning
|
||||
feedback_details = {
|
||||
'reason': reason,
|
||||
'specific_issues': '\n'.join(issues),
|
||||
'configuration_issues': [
|
||||
{'type': 'security_permissive', 'description': 'Rules too permissive (any/any/any)'},
|
||||
{'type': 'security_missing', 'description': 'Missing source address restrictions'}
|
||||
]
|
||||
}
|
||||
|
||||
feedback_system.record_pr_feedback(pr_number, 'rejected', feedback_details)
|
||||
|
||||
# Update orchestrator state
|
||||
state_file = '/var/lib/orchestrator/state.json'
|
||||
try:
|
||||
with open(state_file, 'r') as f:
|
||||
state = json.load(f)
|
||||
|
||||
state['pending_pr'] = None
|
||||
state['last_pr_status'] = 'rejected'
|
||||
state['last_pr_rejected'] = datetime.now().isoformat()
|
||||
|
||||
with open(state_file, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
print("✅ Updated orchestrator state")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not update state: {e}")
|
||||
|
||||
# Show AI learning summary
|
||||
patterns = feedback_system.analyze_feedback_patterns()
|
||||
print(f"\n📊 AI Learning Summary:")
|
||||
print(f"Total feedback entries: {patterns['total_prs']}")
|
||||
print(f"Rejected PRs: {patterns['rejected']}")
|
||||
print(f"Security concerns: {patterns['security_concerns']}")
|
||||
|
||||
print("\n✅ PR closed with feedback. The AI will learn from this!")
|
||||
print("\nNext time the AI generates a configuration, it will:")
|
||||
print("- Avoid any/any/any permit rules")
|
||||
print("- Use specific source addresses")
|
||||
print("- Follow security best practices")
|
||||
|
||||
print("\n⚠️ IMPORTANT: Now manually close the PR in Gitea!")
|
||||
print(f"Go to: {config['gitea']['url']}/{config['gitea']['repo']}/pulls/{pr_number}")
|
||||
print("Click the 'Close Pull Request' button")
|
||||
|
||||
# Quick reject function for current PR
|
||||
def reject_current_pr():
|
||||
"""Reject PR #2 with specific feedback"""
|
||||
close_pr_with_feedback(
|
||||
pr_number=2,
|
||||
reason="Security policy too permissive - any/any/any permit rule is dangerous",
|
||||
issues=[
|
||||
"ALLOW-ESTABLISHED policy permits all traffic from trust to untrust",
|
||||
"Source address should not be 'any' - use specific networks",
|
||||
"Application should not be 'any' - specify required services only",
|
||||
"This configuration could expose internal network to threats"
|
||||
]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--current":
|
||||
# Reject the current PR #2
|
||||
reject_current_pr()
|
||||
else:
|
||||
# Interactive mode
|
||||
pr_num = input("Enter PR number to reject: ")
|
||||
reason = input("Reason for rejection: ")
|
||||
issues = []
|
||||
print("Enter specific issues (empty line to finish):")
|
||||
while True:
|
||||
issue = input("- ")
|
||||
if not issue:
|
||||
break
|
||||
issues.append(issue)
|
||||
|
||||
close_pr_with_feedback(int(pr_num), reason, issues)
|
||||
Reference in New Issue
Block a user