Source code for coiled.cli.env

from typing import Optional

import click
from dask import config
from rich.console import Console
from rich.table import Table

from coiled.exceptions import BuildError
from coiled.types import ArchitectureTypesEnum
from coiled.v2.core import setup_logging

from ..core import Cloud, create_software_environment, delete_software_environment
from ..utils import COILED_SERVER
from .utils import CONTEXT_SETTINGS

console = Console()


@click.group(context_settings=CONTEXT_SETTINGS)
def env():
    """Commands for managing Coiled software environments"""
    setup_logging()


@env.command(context_settings=CONTEXT_SETTINGS)
@click.option("-n", "--name", help="Name of software environment, it must be lowercase.")
@click.option("--container", default=None, help="Base docker image to use.")
@click.option(
    "--ignore-container-entrypoint",
    default=False,
    help="Ignore the ENTRYPOINT when using specified container.",
    is_flag=True,
)
@click.option(
    "--conda",
    default=None,
    help="Conda environment file.",
    type=click.Path(exists=True),
)
@click.option("--pip", default=None, help="Pip requirements file.", type=click.Path(exists=True))
@click.option(
    "--force-rebuild",
    default=False,
    help="Skip checks for an existing software environment build.",
    is_flag=True,
)
@click.option(
    "--account",
    "--workspace",
    default=None,
    type=str,
    help="Workspace to use for creating this software environment."
    " Note: --account is deprecated, please use --workspace instead.",
)
@click.option(
    "--gpu-enabled",
    is_flag=True,
    show_default=True,
    default=False,
    help="Set CUDA virtual package for Conda",
)
@click.option(
    "--architecture",
    type=click.Choice([e.value for e in ArchitectureTypesEnum]),
    default=ArchitectureTypesEnum.X86_64.value,
    show_default=True,
    help="CPU architecture to use for the software environment",
)
@click.option(
    "--region-name",
    default=None,
    type=str,
    help="AWS or GCP region to use for storing this software environment.",
)
@click.option(
    "--include-local-code",
    default=False,
    is_flag=True,
    help="Include local code in the software environment build. "
    "This includes editable installs and importable python files.",
)
@click.option(
    "-i",
    "--ignore-local-package",
    multiple=True,
    help="Ignore a local package in the software environment build."
    " Only applies to packages included by the include-local-code option."
    " Specify multiple times for multiple packages."
    " Example: -i coiled -i pytorch",
)
@click.option(
    "--disable-uv-installer",
    default=False,
    is_flag=True,
    help="Do not use uv to install PyPI packages when building this environment.",
)
def create(
    name,
    container,
    ignore_container_entrypoint,
    conda,
    pip,
    force_rebuild,
    account,
    gpu_enabled,
    architecture,
    region_name,
    include_local_code,
    disable_uv_installer,
    ignore_local_package,
):
    """Create a Coiled software environment"""
    try:
        create_software_environment(
            name=name,
            container=container,
            conda=conda,
            pip=pip,
            force_rebuild=force_rebuild,
            account=account,
            gpu_enabled=gpu_enabled,
            architecture=architecture,
            region_name=region_name,
            include_local_code=include_local_code,
            ignore_local_packages=ignore_local_package,
            use_uv_installer=not disable_uv_installer,
            use_entrypoint=not ignore_container_entrypoint,
        )
    except BuildError as e:
        raise click.ClickException(f"{e}") from e


@env.command(context_settings=CONTEXT_SETTINGS)
@click.argument("name")
@click.option(
    "--workspace", default=None, required=False, help="Coiled workspace (uses default workspace if not specified)."
)
def delete(name: str, workspace: Optional[str]):
    """Delete a Coiled software environment"""
    delete_software_environment(name, workspace=workspace)


@env.command(context_settings=CONTEXT_SETTINGS)
@click.option(
    "--workspace", default=None, required=False, help="Coiled workspace (uses default workspace if not specified)."
)
def list(workspace: str):
    """List the Coiled software environments in a workspace"""
    with Cloud(workspace=workspace) as cloud:
        environments = cloud.list_software_environments(
            workspace,
        )
        table = Table(title="Software Environments")
        table.add_column("Name", style="cyan")
        table.add_column("Updated", style="magenta")
        table.add_column("Link", style="magenta")
        table.add_column("Status", style="green")
        server = config.get("coiled.server", COILED_SERVER).replace("8000", "5173")
        account = workspace or config.get("coiled.workspace", config.get("coiled.account"))
        for env_name, env_details in environments.items():
            latest_build = env_details["latest_spec"].get("latest_build")
            if latest_build:
                build_status = latest_build["state"] if latest_build["state"] != "error" else "[red]error[/red]"
                env_url = f"{server}/software/alias/{env_details['id']}/build/{latest_build['id']}?account={account}"
            else:
                build_status = "n/a"
                env_url = f"{server}/software/alias/{env_details['id']}?account={account}"
            table.add_row(
                env_name,
                env_details["updated"],
                env_url,
                build_status,
            )
        console.print(table)


[docs] @env.command( context_settings=CONTEXT_SETTINGS, help="View the details of a Coiled software environment", ) @click.argument("name") def inspect(name: str): """View the details of a Coiled software environment Parameters ---------- name Identifier of the software environment to use, in the format (<account>/)<name>. If the software environment is owned by the same account as that passed into "account", the (<account>/) prefix is optional. For example, suppose your account is "wondercorp", but your friends at "friendlycorp" have an environment named "xgboost" that you want to use; you can specify this with "friendlycorp/xgboost". If you simply entered "xgboost", this is shorthand for "wondercorp/xgboost". The "name" portion of (<account>/)<name> can only contain ASCII letters, hyphens and underscores. Examples -------- >>> import coiled >>> coiled.inspect("coiled/default") """ with Cloud() as cloud: results = cloud.get_software_info(name) console.print(results)