diff --git a/dockybot/README.md b/dockybot/README.md new file mode 100644 index 00000000..9ec55408 --- /dev/null +++ b/dockybot/README.md @@ -0,0 +1,355 @@ +# ๐Ÿค– DockyBot + +
+ +![DockyBot](https://img.shields.io/badge/DockyBot-๐Ÿณ_Powered-blue?style=for-the-badge) +![Python](https://img.shields.io/badge/Python-3.10+-brightgreen?style=for-the-badge&logo=python) +![Docker](https://img.shields.io/badge/Docker-Required-2496ED?style=for-the-badge&logo=docker) +![Platform](https://img.shields.io/badge/Platforms-4_Supported-orange?style=for-the-badge) + +**๐Ÿณ Docker-powered cross-platform orchestrator** + +*Test your library across multiple Linux distributions with beautiful, interactive output* + +
+ +--- + +## ๐ŸŒŸ Features + +### ๐Ÿงช **Automated Testing** +- ๐Ÿณ **Multi-Platform Support**: Ubuntu, Alpine, CentOS, Debian +- ๐Ÿ“Š **Beautiful Progress Bars**: Real-time progress tracking with Rich UI +- ๐ŸŽจ **Colored Output**: Smart log formatting with emoji indicators +- โšก **Smart Caching**: Build once, run instantly + +### ๐Ÿ› ๏ธ **Interactive Development** +- ๐Ÿ–ฅ๏ธ **Bash Sessions**: Drop into fully-configured containers +- ๐Ÿ“ฆ **Pre-installed Dependencies**: System packages, ODBC drivers, Python libs +- ๐Ÿ”— **Database Ready**: Pre-configured SQL Server connection +- ๐Ÿ’พ **Persistent Images**: No rebuild needed between sessions + +### ๐Ÿ“ˆ **Advanced Monitoring** +- ๐Ÿ“Š **Step-by-Step Progress**: Track installation phases +- โฑ๏ธ **Elapsed Time Tracking**: See exactly how long each phase takes +- ๐ŸŽฏ **Smart Status Detection**: Automatically detects build phases +- ๐Ÿ“ **Detailed Summaries**: Complete test reports with metrics + +--- + +## ๐Ÿš€ Quick Start + +### Prerequisites +- ๐Ÿณ **Docker** (with daemon running) +- ๐Ÿ **Python 3.10+** +- ๐Ÿ“ฆ **Dependencies**: `pip install docker typer rich` + +### Installation +```bash +# Navigate to mssql-python directory +cd /path/to/mssql-python + +# Install dependencies (if not already installed) +pip install docker typer rich + +# You're ready to go! ๐ŸŽ‰ +``` + +--- + +## ๐Ÿ“– Usage Guide + +### ๐Ÿงช **Running Tests** + +#### Test Single Platform +```bash +# Run tests on Ubuntu (recommended) +python -m dockybot test ubuntu + +# Run with verbose output +python -m dockybot test ubuntu -v + +# Test other platforms +python -m dockybot test alpine +python -m dockybot test centos +python -m dockybot test debian +``` + +#### Test All Platforms +```bash +# Run tests across all supported platforms +python -m dockybot test-all + +# With verbose output +python -m dockybot test-all -v +``` + +### ๐Ÿ–ฅ๏ธ **Interactive Development** + +#### Bash Into Container +```bash +# Open interactive bash session (Ubuntu) +python -m dockybot bash ubuntu + +# With verbose setup output +python -m dockybot bash ubuntu -v +``` + +**What you get in the bash session:** +- โœ… **All system dependencies** installed +- โœ… **Microsoft ODBC Driver 18** configured +- โœ… **Python virtual environment** activated +- โœ… **All requirements.txt** packages installed +- โœ… **C++ extensions** built and ready +- โœ… **Database connection** pre-configured +- โœ… **Workspace mounted** at `/workspace` + +#### Manual Testing Inside Container +```bash +# Inside the container, you can: +python -m pytest tests/ -v # Run all tests +python -m pytest tests/test_003* -v # Run specific test +python main.py # Run your scripts +pip list # Check installed packages +echo $DB_CONNECTION_STRING # Check DB config +``` + +### ๐Ÿ–ผ๏ธ **Image Management** + +#### List Cached Images +```bash +# See all DockyBot cached images +python -m dockybot images +``` + +#### Clean Cached Images +```bash +# Clean specific platform +python -m dockybot clean ubuntu + +# Clean all cached images +python -m dockybot clean + +# Force clean without confirmation +python -m dockybot clean --force +``` + +### ๐Ÿ“‹ **Platform Information** +```bash +# List all supported platforms +python -m dockybot platforms +``` + +--- + +## ๐ŸŽจ Output Examples + +### ๐Ÿงช **Test Output** +``` +๐Ÿš€ Starting DockyBot Test Suite +๐Ÿณ Platform: Ubuntu +โฐ Starting at: UTC time +๐Ÿ”— Database: SQL Server via host.docker.internal + +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐Ÿณ Running Ubuntu Tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +๐Ÿ”„ Installing system dependencies โ”โ”โ”โ”โ”โ”โ”โ”โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 25% 0:01:23 + +๐Ÿ“ฆ Setting up tzdata (2025b-0ubuntu0.22.04.1) ... +โœ… Successfully installed docker-7.1.0 typer-0.19.1 rich-14.1.0 +๐Ÿงช tests/test_003_connection.py::test_connection_close PASSED [45%] +โœ… tests/test_007_logging.py::test_setup_logging PASSED [89%] + +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Test Summary โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐Ÿค– DockyBot Results โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ Platform: Ubuntu โ”‚ +โ”‚ Status: โœ… PASSED โ”‚ +โ”‚ Duration: 2:34 โ”‚ +โ”‚ Log Lines: 1247 โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` + +### ๐Ÿ–ฅ๏ธ **Interactive Session** +``` +๐Ÿš€ Starting DockyBot Interactive Session +๐Ÿณ Platform: Ubuntu +๐Ÿ–ผ๏ธ Image: dockybot/ubuntu:latest + +๐ŸŽฏ Container ready! Attaching to interactive session... +๐Ÿ“ Type 'exit' to leave the container + +root@container:/workspace# python -m pytest tests/ -v +root@container:/workspace# pip list +root@container:/workspace# python main.py +``` + +--- + +## ๐Ÿ—๏ธ Architecture + +### ๐Ÿ“‚ **File Structure** +``` +dockybot/ +โ”œโ”€โ”€ README.md # ๐Ÿ“– This documentation +โ”œโ”€โ”€ __init__.py # ๐Ÿ“ฆ Package initialization +โ”œโ”€โ”€ __main__.py # ๐Ÿš€ Entry point +โ”œโ”€โ”€ cli.py # ๐Ÿ–ฅ๏ธ Command-line interface +โ”œโ”€โ”€ docker_runner.py # ๐Ÿณ Docker abstraction layer +โ”œโ”€โ”€ platforms.py # ๐ŸŒ Platform configurations +โ””โ”€โ”€ scripts/ # ๐Ÿ“œ Platform-specific setup scripts + โ”œโ”€โ”€ ubuntu.sh # ๐ŸŸ  Ubuntu 22.04 setup + โ”œโ”€โ”€ alpine.sh # ๐Ÿ”ต Alpine 3.18 setup + โ”œโ”€โ”€ centos.sh # ๐Ÿ”ด CentOS 7 setup + โ””โ”€โ”€ debian.sh # ๐ŸŸฃ Debian 11 setup +``` + +### ๐Ÿณ **Docker Strategy** + +#### **Smart Image Caching** +- **First Run**: Builds `dockybot/ubuntu:latest` with all dependencies +- **Subsequent Runs**: Reuses cached image for instant startup +- **Containers**: Removed after each session (`--rm`) +- **Images**: Persist until manually cleaned + +#### **Platform Configurations** +| Platform | Base Image | Package Manager | Shell | +|----------|------------|----------------|-------| +| ๐ŸŸ  Ubuntu | `ubuntu:22.04` | `apt` | `bash` | +| ๐Ÿ”ต Alpine | `alpine:3.18` | `apk` | `sh` | +| ๐Ÿ”ด CentOS | `centos:7` | `yum` | `bash` | +| ๐ŸŸฃ Debian | `debian:11` | `apt` | `bash` | + +--- + +## ๐ŸŽฏ Advanced Usage + +### ๐Ÿ”ง **Custom Environment Variables** +```bash +# Override default database connection +export DB_CONNECTION_STRING="your_custom_connection_string" +python -m dockybot test ubuntu +``` + +### ๐Ÿณ **Direct Docker Commands** +```bash +# Check DockyBot images +docker images | grep dockybot + +# Run container manually +docker run -it --rm -v $(pwd):/workspace dockybot/ubuntu:latest bash + +# Clean up everything +docker system prune -a +``` + +### ๐Ÿ“Š **Debugging & Logs** +```bash +# Enable verbose output for detailed logging +python -m dockybot test ubuntu -v + +# Check Docker daemon logs +docker system events + +# Container resource usage +docker stats +``` + +--- + +## ๐Ÿƒโ€โ™‚๏ธ Performance Tips + +### โšก **Speed Optimizations** +- **Use cached images**: First run takes 3-5 minutes, subsequent runs are instant +- **Keep images**: Only clean when needed (`python -m dockybot clean`) +- **Use Ubuntu**: Fastest platform for most testing scenarios +- **Verbose mode**: Only use `-v` when debugging + +### ๐Ÿ’พ **Storage Management** +```bash +# Check image sizes +python -m dockybot images + +# Clean specific platforms you don't use +python -m dockybot clean alpine centos debian + +# Full cleanup (forces rebuild next time) +docker system prune -a +``` + +--- + +## ๐Ÿ› Troubleshooting + +### Common Issues + +#### ๐Ÿณ **Docker Not Running** +```bash +# Error: Cannot connect to the Docker daemon +# Solution: Start Docker Desktop or daemon +systemctl start docker # Linux +open -a Docker # macOS +``` + +#### ๐Ÿ”’ **Permission Issues** +```bash +# Error: Permission denied accessing Docker +# Solution: Add user to docker group (Linux) +sudo usermod -aG docker $USER +# Then logout and login again +``` + +#### ๐Ÿ’พ **Disk Space** +```bash +# Error: No space left on device +# Solution: Clean Docker system +docker system prune -a +python -m dockybot clean --force +``` + +#### ๐ŸŒ **Network Issues** +```bash +# Error: Cannot connect to SQL Server +# Check: host.docker.internal resolves correctly +docker run --rm alpine ping -c 1 host.docker.internal +``` + +### ๐Ÿ“ž **Getting Help** +- ๐Ÿ“– **Documentation**: Check this README +- ๐Ÿ› **Issues**: Report bugs in the main repository +- ๐Ÿ’ฌ **Discussions**: Use GitHub Discussions for questions +- ๐Ÿ“ง **Contact**: Reach out to the mssql-python team + +--- + +## ๐Ÿ“ˆ Roadmap + +### ๐Ÿš€ **Coming Soon** +- ๐ŸŽ **macOS Support**: ARM64 and Intel testing +- ๐ŸชŸ **Windows Containers**: Native Windows testing +- ๐Ÿงช **Test Parallelization**: Run multiple platforms simultaneously +- ๐Ÿ“Š **HTML Reports**: Beautiful test result dashboards +- ๐Ÿ”„ **CI Integration**: GitHub Actions workflows +- ๐Ÿ **Python Version Matrix**: Test across Python versions + +### ๐Ÿ’ก **Ideas & Suggestions** +We're always looking for ways to improve DockyBot! Feel free to: +- ๐ŸŒŸ Star the repository +- ๐Ÿ› Report issues +- ๐Ÿ’ก Suggest features +- ๐Ÿค Contribute code + +--- + +
+ +## ๐ŸŽ‰ Happy Testing with DockyBot! + +**Made with โค๏ธ by the mssql-python team** + +*Empowering developers to test across platforms effortlessly* ๐Ÿš€ + +--- + +![Footer](https://img.shields.io/badge/๐Ÿค–_DockyBot-Powered_by_Docker-blue?style=for-the-badge) + +
\ No newline at end of file diff --git a/dockybot/__init__.py b/dockybot/__init__.py new file mode 100644 index 00000000..37b78937 --- /dev/null +++ b/dockybot/__init__.py @@ -0,0 +1,7 @@ +""" +DockyBot - Ubuntu testing with Dagger + +Simplified tool to replicate PR pipeline locally on Ubuntu. +""" + +__version__ = "0.1.0" \ No newline at end of file diff --git a/dockybot/__main__.py b/dockybot/__main__.py new file mode 100644 index 00000000..28e55b6c --- /dev/null +++ b/dockybot/__main__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +""" +DockyBot package main entry point + +Allows running: python -m dockybot +""" + +from .cli import app + +if __name__ == "__main__": + app() \ No newline at end of file diff --git a/dockybot/cli.py b/dockybot/cli.py new file mode 100644 index 00000000..0099d878 --- /dev/null +++ b/dockybot/cli.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +""" +DockyBot CLI - Minimal Docker abstraction for multi-platform testing. +""" + +import typer +from rich.console import Console +from rich.table import Table +from .docker_runner import DockerRunner +from .platforms import PLATFORMS, list_platforms + +app = typer.Typer(help="DockyBot - Run tests across platforms with Docker") +console = Console() + +@app.command() +def test( + platform: str = typer.Argument(..., help="Platform to test on"), + verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output") +): + """Run tests on specified platform.""" + if platform not in PLATFORMS: + console.print(f"[red]Error:[/red] Unknown platform '{platform}'") + console.print(f"Available platforms: {', '.join(PLATFORMS.keys())}") + raise typer.Exit(1) + + runner = DockerRunner() + success = runner.run_platform_test(platform, verbose=verbose) + + if not success: + raise typer.Exit(1) + +@app.command() +def bash( + platform: str = typer.Argument(..., help="Platform to bash into"), + verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output") +): + """Open interactive bash session in platform container with dependencies installed.""" + if platform not in PLATFORMS: + console.print(f"[red]Error:[/red] Unknown platform '{platform}'") + console.print(f"Available platforms: {', '.join(PLATFORMS.keys())}") + raise typer.Exit(1) + + runner = DockerRunner() + success = runner.run_platform_bash(platform, verbose=verbose) + + if not success: + raise typer.Exit(1) + +@app.command() +def images(): + """List DockyBot cached images.""" + runner = DockerRunner() + runner.list_cached_images() + +@app.command() +def clean( + platform: str = typer.Argument(None, help="Platform to clean (optional - cleans all if not specified)"), + force: bool = typer.Option(False, "--force", "-f", help="Force removal without confirmation") +): + """Clean DockyBot cached images.""" + runner = DockerRunner() + runner.clean_cached_images(platform, force) + +@app.command() +def platforms(): + """List available platforms.""" + table = Table(title="Available Platforms") + table.add_column("Platform", style="cyan") + table.add_column("Name", style="green") + table.add_column("Description", style="white") + + for platform_id, config in list_platforms().items(): + table.add_row(platform_id, config['name'], config['description']) + + console.print(table) + +@app.command() +def test_all( + verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output") +): + """Run tests on all platforms.""" + runner = DockerRunner() + results = {} + + for platform in PLATFORMS.keys(): + console.print(f"\n[cyan]Testing {platform}...[/cyan]") + results[platform] = runner.run_platform_test(platform, verbose=verbose) + + # Summary + console.print("\n[bold]Test Results Summary:[/bold]") + for platform, success in results.items(): + status = "[green]โœ“ PASSED[/green]" if success else "[red]โœ— FAILED[/red]" + console.print(f" {platform}: {status}") + + failed_count = sum(1 for success in results.values() if not success) + if failed_count > 0: + console.print(f"\n[red]{failed_count} platform(s) failed[/red]") + raise typer.Exit(1) + else: + console.print("\n[green]All platforms passed![/green]") + +if __name__ == "__main__": + app() diff --git a/dockybot/docker_runner.py b/dockybot/docker_runner.py new file mode 100644 index 00000000..b5d974ce --- /dev/null +++ b/dockybot/docker_runner.py @@ -0,0 +1,811 @@ +""" +Minimal Docker abstraction f # Print welcome message + console.print(f"\n๐Ÿš€ [bold green]Starting DockyBot Test Suite[/bold green]") + console.print(f"๐Ÿณ [bold blue]Platform:[/bold blue] {platfo elif "requirement elif "building" in line_lower and ("c++" in line_lower or "extensions" in line_lower or "pybind" in line_lower): + return "๐Ÿ”จ Building C++ extensions", 5 + elif ("running tests" in line_lower or "pytest" in line_lower or + "tests/test_" in line_lower or "passed" in line_lower or + "failed" in line_lower or "skipped" in line_lower or + "test session starts" in line_lower or "collected" in line_lower): + return "๐Ÿงช Running tests", 6t" in line_lower or "pip install" in line_lower: + return "๐Ÿ“ฆ Installing Python packages", 4 + elif "building" in line_lower and ("c++" in line_lower or "extensions" in line_lower or "pybind" in line_lower): + return "๐Ÿ”จ Building C++ extensions", 5 + elif any(test_indicator in line_lower for test_indicator in [ + "running tests", "pytest", "test session starts", "collected", + "tests/test_", "passed", "failed", "skipped", "::test_" + ]): + return "๐Ÿงช Running tests", 6 + elif "completed successfully" in line_lower or "test pipeline completed" in line_lower: + return "โœ… Tests completed", 7title()}") + console.print(f"โฐ [bold yellow]Starting at:[/bold yellow] {console._environ.get('TZ', 'UTC')} time") + console.print(f"๐Ÿ”— [bold cyan]Database:[/bold cyan] SQL Server via host.docker.internal") + console.print()nning tests across platforms. +""" + +import docker +import tempfile +import os +from pathlib import Path +from rich.console import Console +from rich.live import Live +from rich.panel import Panel +from rich.text import Text +from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn +from rich.rule import Rule +from rich.columns import Columns +from .platforms import get_platform_script +import time + +console = Console() + + +class DockerRunner: + """Minimal Docker abstraction for cross-platform testing.""" + + def __init__(self, verbose: bool = False): + self.verbose = verbose + self.client = docker.from_env() + self.workspace = Path.cwd() + + def run_platform(self, platform_name: str, script_content: str) -> bool: + """Run a platform test script in Docker.""" + + # Print welcome message + console.print(f"\n๏ฟฝ [bold green]Starting DockyBot Test Suite[/bold green]") + console.print(f"๐Ÿณ [bold blue]Platform:[/bold blue] {platform_name.title()}") + console.print() + + # Create temp script + with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as f: + f.write(script_content) + script_path = f.name + + try: + os.chmod(script_path, 0o755) + + # Get platform config + platform_config = self._get_platform_config(platform_name) + + # Prepare environment variables + environment = { + 'DB_CONNECTION_STRING': 'Driver=ODBC Driver 18 for SQL Server;Server=host.docker.internal,1433;Database=master;UID=sa;Pwd=Str0ng@Passw0rd123;Encrypt=no;TrustServerCertificate=yes;', + 'PYTHONPATH': '/workspace', + 'DEBIAN_FRONTEND': 'noninteractive', + 'TZ': 'UTC' + } + + # Run container + container = self.client.containers.run( + platform_config['image'], + command=platform_config['command'] + [f"/tmp/test.sh"], + volumes={ + str(self.workspace): {"bind": "/workspace", "mode": "rw"}, + script_path: {"bind": "/tmp/test.sh", "mode": "ro"} + }, + working_dir="/workspace", + environment=environment, + remove=True, + detach=True, + **platform_config.get('docker_options', {}) + ) + + # Stream output + return self._stream_container_output(container, platform_name) + + finally: + if os.path.exists(script_path): + os.unlink(script_path) + + def _get_platform_config(self, platform_name: str) -> dict: + """Get Docker configuration for platform.""" + configs = { + 'ubuntu': { + 'image': 'ubuntu:22.04', + 'command': ['bash'], + 'docker_options': { + 'extra_hosts': {"host.docker.internal": "host-gateway"} + } + }, + 'alpine': { + 'image': 'alpine:3.18', + 'command': ['sh'], + 'docker_options': { + 'extra_hosts': {"host.docker.internal": "host-gateway"} + } + }, + 'centos': { + 'image': 'centos:7', + 'command': ['bash'], + 'docker_options': { + 'extra_hosts': {"host.docker.internal": "host-gateway"} + } + }, + 'debian': { + 'image': 'debian:11', + 'command': ['bash'], + 'docker_options': { + 'extra_hosts': {"host.docker.internal": "host-gateway"} + } + } + } + + if platform_name not in configs: + raise ValueError(f"Unknown platform: {platform_name}") + + return configs[platform_name] + + def _stream_container_output(self, container, platform_name: str) -> bool: + """Stream container output with live updates and beautiful formatting.""" + + output_lines = [] + current_step = "Starting" + step_count = 0 + total_steps = 8 # Updated number of major steps including DB verification + start_time = time.time() + + # Print header + console.print(Rule(f"[bold blue]๐Ÿณ Running {platform_name.title()} Tests[/bold blue]")) + + with Progress( + SpinnerColumn(), + TextColumn("[bold blue]{task.description}", justify="left"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%", justify="center"), + TextColumn("[dim]{task.fields[elapsed]}", justify="right"), + console=console, + expand=True + ) as progress: + + task = progress.add_task( + f"[cyan]Initializing {platform_name}...", + total=total_steps, + elapsed="0:00" + ) + + try: + for log_line in container.logs(stream=True, follow=True): + line = log_line.decode('utf-8').strip() + if line: + output_lines.append(line) + + # Update progress based on content + new_step, step_progress = self._extract_step_info(line) + if new_step != current_step and step_progress > 0: + current_step = new_step + step_count = step_progress # Use the actual step number, not increment + + # Calculate elapsed time + elapsed_seconds = int(time.time() - start_time) + elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}" + + progress.update( + task, + completed=step_count, + description=f"[cyan]{current_step}", + elapsed=elapsed_str + ) + + # Format and display output + self._display_formatted_line(line) + + # Check result + result = container.wait() + success = result['StatusCode'] == 0 + + # Final elapsed time + elapsed_seconds = int(time.time() - start_time) + elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}" + + # Complete progress + progress.update( + task, + completed=total_steps, + description="[green]โœ… Completed" if success else "[red]โŒ Failed", + elapsed=elapsed_str + ) + + except Exception as e: + elapsed_seconds = int(time.time() - start_time) + elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}" + progress.update(task, description="[red]โŒ Error occurred", elapsed=elapsed_str) + console.print(f"[red]๐Ÿ’ฅ Error streaming logs: {e}[/red]") + return False + + # Print summary (outside the progress context to avoid overlap) + elapsed_seconds = int(time.time() - start_time) + self._print_test_summary(platform_name, success, len(output_lines), elapsed_seconds) + + return success + + def _create_status_panel(self, platform_name: str, status: str) -> Panel: + """Create status panel for live updates.""" + content = Text() + content.append(f"Platform: ", style="bold") + content.append(f"{platform_name}\n", style="blue") + content.append(f"Status: ", style="bold") + content.append(status, style="green" if "โœ…" in status else "yellow" if "..." in status else "red") + + return Panel(content, title="DockyBot", border_style="blue") + + def _extract_step_info(self, line: str) -> tuple[str, int]: + """Extract current step and progress from log line.""" + line_lower = line.lower() + + if ("apt-get update" in line_lower or + "installing system dependencies" in line_lower or + ("install" in line_lower and "dependencies" in line_lower)): + return "๐Ÿ“ฆ Installing system dependencies", 1 + elif ("microsoft odbc" in line_lower or "msodbcsql" in line_lower or + "packages-microsoft-prod" in line_lower): + return "๐Ÿ“€ Installing Microsoft ODBC Driver", 2 + elif "python" in line_lower and ("setup" in line_lower or "environment" in line_lower or "venv" in line_lower): + return "๐Ÿ Setting up Python environment", 3 + elif "requirements.txt" in line_lower or "pip install" in line_lower: + return "๏ฟฝ Installing Python packages", 4 + elif "building" in line_lower and ("c++" in line_lower or "extensions" in line_lower or "pybind" in line_lower): + return "๏ฟฝ Building C++ extensions", 5 + elif "running tests" in line_lower or "pytest" in line_lower: + return "๐Ÿงช Running tests", 6 + elif "completed successfully" in line_lower or "test pipeline completed" in line_lower: + return "โœ… Tests completed", 7 + else: + return "๐Ÿ”„ Processing", 0 + + def _extract_status(self, line: str) -> str: + """Extract current status from log line (legacy method for compatibility).""" + step, _ = self._extract_step_info(line) + return step + + def _display_formatted_line(self, line: str) -> None: + """Display a formatted log line with appropriate styling.""" + line_lower = line.lower() + line_stripped = line.strip() + + # Skip empty lines and less important output + if not line_stripped or self._should_skip_line(line): + return + + # Test results (highest priority - check these first) + if " passed " in line_lower or line_lower.endswith(" passed"): + console.print(f"[green]โœ… {line}[/green]") + elif " failed " in line_lower or line_lower.endswith(" failed") or " error " in line_lower or line_lower.endswith(" failed"): + console.print(f"[red]โŒ {line}[/red]") + elif " skipped " in line_lower or line_lower.endswith(" skipped"): + console.print(f"[yellow]โš ๏ธ {line}[/yellow]") + + # Database connection messages + elif any(word in line_lower for word in ['db_connection_string', 'database connection', 'connection target']): + console.print(f"[cyan]๐Ÿ”— {line}[/cyan]") + + # Installation success messages (check before error patterns) + elif any(phrase in line_lower for phrase in [ + 'successfully installed', 'successfully uninstalled', 'successfully', + 'created symlink', + 'collected packages', 'requirement already satisfied' + ]): + console.print(f"[green]โœ… {line}[/green]") + + # Package installation messages + elif any(phrase in line_lower for phrase in [ + 'installing collected packages', 'collecting', 'unpacking', 'preparing to unpack', + 'setting up', 'selecting previously unselected', 'installing system dependencies', + 'installing microsoft odbc', 'installing python packages', 'downloading', 'processing triggers' + ]): + console.print(f"[cyan]๐Ÿ“ฆ {line}[/cyan]") + + # Actual error conditions (very specific patterns) + elif any(pattern in line_lower for pattern in [ + 'fatal error', 'compilation failed', 'build failed', 'installation failed', + 'command not found', 'no such file or directory', 'permission denied', + 'connection refused', 'timeout error', 'cannot connect' + ]) and not any(success_word in line_lower for success_word in ['successfully', 'completed']): + console.print(f"[red]โŒ {line}[/red]") + + # Warning messages (but not system messages) + elif line_lower.startswith('warning:') or 'update-alternatives: warning' in line_lower: + console.print(f"[yellow]โš ๏ธ {line}[/yellow]") + + # Test execution messages + elif any(phrase in line_lower for phrase in ['running tests', 'pytest']) and 'passed' not in line_lower: + console.print(f"[magenta]๐Ÿงช {line}[/magenta]") + + # Building/compilation messages + elif any(phrase in line_lower for phrase in ['building', 'compiling', 'linking']) and 'failed' not in line_lower: + console.print(f"[cyan]๐Ÿ”จ {line}[/cyan]") + + # Important system messages + elif self._is_important_line(line): + console.print(f"[blue]โ„น๏ธ {line}[/blue]") + + # Verbose-only messages + elif self.verbose: + console.print(f"[dim] {line}[/dim]") + + def _should_skip_line(self, line: str) -> bool: + """Check if line should be skipped entirely.""" + line_lower = line.lower() + + # Skip very verbose package management output + skip_patterns = [ + 'debconf:', 'unable to initialize frontend:', 'dpkg-preconfigure:', + 'reading package lists', 'building dependency tree', + 'reading state information', 'the following new packages', + 'get:', 'hit:', 'ign:', 'reading changelogs', + 'reading database ...', '(reading database', 'files and directories currently installed', + 'preparing to unpack', 'unpacking', 'selecting previously unselected', + 'processing triggers for', 'invoke-rc.d:', 'created symlink', + 'update-alternatives: using', 'no schema files found', + 'first installation detected', 'checking nss setup', + 'current default time zone' + ] + + # Skip if line matches any skip pattern + if any(pattern in line_lower for pattern in skip_patterns): + return True + + # Skip very short or empty lines + if len(line.strip()) < 3: + return True + + # Skip lines that are just progress indicators + if line.strip().startswith('โ”') or line.strip().startswith('โ”‚'): + return True + + return False + + def _is_important_line(self, line: str) -> bool: + """Check if line should be shown in non-verbose mode.""" + important_keywords = [ + "Installing", "Building", "Testing", "Running", "โœ…", "โŒ", + "ERROR", "FAILED", "SUCCESS", "Started", "Finished", + "===", "collected", "warnings summary" + ] + return any(keyword in line for keyword in important_keywords) + + def _print_test_summary(self, platform_name: str, success: bool, log_lines: int, elapsed_seconds: int) -> None: + """Print a formatted test summary.""" + console.print() + console.print(Rule("[bold]Test Summary[/bold]")) + + status_color = "green" if success else "red" + status_icon = "โœ…" if success else "โŒ" + status_text = "PASSED" if success else "FAILED" + + elapsed_str = f"{elapsed_seconds // 60}:{elapsed_seconds % 60:02d}" + + summary_panel = Panel( + f"[bold]Platform:[/bold] {platform_name.title()}\n" + f"[bold]Status:[/bold] [{status_color}]{status_icon} {status_text}[/{status_color}]\n" + f"[bold]Duration:[/bold] {elapsed_str}\n" + f"[bold]Log Lines:[/bold] {log_lines}", + title="๐Ÿค– DockyBot Results", + border_style=status_color + ) + + console.print(summary_panel) + console.print() + + def run_platform_test(self, platform: str, verbose: bool = False) -> bool: + """Run platform test using the predefined test script.""" + self.verbose = verbose + + try: + # Get the test script for this platform + script_content = get_platform_script(platform) + + # Run the platform test + return self.run_platform(platform, script_content) + + except Exception as e: + console.print(f"[red]๐Ÿ’ฅ Error running {platform} test: {e}[/red]") + return False + + def run_platform_bash(self, platform: str, verbose: bool = False) -> bool: + """Run interactive bash session in platform container with dependencies installed.""" + self.verbose = verbose + + try: + # Check if we have a cached image, if not build it + image_name = f"dockybot/{platform}:latest" + + if not self._image_exists(image_name): + console.print(f"๐Ÿ—๏ธ [bold yellow]Building cached image for {platform}...[/bold yellow]") + console.print("โš™๏ธ [dim]This will take a few minutes but only happens once![/dim]") + console.print(f"๐Ÿ’พ [dim]Image will be saved as: {image_name}[/dim]") + + # Build the image with all dependencies + success = self._build_platform_image(platform, image_name) + if not success: + return False + + console.print(f"โœ… [bold green]Image built and cached![/bold green]") + console.print(f"๐Ÿ” [dim]You can see it with: docker images | grep dockybot[/dim]") + else: + console.print(f"๐Ÿš€ [bold green]Using cached image:[/bold green] {image_name}") + console.print(f"โšก [dim]No rebuild needed - dependencies already installed![/dim]") + + # Run interactive session using the cached image + return self._run_interactive_session(platform, image_name) + + except Exception as e: + console.print(f"[red]๐Ÿ’ฅ Error running {platform} bash session: {e}[/red]") + return False + + def _image_exists(self, image_name: str) -> bool: + """Check if Docker image exists locally.""" + try: + self.client.images.get(image_name) + return True + except: + return False + + def _build_platform_image(self, platform: str, image_name: str) -> bool: + """Build a cached Docker image with all dependencies installed.""" + try: + # Get the setup script + script_content = get_platform_script(platform) + setup_script = self._create_build_script(script_content) + + # Create Dockerfile + dockerfile_content = self._create_dockerfile(platform, setup_script) + + # Build image + console.print(f"๐Ÿ“ฆ [cyan]Building image {image_name}...[/cyan]") + + # Create build context + import tempfile + import tarfile + import io + + # Create tar archive with Dockerfile and setup script + tar_buffer = io.BytesIO() + with tarfile.open(fileobj=tar_buffer, mode='w') as tar: + # Add Dockerfile + dockerfile_info = tarfile.TarInfo(name='Dockerfile') + dockerfile_info.size = len(dockerfile_content.encode()) + tar.addfile(dockerfile_info, io.BytesIO(dockerfile_content.encode())) + + # Add setup script + script_info = tarfile.TarInfo(name='setup.sh') + script_info.size = len(setup_script.encode()) + script_info.mode = 0o755 + tar.addfile(script_info, io.BytesIO(setup_script.encode())) + + tar_buffer.seek(0) + + # Build the image + self.client.images.build( + fileobj=tar_buffer, + tag=image_name, + custom_context=True, + rm=True + ) + + console.print(f"โœ… [bold green]Successfully built image:[/bold green] {image_name}") + return True + + except Exception as e: + console.print(f"[red]โŒ Error building image: {e}[/red]") + return False + + def _create_dockerfile(self, platform: str, setup_script: str) -> str: + """Create Dockerfile for the platform.""" + platform_config = self._get_platform_config(platform) + base_image = platform_config['image'] + + dockerfile = f"""FROM {base_image} + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV PYTHONPATH=/workspace +ENV DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=host.docker.internal,1433;Database=master;UID=sa;Pwd=Str0ng@Passw0rd123;Encrypt=no;TrustServerCertificate=yes;" + +# Copy and run setup script +COPY setup.sh /tmp/setup.sh +RUN chmod +x /tmp/setup.sh && /tmp/setup.sh + +# Set working directory +WORKDIR /workspace + +# Default command +CMD ["bash"] +""" + return dockerfile + + def _create_build_script(self, original_script: str) -> str: + """Create build script that installs dependencies but doesn't run tests.""" + lines = original_script.split('\n') + build_lines = [] + + for line in lines: + # Skip the shebang and set -euo pipefail for build + if line.startswith('#!') or 'set -euo pipefail' in line: + continue + # Stop before running tests + if any(test_indicator in line.lower() for test_indicator in + ['python -m pytest', 'pytest', 'running tests']): + break + build_lines.append(line) + + # Add final steps for image + build_lines.extend([ + '', + 'echo "๐ŸŽ‰ DockyBot image build complete!"', + 'echo "โœ… All dependencies installed and ready to use"' + ]) + + return '\n'.join(build_lines) + + def _run_interactive_session(self, platform: str, image_name: str) -> bool: + """Run interactive bash session using cached image.""" + + console.print(f"\n๐Ÿš€ [bold green]Starting DockyBot Interactive Session[/bold green]") + console.print(f"๐Ÿณ [bold blue]Platform:[/bold blue] {platform.title()}") + console.print(f"๐Ÿ–ผ๏ธ [bold cyan]Image:[/bold cyan] {image_name}") + console.print() + + try: + # Create or reuse named container + container_name = f"dockybot-{platform}-session" + + try: + # Try to remove existing container if it exists + existing = self.client.containers.get(container_name) + existing.remove(force=True) + except: + pass # Container doesn't exist, which is fine + + console.print(f"๐ŸŽฏ [bold green]Starting container...[/bold green]") + console.print(f"๐Ÿ“ [dim]Type 'exit' to leave the container[/dim]") + console.print() + + # Run interactive container + import subprocess + import warnings + + # Suppress urllib3 warnings that occur during container cleanup + warnings.filterwarnings("ignore", category=ResourceWarning) + + try: + result = subprocess.run([ + 'docker', 'run', '--rm', '-it', + '--name', container_name, + '-v', f'{str(self.workspace)}:/workspace', + '-w', '/workspace', + '--add-host', 'host.docker.internal:host-gateway', + '-e', 'DB_CONNECTION_STRING=Driver=ODBC Driver 18 for SQL Server;Server=host.docker.internal,1433;Database=master;UID=sa;Pwd=Str0ng@Passw0rd123;Encrypt=no;TrustServerCertificate=yes;', + image_name, + 'bash', '-c', 'source /opt/venv/bin/activate 2>/dev/null || true; exec bash' + ], cwd=str(self.workspace)) + + console.print(f"\n๐Ÿ‘‹ [dim]Session ended. Container cleaned up.[/dim]") + return result.returncode == 0 + + except KeyboardInterrupt: + console.print(f"\nโš ๏ธ [yellow]Session interrupted. Cleaning up...[/yellow]") + # Try to stop the container gracefully + try: + running_container = self.client.containers.get(container_name) + running_container.stop(timeout=5) + except: + pass + return False + + except Exception as e: + console.print(f"[red]๐Ÿ’ฅ Error running interactive session: {e}[/red]") + return False + + def _create_setup_script(self, original_script: str) -> str: + """Create setup script that stops before running tests and keeps container alive.""" + lines = original_script.split('\n') + setup_lines = [] + + for line in lines: + # Stop before running tests + if any(test_indicator in line.lower() for test_indicator in + ['python -m pytest', 'pytest', 'running tests']): + break + setup_lines.append(line) + + # Add keeping container alive instead of exec bash + setup_lines.extend([ + '', + 'echo "๐ŸŽ‰ Setup complete! Container ready for interactive session..."', + 'echo "๐Ÿ’ก You can now run tests manually with: python -m pytest tests/ -v"', + 'echo "๐Ÿ“ Workspace is mounted at: /workspace"', + 'echo "๐Ÿ Python environment is activated"', + 'echo "๐Ÿ”— DB_CONNECTION_STRING is configured"', + 'echo ""', + 'cd /workspace', + 'source /opt/venv/bin/activate', + '# Keep container running', + 'tail -f /dev/null' + ]) + + return '\n'.join(setup_lines) + + def run_platform_interactive(self, platform_name: str, script_content: str) -> bool: + """Run platform in interactive mode.""" + + # Print welcome message + console.print(f"\n๐Ÿš€ [bold green]Starting DockyBot Interactive Session[/bold green]") + console.print(f"๐Ÿณ [bold blue]Platform:[/bold blue] {platform_name.title()}") + console.print(f"๐Ÿ”ง [bold yellow]Mode:[/bold yellow] Interactive Bash") + console.print() + + # Create temp script + with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as f: + f.write(script_content) + script_path = f.name + + try: + os.chmod(script_path, 0o755) + + # Get platform config + platform_config = self._get_platform_config(platform_name) + + # Prepare environment variables + environment = { + 'DB_CONNECTION_STRING': 'Driver=ODBC Driver 18 for SQL Server;Server=host.docker.internal,1433;Database=master;UID=sa;Pwd=Str0ng@Passw0rd123;Encrypt=no;TrustServerCertificate=yes;', + 'PYTHONPATH': '/workspace', + 'DEBIAN_FRONTEND': 'noninteractive', + 'TZ': 'UTC' + } + + # Create or reuse named container + container_name = f"dockybot-{platform_name}" + + console.print(f"๐Ÿ—๏ธ [bold cyan]Setting up container:[/bold cyan] {container_name}") + console.print("โš™๏ธ [dim]Installing dependencies... This may take a few minutes on first run.[/dim]") + console.print() + + try: + # Try to remove existing container if it exists + existing = self.client.containers.get(container_name) + existing.remove(force=True) + except: + pass # Container doesn't exist, which is fine + + # Run container interactively + container = self.client.containers.run( + platform_config['image'], + command=platform_config['command'] + [f"/tmp/setup.sh"], + name=container_name, + volumes={ + str(self.workspace): {"bind": "/workspace", "mode": "rw"}, + script_path: {"bind": "/tmp/setup.sh", "mode": "ro"} + }, + working_dir="/workspace", + environment=environment, + detach=True, + **platform_config.get('docker_options', {}) + ) + + # Wait for setup to complete + console.print("โณ [yellow]Waiting for setup to complete...[/yellow]") + + # Stream setup logs to show progress + for log_line in container.logs(stream=True, follow=True): + line = log_line.decode('utf-8').strip() + if line: + self._display_formatted_line(line) + # Break when we see the completion message + if "Setup complete! Container ready for interactive session" in line: + break + + console.print(f"๐ŸŽฏ [bold green]Container ready![/bold green] Attaching to interactive session...") + console.print(f"๐Ÿ“ [dim]Type 'exit' to leave the container[/dim]") + console.print() + + # Attach to container for interactive session with proper environment + import subprocess + result = subprocess.run([ + 'docker', 'exec', '-it', '-e', 'DB_CONNECTION_STRING=' + environment['DB_CONNECTION_STRING'], + container_name, 'bash', '-c', + 'cd /workspace && source /opt/venv/bin/activate && exec bash' + ], cwd=str(self.workspace)) + + # Clean up + try: + container.remove(force=True) + except: + pass + + return result.returncode == 0 + + finally: + if os.path.exists(script_path): + os.unlink(script_path) + + def list_platforms(self) -> list: + """List available platforms.""" + return ['ubuntu', 'alpine', 'centos', 'debian'] + + def list_cached_images(self) -> None: + """List DockyBot cached images.""" + console.print("\n๐Ÿ–ผ๏ธ [bold blue]DockyBot Cached Images[/bold blue]") + console.print() + + images = self.client.images.list() + dockybot_images = [img for img in images if any('dockybot/' in tag for tag in img.tags)] + + if not dockybot_images: + console.print("๐Ÿ“ญ [dim]No cached images found[/dim]") + console.print("๐Ÿ’ก [dim]Run 'python -m dockybot bash ' to create one[/dim]") + return + + from rich.table import Table + table = Table() + table.add_column("Image", style="cyan") + table.add_column("Size", style="green") + table.add_column("Created", style="yellow") + + for image in dockybot_images: + for tag in image.tags: + if 'dockybot/' in tag: + size_mb = round(image.attrs['Size'] / (1024 * 1024), 1) + created = image.attrs['Created'][:19].replace('T', ' ') + table.add_row(tag, f"{size_mb} MB", created) + + console.print(table) + console.print() + console.print("๐Ÿ’ก [dim]Use 'python -m dockybot clean' to remove cached images[/dim]") + + def clean_cached_images(self, platform: str = None, force: bool = False) -> None: + """Clean DockyBot cached images.""" + if platform: + image_name = f"dockybot/{platform}:latest" + if not self._image_exists(image_name): + console.print(f"๐Ÿ“ญ [yellow]No cached image found for {platform}[/yellow]") + return + + if not force: + import typer + confirm = typer.confirm(f"Remove cached image for {platform}?") + if not confirm: + console.print("โŒ [dim]Cancelled[/dim]") + return + + try: + self.client.images.remove(image_name, force=True) + console.print(f"๐Ÿ—‘๏ธ [green]Removed cached image: {image_name}[/green]") + except Exception as e: + console.print(f"[red]Error removing image: {e}[/red]") + else: + # Clean all dockybot images + images = self.client.images.list() + dockybot_images = [] + for img in images: + for tag in img.tags: + if 'dockybot/' in tag: + dockybot_images.append(tag) + break + + if not dockybot_images: + console.print("๐Ÿ“ญ [dim]No cached images to clean[/dim]") + return + + if not force: + console.print(f"Found {len(dockybot_images)} cached images:") + for img in dockybot_images: + console.print(f" - {img}") + console.print() + + import typer + confirm = typer.confirm("Remove all DockyBot cached images?") + if not confirm: + console.print("โŒ [dim]Cancelled[/dim]") + return + + removed_count = 0 + for img_tag in dockybot_images: + try: + self.client.images.remove(img_tag, force=True) + console.print(f"๐Ÿ—‘๏ธ [green]Removed: {img_tag}[/green]") + removed_count += 1 + except Exception as e: + console.print(f"[red]Error removing {img_tag}: {e}[/red]") + + console.print(f"\nโœ… [green]Cleaned {removed_count} cached images[/green]") \ No newline at end of file diff --git a/dockybot/platforms.py b/dockybot/platforms.py new file mode 100644 index 00000000..d9136451 --- /dev/null +++ b/dockybot/platforms.py @@ -0,0 +1,47 @@ +""" +Platform definitions and test scripts for DockyBot. +""" + +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent / "scripts" + +# Supported platforms +PLATFORMS = { + 'ubuntu': { + 'name': 'Ubuntu 22.04', + 'script': 'ubuntu.sh', + 'description': 'Standard Ubuntu with apt package manager' + }, + 'alpine': { + 'name': 'Alpine Linux 3.18', + 'script': 'alpine.sh', + 'description': 'Lightweight Alpine with apk package manager' + }, + 'centos': { + 'name': 'CentOS 7', + 'script': 'centos.sh', + 'description': 'Enterprise CentOS with yum package manager' + }, + 'debian': { + 'name': 'Debian 11', + 'script': 'debian.sh', + 'description': 'Debian stable with apt package manager' + } +} + +def get_platform_script(platform_name: str) -> str: + """Get the test script content for a platform.""" + if platform_name not in PLATFORMS: + raise ValueError(f"Unknown platform: {platform_name}. Available: {list(PLATFORMS.keys())}") + + script_file = SCRIPT_DIR / PLATFORMS[platform_name]['script'] + + if not script_file.exists(): + raise FileNotFoundError(f"Script not found: {script_file}") + + return script_file.read_text() + +def list_platforms() -> dict: + """List all available platforms.""" + return PLATFORMS \ No newline at end of file diff --git a/dockybot/scripts/alpine.sh b/dockybot/scripts/alpine.sh new file mode 100644 index 00000000..f4346296 --- /dev/null +++ b/dockybot/scripts/alpine.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -euo pipefail + +echo "Starting Alpine Linux test pipeline..." + +# Install system dependencies +apk update +apk add --no-cache python3 py3-pip python3-dev cmake make gcc g++ libc-dev curl wget gnupg unixodbc-dev + +echo "Setting up Python environment..." +python3 -m venv /opt/venv +source /opt/venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +echo "Building C++ extensions..." +cd mssql_python/pybind +chmod +x build.sh +./build.sh + +echo "Running tests..." +cd /workspace +source /opt/venv/bin/activate +python -m pytest tests/ -v + +echo "Alpine test pipeline completed successfully!" \ No newline at end of file diff --git a/dockybot/scripts/centos.sh b/dockybot/scripts/centos.sh new file mode 100644 index 00000000..1071eb32 --- /dev/null +++ b/dockybot/scripts/centos.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +echo "Starting CentOS 7 test pipeline..." + +# Install system dependencies +yum update -y +yum groupinstall -y "Development Tools" +yum install -y python3 python3-pip python3-devel cmake curl wget unixodbc-devel + +echo "Setting up Python environment..." +python3 -m venv /opt/venv +source /opt/venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +echo "Building C++ extensions..." +cd mssql_python/pybind +chmod +x build.sh +./build.sh + +echo "Running tests..." +cd /workspace +source /opt/venv/bin/activate +python -m pytest tests/ -v + +echo "CentOS test pipeline completed successfully!" \ No newline at end of file diff --git a/dockybot/scripts/debian.sh b/dockybot/scripts/debian.sh new file mode 100644 index 00000000..3a731acf --- /dev/null +++ b/dockybot/scripts/debian.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +echo "Starting Debian 11 test pipeline..." + +# Install system dependencies +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y python3 python3-pip python3-venv python3-dev cmake build-essential curl wget gnupg unixodbc-dev + +echo "Setting up Python environment..." +python3 -m venv /opt/venv +source /opt/venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +echo "Building C++ extensions..." +cd mssql_python/pybind +chmod +x build.sh +./build.sh + +echo "Running tests..." +cd /workspace +source /opt/venv/bin/activate +python -m pytest tests/ -v + +echo "Debian test pipeline completed successfully!" \ No newline at end of file diff --git a/dockybot/scripts/ubuntu.sh b/dockybot/scripts/ubuntu.sh new file mode 100644 index 00000000..730d145d --- /dev/null +++ b/dockybot/scripts/ubuntu.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail + +echo "Starting Ubuntu 22.04 test pipeline..." + +# Install system dependencies +export DEBIAN_FRONTEND=noninteractive +export TZ=UTC +ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +apt-get update && apt-get install -y python3 python3-pip python3-venv python3-full cmake curl wget gnupg software-properties-common build-essential python3-dev pybind11-dev + +echo "Installing Microsoft ODBC Driver..." +curl -sSL -O https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb +dpkg -i packages-microsoft-prod.deb || true +rm packages-microsoft-prod.deb +apt-get update +ACCEPT_EULA=Y apt-get install -y msodbcsql18 +ACCEPT_EULA=Y apt-get install -y mssql-tools18 +apt-get install -y unixodbc-dev + +echo "Setting up Python environment..." +python3 -m venv /opt/venv +source /opt/venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +echo "Verifying database connection setup..." +if [ -z "${DB_CONNECTION_STRING:-}" ]; then + echo "โŒ Warning: DB_CONNECTION_STRING environment variable is not set!" + exit 1 +else + echo "โœ… DB_CONNECTION_STRING is configured" + # Print first part of connection string for verification (without password) + echo "Connection target: $(echo "$DB_CONNECTION_STRING" | grep -o 'Server=[^;]*')" +fi + +echo "Building C++ extensions..." +cd mssql_python/pybind +chmod +x build.sh +./build.sh + +echo "Running tests..." +cd /workspace +source /opt/venv/bin/activate +python -m pytest tests/ -v + +echo "Ubuntu test pipeline completed successfully!" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5abf13dc..bb6d10d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,8 @@ pybind11 coverage unittest-xml-reporting setuptools -psutil \ No newline at end of file +psutil +# DockyBot dependencies +typer[all]>=0.9.0 +docker>=6.0.0 +rich>=13.0.0 \ No newline at end of file