#!/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('') 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)