This commit is contained in:
Rogelio
2025-10-13 18:16:25 +00:00
parent 739f087cef
commit 325f1ef439
415 changed files with 46870 additions and 0 deletions

290
.mise/tasks/container/push.py Executable file
View File

@@ -0,0 +1,290 @@
#!/usr/bin/env -S uv run --script
#MISE silent=true
#MISE description="Push the container to Azure"
# /// script
# dependencies = ["rich"]
# ///
import argparse
import subprocess
import sys
from typing import Optional
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Prompt, Confirm
from rich.table import Table
console = Console()
# Azure Container Registry configuration
AZURE_REGISTRY = "iaservicecontainers.azurecr.io"
IMAGE_PREFIX = "mayacontigo/"
def run_docker_command(command: list[str], description: str) -> bool:
"""Run a docker command with rich progress indication."""
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
transient=True,
) as progress:
task = progress.add_task(description, total=None)
try:
subprocess.run(
command,
capture_output=True,
text=True,
check=True
)
progress.update(task, description=f"{description}")
return True
except subprocess.CalledProcessError as e:
progress.update(task, description=f"{description}")
console.print(f"[red]Error running command: {' '.join(command)}[/red]")
console.print(f"[red]Error output: {e.stderr}[/red]")
return False
except FileNotFoundError:
progress.update(task, description=f"{description}")
console.print("[red]Docker command not found. Please ensure Docker is installed and in your PATH.[/red]")
return False
def check_docker_image_exists(image_name: str) -> bool:
"""Check if a Docker image exists locally."""
try:
result = subprocess.run(
["docker", "images", "-q", image_name],
capture_output=True,
text=True,
check=True
)
return bool(result.stdout.strip())
except subprocess.CalledProcessError:
return False
def get_available_images() -> list[str]:
"""Get list of available Docker images filtered by prefix."""
try:
result = subprocess.run(
["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"],
capture_output=True,
text=True,
check=True
)
images = [line.strip() for line in result.stdout.split('\n') if line.strip()]
# Filter images to only include those with the specified prefix
filtered_images = [img for img in images if not img.startswith('<none>') and img.startswith(IMAGE_PREFIX)]
return filtered_images
except subprocess.CalledProcessError:
return []
def display_available_images(images: list[str], show_numbers: bool = False) -> None:
"""Display available Docker images in a nice table."""
if not images:
console.print(f"[yellow]No Docker images found locally with prefix '{IMAGE_PREFIX}'.[/yellow]")
return
table = Table(title=f"Available Docker Images (prefix: {IMAGE_PREFIX})")
if show_numbers:
table.add_column("No.", style="bold yellow", width=4)
table.add_column("Image", style="cyan")
table.add_column("Registry Status", style="magenta")
for i, image in enumerate(images[:10], 1): # Show first 10 images
registry_status = "🏷️ Ready to tag" if not image.startswith(AZURE_REGISTRY) else "🚀 Already tagged"
if show_numbers:
table.add_row(str(i), image, registry_status)
else:
table.add_row(image, registry_status)
if len(images) > 10:
remaining_text = f"[dim]and {len(images) - 10} more[/dim]"
if show_numbers:
table.add_row("...", "...", remaining_text)
else:
table.add_row("...", remaining_text)
console.print(table)
def interactive_mode() -> Optional[str]:
"""Run in interactive mode to select an image."""
console.print(Panel.fit(
"[bold blue]🐳 Docker Image Push to Azure[/bold blue]\n"
"[dim]Interactive mode - Let's select an image to push[/dim]",
border_style="blue"
))
# Get available images
console.print("\n[bold]Fetching available Docker images...[/bold]")
images = get_available_images()
if not images:
console.print(f"[red]No Docker images found with prefix '{IMAGE_PREFIX}'. Please build an image first.[/red]")
return None
display_available_images(images, show_numbers=True)
# Create choices with numbers
choices = [str(i) for i in range(1, len(images) + 1)]
console.print()
try:
choice = Prompt.ask(
"Select an image to push",
choices=choices,
default="1"
)
selected_image = images[int(choice) - 1]
console.print(f"[green]✅ Selected image: {selected_image}[/green]")
return selected_image
except (ValueError, IndexError, KeyboardInterrupt):
console.print("[yellow]Selection cancelled.[/yellow]")
return None
def push_image(image_name: str, force: bool = False) -> bool:
"""Push a Docker image to Azure Container Registry."""
# Validate image name
if not image_name:
console.print("[red]Error: Image name cannot be empty[/red]")
return False
# Check if image has the required prefix
if not image_name.startswith(IMAGE_PREFIX):
console.print(f"[red]Error: Image '{image_name}' does not have the required prefix '{IMAGE_PREFIX}'[/red]")
return False
# Check if image exists locally
if not check_docker_image_exists(image_name):
console.print(f"[red]Error: Image '{image_name}' not found locally[/red]")
console.print(f"[yellow]Tip: Run 'docker images' to see available images with prefix '{IMAGE_PREFIX}'[/yellow]")
return False
# Prepare Azure registry image name (remove the prefix since it will be added by the registry)
image_name_without_prefix = image_name[len(IMAGE_PREFIX):]
azure_image_name = f"{AZURE_REGISTRY}/{image_name_without_prefix}"
# Show what we're about to do
info_panel = Panel.fit(
f"[bold]Image:[/bold] {image_name}\n"
f"[bold]Target:[/bold] {azure_image_name}\n"
f"[bold]Registry:[/bold] {AZURE_REGISTRY}",
title="🚀 Push Configuration",
border_style="green"
)
console.print(info_panel)
# Confirm action unless force is specified
if not force:
if not Confirm.ask(f"\nProceed with pushing [cyan]{image_name}[/cyan] to Azure?"):
console.print("[yellow]Operation cancelled.[/yellow]")
return False
console.print("\n[bold]Starting push process...[/bold]")
# Tag the image
if not run_docker_command(
["docker", "tag", image_name, azure_image_name],
f"Tagging image as {azure_image_name}"
):
return False
# Push the image
if not run_docker_command(
["docker", "push", azure_image_name],
f"Pushing {azure_image_name} to registry"
):
return False
# Success message
success_panel = Panel.fit(
f"[bold green]✅ Successfully pushed![/bold green]\n"
f"[dim]Image: {azure_image_name}[/dim]",
title="🎉 Success",
border_style="green"
)
console.print(success_panel)
return True
def main():
parser = argparse.ArgumentParser(
description="Push Docker images to Azure Container Registry",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""
Examples:
python push.py {IMAGE_PREFIX}my-app:latest # Push specific image
python push.py --interactive # Interactive mode
python push.py {IMAGE_PREFIX}my-app:latest --force # Skip confirmation
python push.py --list # List available images
"""
)
parser.add_argument(
"image_name",
nargs="?",
help="Name of the Docker image to push"
)
parser.add_argument(
"-i", "--interactive",
action="store_true",
help="Run in interactive mode"
)
parser.add_argument(
"-f", "--force",
action="store_true",
help="Skip confirmation prompts"
)
parser.add_argument(
"--list",
action="store_true",
help="List available Docker images and exit"
)
args = parser.parse_args()
# Handle --list option
if args.list:
console.print("[bold]Available Docker Images:[/bold]")
images = get_available_images()
display_available_images(images)
return
# Determine image name
image_name = args.image_name
# Interactive mode or no image provided
if args.interactive or not image_name:
image_name = interactive_mode()
if not image_name:
sys.exit(1)
# Push the image
success = push_image(image_name, args.force)
sys.exit(0 if success else 1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
console.print("\n[yellow]Operation cancelled by user.[/yellow]")
sys.exit(1)
except Exception as e:
console.print(f"[red]Unexpected error: {e}[/red]")
sys.exit(1)

337
.mise/tasks/container/start.py Executable file
View File

@@ -0,0 +1,337 @@
#!/usr/bin/env -S uv run --script
#MISE silent=true
#MISE description="Run the container for a project"
# /// script
# dependencies = ["rich"]
# ///
import argparse
import json
import subprocess
import sys
from typing import Optional
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Prompt, Confirm
from rich.table import Table
console = Console()
# Configuration
VAULT_ADDRESS = "https://vault.ia-innovacion.work"
DOCKER_REGISTRY = "mayacontigo"
DEFAULT_PORT = 9000
CONTAINER_PORT = 80
def run_command(command: list[str], description: str, capture_output: bool = True) -> tuple[bool, str]:
"""Run a command with rich progress indication."""
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
transient=True,
) as progress:
task = progress.add_task(description, total=None)
try:
result = subprocess.run(
command,
capture_output=capture_output,
text=True,
check=True
)
progress.update(task, description=f"{description}")
return True, result.stdout.strip() if capture_output else ""
except subprocess.CalledProcessError as e:
progress.update(task, description=f"{description}")
error_msg = e.stderr if capture_output else str(e)
console.print(f"[red]Error running command: {' '.join(command)}[/red]")
if error_msg:
console.print(f"[red]Error output: {error_msg}[/red]")
return False, ""
except FileNotFoundError:
progress.update(task, description=f"{description}")
console.print(f"[red]Command not found: {command[0]}. Please ensure it's installed and in your PATH.[/red]")
return False, ""
def get_vault_token() -> Optional[str]:
"""Get vault token from Vault CLI."""
success, output = run_command(
["vault", "token", "lookup", "-format=json", f"-address={VAULT_ADDRESS}"],
"Fetching Vault token"
)
if not success:
return None
try:
data = json.loads(output)
return data.get("data", {}).get("id")
except json.JSONDecodeError:
console.print("[red]Failed to parse Vault token response[/red]")
return None
def check_docker_image_exists(app_name: str) -> bool:
"""Check if the Docker image exists locally or can be pulled."""
image_name = f"{DOCKER_REGISTRY}/{app_name}"
# First check if image exists locally
success, _ = run_command(
["docker", "images", "-q", image_name],
f"Checking if {image_name} exists locally"
)
if success:
return True
# If not local, try to pull it
console.print(f"[yellow]Image {image_name} not found locally. Attempting to pull...[/yellow]")
success, _ = run_command(
["docker", "pull", image_name],
f"Pulling {image_name}"
)
return success
def get_available_apps() -> list[str]:
"""Get list of available Docker images from the registry."""
try:
result = subprocess.run(
["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"],
capture_output=True,
text=True,
check=True
)
images = [line.strip() for line in result.stdout.split('\n') if line.strip()]
# Filter for banortegpt registry images
apps = []
for img in images:
if img.startswith(f"{DOCKER_REGISTRY}/"):
app_name = img.replace(f"{DOCKER_REGISTRY}/", "").split(":")[0]
if app_name not in apps:
apps.append(app_name)
return apps
except subprocess.CalledProcessError:
return []
def display_available_apps(apps: list[str]) -> None:
"""Display available apps in a nice table."""
if not apps:
console.print("[yellow]No apps found locally.[/yellow]")
console.print("[dim]Try pulling an image first or check your Docker registry connection.[/dim]")
return
table = Table(title="Available Apps")
table.add_column("App Name", style="cyan")
table.add_column("Image", style="magenta")
table.add_column("Status", style="green")
for app in apps:
image = f"{DOCKER_REGISTRY}/{app}"
status = "🚀 Ready to run"
table.add_row(app, image, status)
console.print(table)
def interactive_mode() -> Optional[str]:
"""Run in interactive mode to select an app."""
console.print(Panel.fit(
"[bold blue]🐳 Docker Container Starter[/bold blue]\n"
"[dim]Interactive mode - Let's select an app to run[/dim]",
border_style="blue"
))
# Get available apps
console.print("\n[bold]Fetching available apps...[/bold]")
apps = get_available_apps()
if not apps:
console.print("[red]No apps found. Please ensure you have Docker images available.[/red]")
return None
display_available_apps(apps)
# Prompt for app selection
console.print("\n[bold]Select an app to run:[/bold]")
app_name = Prompt.ask(
"Enter the app name (or press Enter to see suggestions)",
default="",
show_default=False
)
if not app_name:
# Show suggestions
console.print("\n[dim]Here are your available apps:[/dim]")
for i, app in enumerate(apps, 1):
console.print(f" {i}. [cyan]{app}[/cyan]")
choice = Prompt.ask(
"Enter app name or number from suggestions",
default="1"
)
try:
choice_num = int(choice)
if 1 <= choice_num <= len(apps):
app_name = apps[choice_num - 1]
else:
console.print("[red]Invalid choice.[/red]")
return None
except ValueError:
app_name = choice
return app_name
def start_container(app_name: str, port: int = DEFAULT_PORT, force: bool = False) -> bool:
"""Start a Docker container for the specified app."""
# Validate app name
if not app_name:
console.print("[red]Error: App name cannot be empty[/red]")
return False
# Check if image exists
if not check_docker_image_exists(app_name):
console.print(f"[red]Error: Image for app '{app_name}' not found[/red]")
return False
# Get vault token
console.print("\n[bold]Getting Vault token...[/bold]")
vault_token = get_vault_token()
if not vault_token:
console.print("[red]Failed to get Vault token[/red]")
return False
# Prepare container configuration
image_name = f"{DOCKER_REGISTRY}/{app_name}"
# Show what we're about to do
info_panel = Panel.fit(
f"[bold]App:[/bold] {app_name}\n"
f"[bold]Image:[/bold] {image_name}\n"
f"[bold]Port:[/bold] {port}:{CONTAINER_PORT}\n"
f"[bold]Vault:[/bold] {VAULT_ADDRESS}\n"
f"[bold]Token:[/bold] {'*' * 8}...{vault_token[-4:] if len(vault_token) > 8 else '****'}",
title="🚀 Container Configuration",
border_style="green"
)
console.print(info_panel)
# Confirm action unless force is specified
if not force:
if not Confirm.ask(f"\nStart container for [cyan]{app_name}[/cyan] on port {port}?"):
console.print("[yellow]Operation cancelled.[/yellow]")
return False
console.print("\n[bold]Starting container...[/bold]")
# Build docker run command
docker_cmd = [
"docker", "run",
"-p", f"{port}:{CONTAINER_PORT}",
"--rm",
"-it",
"-e", f"VAULT_TOKEN={vault_token}",
image_name
]
# Run the container (this will be interactive)
console.print(f"[green]Running: {' '.join(docker_cmd[:7])}... {image_name}[/green]")
console.print(f"[dim]Container will be available at: http://localhost:{port}[/dim]")
console.print("[dim]Press Ctrl+C to stop the container[/dim]\n")
try:
subprocess.run(docker_cmd, check=True)
return True
except subprocess.CalledProcessError as e:
console.print(f"[red]Container failed to start: {e}[/red]")
return False
except KeyboardInterrupt:
console.print("\n[yellow]Container stopped by user.[/yellow]")
return True
def main():
parser = argparse.ArgumentParser(
description="Start Docker containers for Banorte GPT apps",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python start.py my-app # Start specific app
python start.py --interactive # Interactive mode
python start.py my-app --port 8080 # Custom port
python start.py my-app --force # Skip confirmation
"""
)
parser.add_argument(
"app_name",
nargs="?",
help="Name of the app to start"
)
parser.add_argument(
"-i", "--interactive",
action="store_true",
help="Run in interactive mode"
)
parser.add_argument(
"-p", "--port",
type=int,
default=DEFAULT_PORT,
help=f"Port to bind to (default: {DEFAULT_PORT})"
)
parser.add_argument(
"-f", "--force",
action="store_true",
help="Skip confirmation prompts"
)
parser.add_argument(
"--list",
action="store_true",
help="List available apps and exit"
)
args = parser.parse_args()
# Handle --list option
if args.list:
console.print("[bold]Available Apps:[/bold]")
apps = get_available_apps()
display_available_apps(apps)
return
# Determine app name
app_name = args.app_name
# Interactive mode or no app provided
if args.interactive or not app_name:
app_name = interactive_mode()
if not app_name:
sys.exit(1)
# Start the container
success = start_container(app_name, args.port, args.force)
sys.exit(0 if success else 1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
console.print("\n[yellow]Operation cancelled by user.[/yellow]")
sys.exit(1)
except Exception as e:
console.print(f"[red]Unexpected error: {e}[/red]")
sys.exit(1)