403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.216
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/repo/builder/backend/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/repo/builder/backend//run_build.py
#!/usr/bin/env python3
"""
Build Runner CLI

Execute build configurations from the command line.

Usage:
    python run_build.py [options]

Options:
    --config <path>     Path to JSON build config file
    --dry-run           Validate steps without executing
    --verbose           Enable verbose output

Examples:
    # Run the example build config
    python run_build.py --config configs/example.json

    # Dry run to validate
    python run_build.py --config configs/example.json --dry-run

    # Run with inline config (uses default example)
    python run_build.py
"""

import argparse
import asyncio
import json
import logging
import os
import re
import sys
import time
from pathlib import Path

from dotenv import load_dotenv

# Add app to path
sys.path.insert(0, str(Path(__file__).parent))

from app.types import BuildConfig, BuildStep, StepType
from app.services.builder_service import BuilderService

# Directory containing config files
CONFIGS_DIR = Path(__file__).parent / 'configs'

# Load environment variables from .env
SCRIPTS_ENV_PATH = Path(__file__).parent / '.env'
if SCRIPTS_ENV_PATH.exists():
    load_dotenv(SCRIPTS_ENV_PATH)


def substitute_env_vars(value: str) -> str:
    """Substitute ${VAR_NAME} placeholders with environment variable values."""
    if not isinstance(value, str):
        return value

    def replace_var(match):
        var_name = match.group(1)
        env_value = os.getenv(var_name, '')
        if not env_value:
            logging.warning(f"Environment variable {var_name} not set")
        return env_value

    # Match ${VAR_NAME} pattern
    return re.sub(r'\$\{(\w+)\}', replace_var, value)


def substitute_config_vars(data: dict) -> dict:
    """Recursively substitute environment variables in config data."""
    if isinstance(data, dict):
        return {k: substitute_config_vars(v) for k, v in data.items()}
    elif isinstance(data, list):
        return [substitute_config_vars(item) for item in data]
    elif isinstance(data, str):
        return substitute_env_vars(data)
    else:
        return data


def list_configs():
    """List all available build configurations."""
    if not CONFIGS_DIR.exists():
        print(f"Config directory not found: {CONFIGS_DIR}")
        return

    configs = sorted(CONFIGS_DIR.glob('*.json'))
    if not configs:
        print("No configuration files found.")
        return

    print("\nAvailable build configurations:")
    print("=" * 60)

    for config_path in configs:
        try:
            with open(config_path, 'r') as f:
                data = json.load(f)
            name = data.get('name', config_path.stem)
            description = data.get('description', 'No description')
            steps = len(data.get('steps', []))
            print(f"\n  {config_path.name}")
            print(f"    Name:  {name}")
            print(f"    Desc:  {description}")
            print(f"    Steps: {steps}")
        except Exception as e:
            print(f"\n  {config_path.name}")
            print(f"    Error reading config: {e}")

    print("\n" + "=" * 60)
    print("\nUsage: python run_build.py -c configs/<name>.json")
    print()


def setup_logging(verbose: bool = False):
    """Configure logging for CLI output."""
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format='%(levelname)s %(asctime)s %(message)s',
        datefmt='%H:%M:%S'
    )


def load_config_from_file(config_path: str) -> BuildConfig:
    """Load build configuration from a JSON file with env variable substitution."""
    with open(config_path, 'r') as f:
        data = json.load(f)
    # Substitute ${VAR_NAME} placeholders with env values
    data = substitute_config_vars(data)
    return BuildConfig(**data)


def create_example_config() -> BuildConfig:
    """Create an example build configuration for testing."""
    return BuildConfig(
        name="Example Build",
        description="A sample build configuration demonstrating various step types",
        steps=[
            BuildStep(
                name="Check Python version",
                step_type=StepType.SHELL,
                command="python --version",
                timeout=30
            ),
            BuildStep(
                name="List current directory",
                step_type=StepType.SHELL,
                command="ls -la",
                timeout=30
            ),
            BuildStep(
                name="Show disk usage",
                step_type=StepType.SHELL,
                command="df -h",
                timeout=30
            ),
            BuildStep(
                name="Wait 2 seconds",
                step_type=StepType.WAIT,
                timeout=2
            ),
            BuildStep(
                name="Show date",
                step_type=StepType.SHELL,
                command="date",
                timeout=30
            ),
        ],
        stop_on_failure=True
    )


async def run_build_cli(config: BuildConfig, dry_run: bool = False):
    """Run a build and print progress to console."""
    builder = BuilderService()

    print(f"\n{'='*60}")
    print(f"BUILD: {config.name}")
    if config.description:
        print(f"       {config.description}")
    print(f"STEPS: {len(config.steps)}")
    if dry_run:
        print("MODE:  DRY RUN (validation only)")
    print(f"{'='*60}\n")

    start_time = time.time()

    # Progress callback for console output
    async def console_progress(event):
        status = event.status.upper()
        progress = f"[{event.progress:3d}%]"

        if event.status == 'starting':
            print(f"{progress} {event.message}")
        elif event.status == 'running_step':
            print(f"\n{progress} {event.message}")
        elif event.status == 'step_complete':
            if event.step_result:
                result = event.step_result
                status_icon = "OK" if result.status.value == 'success' else "FAIL"
                duration = f"({result.duration:.2f}s)"
                print(f"       [{status_icon}] {result.step_name} {duration}")
                if result.output and len(result.output) < 200:
                    # Print short outputs
                    for line in result.output.split('\n')[:3]:
                        print(f"           {line}")
                if result.error:
                    print(f"           ERROR: {result.error}")

    result = await builder.run_build(
        config=config,
        dry_run=dry_run,
        progress_callback=console_progress
    )

    # Print summary
    elapsed = time.time() - start_time
    print(f"\n{'='*60}")
    print(f"BUILD {'SUCCEEDED' if result.success else 'FAILED'}")
    print(f"{'='*60}")
    print(f"Build ID:        {result.build_id}")
    print(f"Total Steps:     {result.total_steps}")
    print(f"Completed:       {result.completed_steps}")
    print(f"Failed:          {result.failed_steps}")
    print(f"Skipped:         {result.skipped_steps}")
    print(f"Duration:        {result.total_duration:.2f}s")

    if result.error:
        print(f"\nError: {result.error}")

    print()

    return result.success


