mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2025-12-31 16:30:00 -06:00
Implement a complete, production-ready CI/CD pipeline that runs 100% on GitHub Actions with zero external dependencies. This replaces and consolidates existing workflows with an optimized, streamlined pipeline. ## Major Changes - Add 3 new workflows (ci-comprehensive, cd-development, cd-release) - Remove 2 redundant workflows (backed up) - Add 130+ tests across 4 new test files - Add 8 documentation guides (60+ KB) - Add developer tools and scripts
269 lines
7.8 KiB
Python
269 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
TimeTracker CI/CD Setup Validation Script
|
||
Validates that all CI/CD components are properly configured
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
|
||
class Colors:
|
||
"""ANSI color codes for terminal output"""
|
||
GREEN = '\033[92m'
|
||
RED = '\033[91m'
|
||
YELLOW = '\033[93m'
|
||
BLUE = '\033[94m'
|
||
ENDC = '\033[0m'
|
||
BOLD = '\033[1m'
|
||
|
||
|
||
def print_header(text):
|
||
"""Print a formatted header"""
|
||
print(f"\n{Colors.BOLD}{Colors.BLUE}{'=' * 60}{Colors.ENDC}")
|
||
print(f"{Colors.BOLD}{Colors.BLUE}{text:^60}{Colors.ENDC}")
|
||
print(f"{Colors.BOLD}{Colors.BLUE}{'=' * 60}{Colors.ENDC}\n")
|
||
|
||
|
||
def print_success(text):
|
||
"""Print success message"""
|
||
print(f"{Colors.GREEN}✓{Colors.ENDC} {text}")
|
||
|
||
|
||
def print_error(text):
|
||
"""Print error message"""
|
||
print(f"{Colors.RED}✗{Colors.ENDC} {text}")
|
||
|
||
|
||
def print_warning(text):
|
||
"""Print warning message"""
|
||
print(f"{Colors.YELLOW}⚠{Colors.ENDC} {text}")
|
||
|
||
|
||
def print_info(text):
|
||
"""Print info message"""
|
||
print(f"{Colors.BLUE}ℹ{Colors.ENDC} {text}")
|
||
|
||
|
||
def check_file_exists(filepath, required=True):
|
||
"""Check if a file exists"""
|
||
path = Path(filepath)
|
||
if path.exists():
|
||
print_success(f"Found: {filepath}")
|
||
return True
|
||
else:
|
||
if required:
|
||
print_error(f"Missing required file: {filepath}")
|
||
else:
|
||
print_warning(f"Optional file not found: {filepath}")
|
||
return False
|
||
|
||
|
||
def check_python_package(package_name):
|
||
"""Check if a Python package is installed"""
|
||
try:
|
||
__import__(package_name)
|
||
print_success(f"Python package '{package_name}' is installed")
|
||
return True
|
||
except ImportError:
|
||
print_error(f"Python package '{package_name}' is NOT installed")
|
||
return False
|
||
|
||
|
||
def run_command(command, description):
|
||
"""Run a command and check if it succeeds"""
|
||
try:
|
||
result = subprocess.run(
|
||
command,
|
||
shell=True,
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=30
|
||
)
|
||
if result.returncode == 0:
|
||
print_success(f"{description}: OK")
|
||
return True
|
||
else:
|
||
print_error(f"{description}: FAILED")
|
||
if result.stderr:
|
||
print(f" Error: {result.stderr[:200]}")
|
||
return False
|
||
except subprocess.TimeoutExpired:
|
||
print_error(f"{description}: TIMEOUT")
|
||
return False
|
||
except Exception as e:
|
||
print_error(f"{description}: ERROR ({str(e)})")
|
||
return False
|
||
|
||
|
||
def main():
|
||
"""Main validation function"""
|
||
print_header("TimeTracker CI/CD Setup Validation")
|
||
|
||
# Track results
|
||
checks = {
|
||
'workflows': [],
|
||
'tests': [],
|
||
'config': [],
|
||
'docs': [],
|
||
'python': []
|
||
}
|
||
|
||
# 1. Check GitHub Actions workflows
|
||
print_header("1. GitHub Actions Workflows")
|
||
workflows = [
|
||
'.github/workflows/ci-comprehensive.yml',
|
||
'.github/workflows/cd-development.yml',
|
||
'.github/workflows/cd-release.yml',
|
||
'.github/workflows/docker-publish.yml',
|
||
'.github/workflows/migration-check.yml',
|
||
]
|
||
|
||
for workflow in workflows:
|
||
checks['workflows'].append(check_file_exists(workflow))
|
||
|
||
# 2. Check test files
|
||
print_header("2. Test Files")
|
||
test_files = [
|
||
'tests/conftest.py',
|
||
'tests/test_basic.py',
|
||
'tests/test_routes.py',
|
||
'tests/test_models_comprehensive.py',
|
||
'tests/test_security.py',
|
||
'tests/test_analytics.py',
|
||
'tests/test_invoices.py',
|
||
]
|
||
|
||
for test_file in test_files:
|
||
checks['tests'].append(check_file_exists(test_file))
|
||
|
||
# 3. Check configuration files
|
||
print_header("3. Configuration Files")
|
||
config_files = [
|
||
('pytest.ini', True),
|
||
('requirements-test.txt', True),
|
||
('.pre-commit-config.yaml', False),
|
||
('Makefile', False),
|
||
('.gitignore', True),
|
||
]
|
||
|
||
for config_file, required in config_files:
|
||
checks['config'].append(check_file_exists(config_file, required))
|
||
|
||
# 4. Check documentation
|
||
print_header("4. Documentation")
|
||
docs = [
|
||
'CI_CD_DOCUMENTATION.md',
|
||
'CI_CD_QUICK_START.md',
|
||
'CI_CD_IMPLEMENTATION_SUMMARY.md',
|
||
]
|
||
|
||
for doc in docs:
|
||
checks['docs'].append(check_file_exists(doc))
|
||
|
||
# 5. Check Python dependencies
|
||
print_header("5. Python Dependencies")
|
||
packages = [
|
||
'pytest',
|
||
'flask',
|
||
'sqlalchemy',
|
||
]
|
||
|
||
for package in packages:
|
||
checks['python'].append(check_python_package(package))
|
||
|
||
# 6. Check Python test dependencies
|
||
print_header("6. Test Dependencies")
|
||
test_packages = [
|
||
'pytest',
|
||
'pytest_cov',
|
||
'pytest_flask',
|
||
'black',
|
||
'flake8',
|
||
'bandit',
|
||
]
|
||
|
||
test_deps_ok = True
|
||
for package in test_packages:
|
||
if not check_python_package(package.replace('_', '-')):
|
||
test_deps_ok = False
|
||
|
||
if not test_deps_ok:
|
||
print_info("Install test dependencies: pip install -r requirements-test.txt")
|
||
|
||
# 7. Run quick tests
|
||
print_header("7. Quick Test Validation")
|
||
|
||
# Check if pytest can discover tests
|
||
if run_command('pytest --collect-only -q', 'Test discovery'):
|
||
print_info("Tests can be discovered successfully")
|
||
|
||
# Try to run smoke tests (if they exist)
|
||
if run_command('pytest -m smoke --co -q', 'Smoke test discovery'):
|
||
print_info("Smoke tests are properly marked")
|
||
|
||
# 8. Check Docker setup
|
||
print_header("8. Docker Configuration")
|
||
docker_files = [
|
||
('Dockerfile', True),
|
||
('docker-compose.yml', True),
|
||
('.dockerignore', False),
|
||
]
|
||
|
||
for docker_file, required in docker_files:
|
||
check_file_exists(docker_file, required)
|
||
|
||
# 9. Check helper scripts
|
||
print_header("9. Helper Scripts")
|
||
scripts = [
|
||
'scripts/run-tests.sh',
|
||
'scripts/run-tests.bat',
|
||
]
|
||
|
||
for script in scripts:
|
||
check_file_exists(script, required=False)
|
||
|
||
# 10. Summary
|
||
print_header("Validation Summary")
|
||
|
||
total_checks = sum(len(v) for v in checks.values())
|
||
passed_checks = sum(sum(v) for v in checks.values())
|
||
|
||
print(f"\n{Colors.BOLD}Results:{Colors.ENDC}")
|
||
print(f" Workflows: {sum(checks['workflows'])}/{len(checks['workflows'])}")
|
||
print(f" Tests: {sum(checks['tests'])}/{len(checks['tests'])}")
|
||
print(f" Configuration: {sum(checks['config'])}/{len(checks['config'])}")
|
||
print(f" Documentation: {sum(checks['docs'])}/{len(checks['docs'])}")
|
||
print(f" Python deps: {sum(checks['python'])}/{len(checks['python'])}")
|
||
print(f"\n{Colors.BOLD}Total: {passed_checks}/{total_checks}{Colors.ENDC}")
|
||
|
||
if passed_checks == total_checks:
|
||
print(f"\n{Colors.GREEN}{Colors.BOLD}✓ All checks passed! CI/CD setup is complete.{Colors.ENDC}")
|
||
print(f"\n{Colors.BOLD}Next steps:{Colors.ENDC}")
|
||
print(" 1. Run smoke tests: pytest -m smoke")
|
||
print(" 2. Create a test PR to verify CI works")
|
||
print(" 3. Review documentation: CI_CD_QUICK_START.md")
|
||
return 0
|
||
else:
|
||
failed = total_checks - passed_checks
|
||
print(f"\n{Colors.YELLOW}{Colors.BOLD}⚠ Setup incomplete: {failed} checks failed{Colors.ENDC}")
|
||
print(f"\n{Colors.BOLD}Action required:{Colors.ENDC}")
|
||
print(" 1. Review errors above")
|
||
print(" 2. Install missing dependencies: pip install -r requirements-test.txt")
|
||
print(" 3. Check documentation: CI_CD_DOCUMENTATION.md")
|
||
return 1
|
||
|
||
|
||
if __name__ == '__main__':
|
||
try:
|
||
sys.exit(main())
|
||
except KeyboardInterrupt:
|
||
print(f"\n\n{Colors.YELLOW}Validation interrupted by user{Colors.ENDC}")
|
||
sys.exit(130)
|
||
except Exception as e:
|
||
print(f"\n{Colors.RED}Validation failed with error: {e}{Colors.ENDC}")
|
||
sys.exit(1)
|
||
|