forked from innovacion/Mayacontigo
ic
This commit is contained in:
290
.mise/tasks/container/push.py
Executable file
290
.mise/tasks/container/push.py
Executable 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
337
.mise/tasks/container/start.py
Executable 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)
|
||||
Reference in New Issue
Block a user