Files
nfoguard/config/validation_cli.py
T
2025-09-29 08:59:47 -04:00

293 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Configuration Validation CLI for NFOGuard
Provides command-line validation and reporting of configuration issues
"""
import sys
import json
import argparse
from pathlib import Path
from typing import Dict, Any, List
from datetime import datetime
from config.validator import validate_configuration, ValidationSeverity
from config.runtime_validator import RuntimeValidator
from config.settings import NFOGuardConfig
class ValidationReporter:
"""Formats and displays validation results"""
def __init__(self, verbose: bool = False, json_output: bool = False):
self.verbose = verbose
self.json_output = json_output
# Color codes for terminal output
self.colors = {
'error': '\033[91m', # Red
'warning': '\033[93m', # Yellow
'info': '\033[94m', # Blue
'success': '\033[92m', # Green
'reset': '\033[0m', # Reset
'bold': '\033[1m' # Bold
}
def report_validation_results(self, result, runtime_result=None) -> int:
"""
Report validation results
Returns:
Exit code (0 for success, 1 for warnings, 2 for errors)
"""
if self.json_output:
return self._report_json(result, runtime_result)
else:
return self._report_human_readable(result, runtime_result)
def _report_json(self, result, runtime_result=None) -> int:
"""Report results in JSON format"""
output = {
"timestamp": datetime.now().isoformat(),
"validation": result.to_dict()
}
if runtime_result:
output["runtime_validation"] = runtime_result.to_dict()
print(json.dumps(output, indent=2))
if result.errors_count > 0:
return 2
elif result.warnings_count > 0:
return 1
return 0
def _report_human_readable(self, result, runtime_result=None) -> int:
"""Report results in human-readable format"""
print(f"{self.colors['bold']}NFOGuard Configuration Validation Report{self.colors['reset']}")
print("=" * 50)
# Overall status
if result.is_valid:
status_color = self.colors['success']
status_text = "✓ VALID"
else:
status_color = self.colors['error']
status_text = "✗ INVALID"
print(f"Status: {status_color}{status_text}{self.colors['reset']}")
print(f"Errors: {result.errors_count}")
print(f"Warnings: {result.warnings_count}")
print(f"Total Issues: {len(result.issues)}")
print()
# Report issues by severity
if result.issues:
self._report_issues_by_severity(result.issues)
# Report runtime validation if available
if runtime_result:
print(f"\n{self.colors['bold']}Runtime Validation{self.colors['reset']}")
print("-" * 20)
if runtime_result.issues:
self._report_issues_by_severity(runtime_result.issues, "Runtime")
else:
print(f"{self.colors['success']}✓ All runtime checks passed{self.colors['reset']}")
# Summary and recommendations
self._report_summary_and_recommendations(result)
# Return appropriate exit code
if result.errors_count > 0 or (runtime_result and runtime_result.errors_count > 0):
return 2
elif result.warnings_count > 0 or (runtime_result and runtime_result.warnings_count > 0):
return 1
return 0
def _report_issues_by_severity(self, issues: List, context: str = "Configuration") -> None:
"""Report issues grouped by severity"""
errors = [issue for issue in issues if issue.severity == ValidationSeverity.ERROR]
warnings = [issue for issue in issues if issue.severity == ValidationSeverity.WARNING]
info_issues = [issue for issue in issues if issue.severity == ValidationSeverity.INFO]
if errors:
print(f"{self.colors['error']}{self.colors['bold']}ERRORS ({len(errors)}):{self.colors['reset']}")
for issue in errors:
self._format_issue(issue)
print()
if warnings:
print(f"{self.colors['warning']}{self.colors['bold']}WARNINGS ({len(warnings)}):{self.colors['reset']}")
for issue in warnings:
self._format_issue(issue)
print()
if info_issues and self.verbose:
print(f"{self.colors['info']}{self.colors['bold']}INFO ({len(info_issues)}):{self.colors['reset']}")
for issue in info_issues:
self._format_issue(issue)
print()
def _format_issue(self, issue) -> None:
"""Format a single validation issue"""
severity_colors = {
ValidationSeverity.ERROR: self.colors['error'],
ValidationSeverity.WARNING: self.colors['warning'],
ValidationSeverity.INFO: self.colors['info']
}
color = severity_colors.get(issue.severity, '')
print(f" {color}{issue.setting}:{self.colors['reset']} {issue.message}")
if issue.current_value is not None:
print(f" Current: {issue.current_value}")
if issue.suggested_value is not None:
print(f" Suggested: {issue.suggested_value}")
if self.verbose and issue.details:
print(f" Details: {issue.details}")
def _report_summary_and_recommendations(self, result) -> None:
"""Report summary and general recommendations"""
print(f"\n{self.colors['bold']}Summary{self.colors['reset']}")
print("-" * 10)
if result.is_valid:
print(f"{self.colors['success']}✓ Your configuration is valid and ready to use!{self.colors['reset']}")
else:
print(f"{self.colors['error']}✗ Your configuration has issues that need to be resolved.{self.colors['reset']}")
# Provide specific recommendations based on issue types
recommendations = self._generate_recommendations(result)
if recommendations:
print(f"\n{self.colors['bold']}Recommendations{self.colors['reset']}")
print("-" * 15)
for rec in recommendations:
print(f" {self.colors['info']}{rec}{self.colors['reset']}")
def _generate_recommendations(self, result) -> List[str]:
"""Generate recommendations based on validation results"""
recommendations = []
# Check for common patterns
path_issues = [issue for issue in result.issues if 'path' in issue.setting.lower()]
if path_issues:
recommendations.append("Verify all file paths are correct for your environment")
recommendations.append("Ensure paths are absolute when using Docker")
db_issues = [issue for issue in result.issues if 'db' in issue.setting.lower()]
if db_issues:
recommendations.append("Check database connection settings and credentials")
url_issues = [issue for issue in result.issues if 'url' in issue.setting.lower()]
if url_issues:
recommendations.append("Verify API URLs are reachable and include the correct port")
perf_issues = [issue for issue in result.issues if any(perf in issue.setting.lower()
for perf in ['concurrent', 'delay', 'timeout'])]
if perf_issues:
recommendations.append("Consider adjusting performance settings based on your system resources")
# General recommendations
if result.errors_count > 0:
recommendations.append("Fix all ERROR-level issues before starting NFOGuard")
if result.warnings_count > 0:
recommendations.append("Review WARNING-level issues to optimize performance and reliability")
return recommendations
async def run_validation(args) -> int:
"""Run configuration validation"""
reporter = ValidationReporter(verbose=args.verbose, json_output=args.json)
try:
# Run static validation
print("Running configuration validation..." if not args.json else "", file=sys.stderr)
result = validate_configuration()
runtime_result = None
# Run runtime validation if requested
if args.runtime:
print("Running runtime validation..." if not args.json else "", file=sys.stderr)
try:
config = NFOGuardConfig()
runtime_validator = RuntimeValidator(config)
runtime_result = await runtime_validator.validate_runtime_config()
except Exception as e:
if not args.json:
print(f"Runtime validation failed: {e}", file=sys.stderr)
# Continue with static validation results
# Report results
return reporter.report_validation_results(result, runtime_result)
except Exception as e:
if args.json:
error_output = {
"timestamp": datetime.now().isoformat(),
"error": {
"message": str(e),
"type": type(e).__name__
}
}
print(json.dumps(error_output, indent=2))
else:
print(f"Validation failed: {e}", file=sys.stderr)
return 2
def main():
"""Main CLI entry point"""
parser = argparse.ArgumentParser(
description="Validate NFOGuard configuration",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Basic validation
%(prog)s --runtime # Include runtime checks
%(prog)s --verbose # Show detailed information
%(prog)s --json # Output JSON format
%(prog)s --runtime --json # Runtime validation with JSON output
"""
)
parser.add_argument(
"--runtime",
action="store_true",
help="Perform runtime validation (tests actual connectivity and permissions)"
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Show verbose output including info-level messages"
)
parser.add_argument(
"--json",
action="store_true",
help="Output results in JSON format"
)
args = parser.parse_args()
# Run validation
import asyncio
try:
exit_code = asyncio.run(run_validation(args))
sys.exit(exit_code)
except KeyboardInterrupt:
print("Validation interrupted", file=sys.stderr)
sys.exit(130)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
sys.exit(2)
if __name__ == "__main__":
main()