def main():
    parser = argparse.ArgumentParser(
        description="Build Runner CLI - Execute build configurations",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python run_build.py --list                     List available configs
  python run_build.py -c configs/example.json    Run example build
  python run_build.py -c configs/example.json -n Dry-run (validate only)
  python run_build.py -c configs/deploy-to-server.json  Full deployment

  # Create a new site with parameters:
  python run_build.py -c configs/build-site.json \\
    --slug mysite \\
    --wp-dir mysite.ai \\
    --wp-host http://mysite.turmansolutions.test \\
    --cb-host chat-mysite.turmansolutions.test

  # Create SSL certificate:
  python run_build.py -c configs/create-ssl-cert.json \\
    --wp-domain mysite.turmansolutions.ai \\
    --cb-domain chat-mysite.turmansolutions.ai

Common configs:
  deploy-to-server.json  - Build and deploy to production server
  build-angular.json     - Build Angular apps only (no deploy)
  db-backup.json         - Run database backups
  build-site.json        - Create a new site configuration
  create-ssl-cert.json   - Issue and install SSL certificate
  example.json           - Simple test configuration
        """
    )
    parser.add_argument(
        '--config', '-c',
        type=str,
        metavar='FILE',
        help='Path to JSON build config file (e.g., configs/deploy-to-server.json)'
    )
    parser.add_argument(
        '--list', '-l',
        action='store_true',
        help='List all available build configurations'
    )
    parser.add_argument(
        '--dry-run', '-n',
        action='store_true',
        help='Validate steps without executing (safe to run)'
    )
    parser.add_argument(
        '--verbose', '-v',
        action='store_true',
        help='Enable verbose/debug output'
    )

    # Site creation parameters (optional)
    parser.add_argument(
        '--slug',
        type=str,
        help='Site slug for build-site config'
    )
    parser.add_argument(
        '--wp-dir',
        type=str,
        help='WordPress directory for build-site config'
    )
    parser.add_argument(
        '--wp-host',
        type=str,
        help='WordPress host URL for build-site config'
    )
    parser.add_argument(
        '--cb-host',
        type=str,
        help='Chatbot host for build-site config'
    )
    parser.add_argument(
        '--wp-domain',
        type=str,
        help='WordPress domain for SSL cert config (e.g., mysite.turmansolutions.ai)'
    )
    parser.add_argument(
        '--cb-domain',
        type=str,
        help='Chatbot domain for SSL cert config (e.g., chat-mysite.turmansolutions.ai)'
    )
    parser.add_argument(
        '--site-name',
        type=str,
        help='Site name for WordPress installation (e.g., My New Site)'
    )
    parser.add_argument(
        '--admin-email',
        type=str,
        help='Admin email for WordPress installation'
    )

    args = parser.parse_args()

    # Handle --list first
    if args.list:
        list_configs()
        sys.exit(0)

    setup_logging(args.verbose)

    # Inject CLI parameters as environment variables for use in configs
    if args.slug:
        os.environ['PARAM_SLUG'] = args.slug
        os.environ['PARAM_DB_SLUG'] = args.slug.replace('-', '_')
    if args.wp_dir:
        os.environ['PARAM_WP_DIR'] = args.wp_dir
    if args.wp_host:
        os.environ['PARAM_WP_HOST'] = args.wp_host
    if args.cb_host:
        os.environ['PARAM_CB_HOST'] = args.cb_host
    if args.wp_domain:
        os.environ['PARAM_WP_DOMAIN'] = args.wp_domain
    if args.cb_domain:
        os.environ['PARAM_CB_DOMAIN'] = args.cb_domain
    if args.site_name:
        os.environ['PARAM_SITE_NAME'] = args.site_name
    if args.admin_email:
        os.environ['PARAM_ADMIN_EMAIL'] = args.admin_email

    # Load or create config
    if args.config:
        config_path = Path(args.config)
        if not config_path.exists():
            print(f"Error: Config file not found: {args.config}")
            sys.exit(1)
        config = load_config_from_file(args.config)
        print(f"Loaded config from: {args.config}")
    else:
        config = create_example_config()
        print("Using example build config (no --config specified)")

    # Run the build
    try:
        success = asyncio.run(run_build_cli(config, dry_run=args.dry_run))
        sys.exit(0 if success else 1)
    except KeyboardInterrupt:
        print("\nBuild interrupted by user")
        sys.exit(130)
    except Exception as e:
        logging.error(f"Build error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()

Youez - 2016 - github.com/yon3zu
LinuXploit