diff --git a/workspace/sprint_1_2/CODEBASE/deps/implementation/backend_deploy/gitea_modal_build.py b/workspace/sprint_1_2/CODEBASE/deps/implementation/backend_deploy/gitea_modal_build.py index 7d13e0e..e0fc232 100644 --- a/workspace/sprint_1_2/CODEBASE/deps/implementation/backend_deploy/gitea_modal_build.py +++ b/workspace/sprint_1_2/CODEBASE/deps/implementation/backend_deploy/gitea_modal_build.py @@ -22,40 +22,36 @@ import json import os from pathlib import Path import subprocess + import modal # ============================================================================= # Configuration # ============================================================================= + +APP_NAME = "msk-lumina-backend-builder" + +# Default registry/image settings DEFAULT_REGISTRY = "public.ecr.aws/i9a4e3f6/msk-cv-inference-server" DEFAULT_IMAGE_NAME = "msk-lumina-backend" DEFAULT_TAG = "latest" -APP_NAME = "msk-lumina-backend-builder" + +# Modal secret for ECR credentials +ECR_SECRET_NAME = "ecr-credentials" # ============================================================================= -# Modal Image Definition +# Modal Image Definition - Kaniko-based builder (no Docker daemon required) # ============================================================================= -# 1. Start from Kaniko to extract the binary, and map it to a local folder via an App build -kaniko_image = ( - modal.Image.from_registry("gcr.io/kaniko-project/executor:debug") - .add_local_dir(os.getcwd(), remote_path="/workspace") -) -# 2. Build your final builder image on a clean Debian system that HAS apt-get +# We bake the project source into the image at /workspace so Kaniko can access +# the build context and Dockerfile at runtime. +# The Dockerfile path is resolved at runtime, not at image build time. builder_image = ( modal.Image.debian_slim() .apt_install("curl", "ca-certificates") - .pip_install("boto3") - - # Use standard dockerfile_commands to copy the binary directly from the Kaniko base image layer - .dockerfile_commands( - [ - "COPY --from=gcr.io/kaniko-project/executor:debug /kaniko/executor /usr/local/bin/kaniko" - ] - ) - .add_local_dir( - os.getcwd(), - remote_path="/workspace", + .run_commands( + "curl -sSL https://github.com/chainguard-forks/kaniko/releases/latest/download/kaniko-linux-amd64 -o /usr/local/bin/kaniko", + "chmod +x /usr/local/bin/kaniko", ) ) @@ -64,11 +60,15 @@ app = modal.App(APP_NAME, image=builder_image) # ============================================================================= # Build and Push Function (Kaniko) # ============================================================================= + @app.function( - timeout=900, + timeout=900, # 15 min for heavy ML deps (torch, transformers, etc.) cpu=4, - memory=8192, - secrets=[modal.Secret.from_name("aws-secrets")], + memory=8192, # 8GB RAM - plenty for wheel building + secrets=[ + modal.Secret.from_name("aws-secrets"), # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + modal.Secret.from_name(ECR_SECRET_NAME), # ECR_USERNAME, ECR_PASSWORD + ], ) def build_and_push( registry: str = DEFAULT_REGISTRY, @@ -77,71 +77,73 @@ def build_and_push( push: bool = True, platform: str = "linux/amd64", ) -> dict: - import boto3 + """ + Build the backend Docker image with Kaniko and push to registry. + + Args: + registry: Container registry hostname (e.g., public.ecr.aws/vkist-project) + image_name: Image name (e.g., vkist-backend) + tag: Image tag (e.g., v1.2.3, latest, git-sha) + push: Whether to push to registry + platform: Target platform (linux/amd64, linux/arm64) + + Returns: + Dict with image reference and build status + """ + # Resolve paths at runtime inside Modal. + # The project files are baked into /workspace by the Modal image definition. + workspace = Path("/workspace") + project_root = workspace if (workspace / "backend").exists() else workspace + dockerfile_path = workspace / "deps" / "implementation" / "backend_deploy" / "Dockerfile" + + print(f"--- Runtime Path Verification ---") + print(f"Workspace: {workspace} (Exists: {workspace.exists()})") + print(f"PROJECT_ROOT: {project_root} (Exists: {project_root.exists()})") + print(f"DOCKERFILE_PATH: {dockerfile_path} (Exists: {dockerfile_path.exists()})") + print(f"----------------------------------------") + full_image = f"{registry}/{image_name}:{tag}" print(f"Building {full_image} for {platform}") - # Explicitly use the inside-container paths - workspace_root = Path("/workspace") - # Adjust this path if your Dockerfile lives in a specific subdirectory inside your workspace - dockerfile_path = workspace_root / "Dockerfile" - # Prepare docker config for Kaniko if pushing to ECR docker_config_dir = None if push and ("ecr.aws" in registry or "ecr." in registry): - print(f"Configuring ECR authentication...") - region = os.getenv("AWS_REGION", "us-east-1") + print(f"Configuring ECR authentication from Modal secret '{ECR_SECRET_NAME}'...") - ecr = boto3.client("ecr", region_name=region) - auth = ecr.get_authorization_token() - token = auth["authorizationData"][0]["authorizationToken"] - username, password = base64.b64decode(token).decode().split(":") + ecr_username = os.getenv("ECR_USERNAME") + ecr_password = os.getenv("ECR_PASSWORD") + + if not ecr_username or not ecr_password: + raise RuntimeError( + f"ECR_USERNAME and ECR_PASSWORD must be set in Modal secret '{ECR_SECRET_NAME}'" + ) config = { "auths": { registry: { - "username": username, - "password": password, + "username": ecr_username, + "password": ecr_password, } } } - docker_config_dir = workspace_root / ".kaniko" - docker_config_dir.mkdir(exist_ok=True) + docker_config_dir = project_root / ".kaniko" + docker_config_dir.mkdir(parents=True, exist_ok=True) (docker_config_dir / "config.json").write_text(json.dumps(config)) - print(f"ECR auth configured for {registry}") - - # # --- INSPECT MODAL WORKSPACE DIRECTORIES HERE --- Success - # print("\n=== DEBUG: Inspecting Modal Container Filesystem ===") - # try: - # # 1. Print the contents of the /workspace root directory - # print(f"Contents of {workspace_root}:") - # workspace_files = subprocess.run(["ls", "-la", str(workspace_root)], capture_output=True, text=True) - # print(workspace_files.stdout) - - # # 2. Verify exactly where the copied Kaniko binary lives - # print("Checking for Kaniko binary location:") - # # kaniko_check = subprocess.run(["ls", "-la", "/usr/local/bin/kaniko"], capture_output=True, text=True) - # # print(kaniko_check.stdout if kaniko_check.returncode == 0 else "❌ Kaniko not found in /usr/local/bin/kaniko\n") - - # # except Exception as e: - # # print(f"Failed to run inspection: {e}") - # # print("==================================================\n") + print(f"ECR auth configured for {registry}") # Build with Kaniko kaniko_cmd = [ - "/usr/local/bin/kaniko", # Update this line to match where it was copied - "--context", str(workspace_root), + "/usr/local/bin/kaniko", + "--context", str(project_root), "--dockerfile", str(dockerfile_path), "--destination", full_image, - "--custom-platform", platform, + "--platform", platform, "--verbosity", "info", ] - # Configure the environment variables to include DOCKER_CONFIG if auth exists - current_env = os.environ.copy() if docker_config_dir: - current_env["DOCKER_CONFIG"] = str(docker_config_dir) + kaniko_cmd.extend(["--docker-config", str(docker_config_dir)]) print(f"Running Kaniko: {' '.join(kaniko_cmd)}") result = subprocess.run(kaniko_cmd, capture_output=True, text=True)