import datetime
from pathlib import Path
from typing import Optional
from gigl.common.constants import (
    DOCKER_LATEST_BASE_CPU_IMAGE_NAME_WITH_TAG,
    DOCKER_LATEST_BASE_CUDA_IMAGE_NAME_WITH_TAG,
    DOCKER_LATEST_BASE_DATAFLOW_IMAGE_NAME_WITH_TAG,
)
from gigl.common.logger import Logger
from gigl.common.utils.os_utils import run_command_and_stream_stdout
[docs]
CUSTOMER_SRC_DOCKERFILE_PATH = (
    Path(__file__).parent.resolve() / "Dockerfile.customer_src"
).as_posix() 
[docs]
def build_and_push_customer_src_images(
    context_path: str,
    export_docker_artifact_registry: str,
    base_image_cuda: str = DOCKER_LATEST_BASE_CUDA_IMAGE_NAME_WITH_TAG,
    base_image_cpu: str = DOCKER_LATEST_BASE_CPU_IMAGE_NAME_WITH_TAG,
    base_image_dataflow: str = DOCKER_LATEST_BASE_DATAFLOW_IMAGE_NAME_WITH_TAG,
) -> tuple[str, str, str]:
    """
    Package user provided code located at context_path into docker images based on the base images provided.
    The images are pushed to the export_docker_artifact_registry.
    Args:
        context_path (str): Root directory that will be copied into the docker images.
        export_docker_artifact_registry (str): Docker artifact registry to push the images to.
        base_image_cuda (str): Base image to use for the CUDA image.
        base_image_cpu (str): Base image to use for the CPU image.
        base_image_dataflow (str): Base image to use for the Dataflow image.
    Returns:
        tuple[str, str, str]: The names of cuda, cpu, and dataflow images.
    """
    logger.info(
        f"Building and pushing customer src images to {export_docker_artifact_registry}"
    )
    logger.info(
        f"Using base images: {base_image_cuda}, {base_image_cpu}, {base_image_dataflow}"
    )
    logger.info(f"Using context path: {context_path}")
    tag = f"{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
    export_cuda_image_name = f"{export_docker_artifact_registry}/src-cuda:{tag}"
    export_cpu_image_name = f"{export_docker_artifact_registry}/src-cpu:{tag}"
    export_dataflow_image_name = (
        f"{export_docker_artifact_registry}/src-cpu-dataflow:{tag}"
    )
    logger.info(f"Building and pushing cuda image to {export_cuda_image_name}")
    build_and_push_image(
        base_image=base_image_cuda,
        image_name=export_cuda_image_name,
        dockerfile_path=CUSTOMER_SRC_DOCKERFILE_PATH,
        context_path=context_path,
    )
    logger.info(f"Building and pushing cpu image to {export_cpu_image_name}")
    build_and_push_image(
        base_image=base_image_cpu,
        image_name=export_cpu_image_name,
        dockerfile_path=CUSTOMER_SRC_DOCKERFILE_PATH,
        context_path=context_path,
    )
    logger.info(f"Building and pushing dataflow image to {export_dataflow_image_name}")
    build_and_push_image(
        base_image=base_image_dataflow,
        image_name=export_dataflow_image_name,
        dockerfile_path=CUSTOMER_SRC_DOCKERFILE_PATH,
        context_path=context_path,
    )
    logger.info(f"Done building and pushing customer src images")
    return export_cuda_image_name, export_cpu_image_name, export_dataflow_image_name 
[docs]
def build_and_push_image(
    base_image: Optional[str],
    image_name: str,
    dockerfile_path: str,
    context_path: str,
    multi_arch: bool = False,
) -> None:
    """
    Builds and pushes a Docker image.
    Args:
        base_image (Optional[str]): The base image to use for the build.
        image_name (str): The name of the Docker image to build and push.
        dockerfile_path (str): The path to the Dockerfile to use for the build.
        context_path (str): The path to the context to use for the build.
        multi_arch (bool): Whether to build a multi-architecture Docker image. Defaults to False.
    """
    if multi_arch:
        build_command = [
            "docker",
            "buildx",
            "build",
            "--platform",
            "linux/amd64,linux/arm64",
            "-f",
            str(dockerfile_path),
            "-t",
            image_name,
            "--push",
        ]
    else:
        build_command = [
            "docker",
            "build",
            "-f",
            str(dockerfile_path),
            "-t",
            image_name,
        ]
    if base_image:
        build_command.extend(["--build-arg", f"BASE_IMAGE={base_image}"])
    build_command.append(context_path)
    logger.info(f"Running command: {' '.join(build_command)}")
    returncode = run_command_and_stream_stdout(" ".join(build_command))
    if returncode != 0:
        logger.error(f"Command failed: {' '.join(build_command)}")
        raise RuntimeError(f"Docker build failed with exit code {returncode}")
    # Push image if it's not a multi-arch build (multi-arch images are pushed in the build step)
    if not multi_arch:
        push_command = ["docker", "push", image_name]
        returncode = run_command_and_stream_stdout(" ".join(push_command))
        if returncode != 0:
            logger.error(f"Command failed: {' '.join(push_command)}")
            raise RuntimeError(f"Docker push failed with exit code {returncode}")