diff --git a/src/flyte/cli/_delete.py b/src/flyte/cli/_delete.py index 9ecd71318..5a892abb9 100644 --- a/src/flyte/cli/_delete.py +++ b/src/flyte/cli/_delete.py @@ -71,22 +71,21 @@ def app(cfg: common.CLIConfig, name: str, project: str | None = None, domain: st default=False, help="Also delete the Docker volume used for persistent storage.", ) -def demo(volume: bool): +def devbox(volume: bool): """ - Stop and remove the local Flyte demo cluster container. + Stop and remove the local Flyte devbox cluster container. """ console = common.get_console() - result = subprocess.run(["docker", "stop", "flyte-demo"], capture_output=True, check=False) + result = subprocess.run(["docker", "stop", "flyte-devbox"], capture_output=True, check=False) if result.returncode == 0: - # The container is started with --rm, so wait for it to be fully removed - subprocess.run(["docker", "wait", "flyte-demo"], capture_output=True, check=False) - console.print("[green]Demo cluster stopped.[/green]") + subprocess.run(["docker", "wait", "flyte-devbox"], capture_output=True, check=False) + console.print("[green]Devbox cluster stopped.[/green]") else: - console.print("[yellow]Demo cluster is not running.[/yellow]") + console.print("[yellow]Devbox cluster is not running.[/yellow]") if volume: - result = subprocess.run(["docker", "volume", "rm", "flyte-demo"], capture_output=True, check=False) + result = subprocess.run(["docker", "volume", "rm", "flyte-devbox"], capture_output=True, check=False) if result.returncode == 0: - console.print("[green]Docker volume 'flyte-demo' deleted.[/green]") + console.print("[green]Docker volume 'flyte-devbox' deleted.[/green]") else: - console.print("[yellow]Docker volume 'flyte-demo' does not exist.[/yellow]") + console.print("[yellow]Docker volume 'flyte-devbox' does not exist.[/yellow]") diff --git a/src/flyte/cli/_demo.py b/src/flyte/cli/_devbox.py similarity index 85% rename from src/flyte/cli/_demo.py rename to src/flyte/cli/_devbox.py index 249e998aa..7bc369f93 100644 --- a/src/flyte/cli/_demo.py +++ b/src/flyte/cli/_devbox.py @@ -11,13 +11,13 @@ from rich.panel import Panel from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn -_CONTAINER_NAME = "flyte-demo" -_VOLUME_NAME = "flyte-demo" +_CONTAINER_NAME = "flyte-devbox" +_VOLUME_NAME = "flyte-devbox" _KUBE_DIR = Path( "/tmp/.kube" ) # This path is used to store k3s kubeconfig file, we later merge it with the default kubeconfig _KUBECONFIG_PATH = _KUBE_DIR / "kubeconfig" -_FLYTE_DEMO_CONFIG_DIR = Path.home() / ".flyte" / "demo" +_FLYTE_DEVBOX_CONFIG_DIR = Path.home() / ".flyte" / "devbox" _PORTS = ["6443:6443", "30000:30000", "30001:30001", "30002:30002", "30003:30003", "30080:30080"] _CONSOLE_READYZ_URL = "http://localhost:30080/readyz" @@ -81,7 +81,7 @@ def _run_container( is_dev_mode: bool, container_name: str, kube_dir: Path, - flyte_demo_config_dir: Path, + flyte_devbox_config_dir: Path, volume_name: str, ports: list[str], gpu: bool = False, @@ -103,7 +103,7 @@ def _run_container( "--volume", f"{kube_dir}:/.kube", "--volume", - f"{flyte_demo_config_dir}:/var/lib/flyte/config", + f"{flyte_devbox_config_dir}:/var/lib/flyte/config", "--volume", f"{volume_name}:/var/lib/flyte/storage", ] @@ -141,7 +141,7 @@ def _wait_for_kubeconfig(kubeconfig_path: Path, timeout: int = 60) -> None: time.sleep(1) -def _switch_k8s_context(context: str = "flyte-demo", namespace: str = "flyte") -> None: +def _switch_k8s_context(context: str = "flyte-devbox", namespace: str = "flyte") -> None: try: subprocess.run(["kubectl", "config", "use-context", context], check=True, capture_output=True, text=True) subprocess.run( @@ -211,33 +211,33 @@ def _merge_kubeconfig(kubeconfig_path: Path, container_name: str) -> None: console = Console() -def _wait_for_demo_ready(is_dev_mode: bool) -> None: +def _wait_for_devbox_ready(is_dev_mode: bool) -> None: if not is_dev_mode: _wait_for_console_ready(_CONSOLE_READYZ_URL) -def stop_demo() -> None: +def stop_devbox() -> None: if _container_is_paused(_CONTAINER_NAME): - console.print("[yellow]Demo cluster is already paused.[/yellow]") + console.print("[yellow]Devbox cluster is already paused.[/yellow]") return if not _container_is_running(_CONTAINER_NAME): - console.print("[yellow]Demo cluster is not running.[/yellow]") + console.print("[yellow]Devbox cluster is not running.[/yellow]") return subprocess.run(["docker", "pause", _CONTAINER_NAME], check=True, capture_output=True) - console.print("[green]Demo cluster stopped.[/green] Run [bold]flyte start demo[/bold] to resume.") + console.print("[green]Devbox cluster stopped.[/green] Run [bold]flyte start devbox[/bold] to resume.") -def launch_demo(image_name: str, is_dev_mode: bool, gpu: bool = False, log_format: str = "console") -> None: +def launch_devbox(image_name: str, is_dev_mode: bool, gpu: bool = False, log_format: str = "console") -> None: _ensure_volume(_VOLUME_NAME) if _container_is_paused(_CONTAINER_NAME): - console.print("[cyan]Resuming paused demo cluster...[/cyan]") + console.print("[cyan]Resuming paused devbox cluster...[/cyan]") subprocess.run(["docker", "unpause", _CONTAINER_NAME], check=True, capture_output=True) return if _container_is_running(_CONTAINER_NAME): - console.print("[yellow]Flyte demo cluster is already running.[/yellow]") - if not click.confirm("Do you want to delete the existing demo cluster and start a new one?"): + console.print("[yellow]Flyte devbox cluster is already running.[/yellow]") + if not click.confirm("Do you want to delete the existing devbox cluster and start a new one?"): return subprocess.run(["docker", "stop", _CONTAINER_NAME], check=True, capture_output=True) @@ -249,9 +249,9 @@ def launch_demo(image_name: str, is_dev_mode: bool, gpu: bool = False, log_forma steps = _STEPS_DEV if is_dev_mode else _STEPS if log_format == "json": - _launch_demo_plain(image_name, is_dev_mode, steps, gpu=gpu) + _launch_devbox_plain(image_name, is_dev_mode, steps, gpu=gpu) else: - _launch_demo_rich(image_name, is_dev_mode, steps, gpu=gpu) + _launch_devbox_rich(image_name, is_dev_mode, steps, gpu=gpu) def _run_step(step_id: str, image_name: str, is_dev_mode: bool, gpu: bool = False) -> None: @@ -259,7 +259,7 @@ def _run_step(step_id: str, image_name: str, is_dev_mode: bool, gpu: bool = Fals _pull_image(image_name) elif step_id == "start": _run_container( - image_name, is_dev_mode, _CONTAINER_NAME, _KUBE_DIR, _FLYTE_DEMO_CONFIG_DIR, _VOLUME_NAME, _PORTS, gpu=gpu + image_name, is_dev_mode, _CONTAINER_NAME, _KUBE_DIR, _FLYTE_DEVBOX_CONFIG_DIR, _VOLUME_NAME, _PORTS, gpu=gpu ) elif step_id == "kubeconfig": _wait_for_kubeconfig(_KUBECONFIG_PATH) @@ -268,10 +268,10 @@ def _run_step(step_id: str, image_name: str, is_dev_mode: bool, gpu: bool = Fals elif step_id == "context": _switch_k8s_context() elif step_id == "ready": - _wait_for_demo_ready(is_dev_mode) + _wait_for_devbox_ready(is_dev_mode) -def _launch_demo_plain(image_name: str, is_dev_mode: bool, steps: list[tuple[str, str]], gpu: bool = False) -> None: +def _launch_devbox_plain(image_name: str, is_dev_mode: bool, steps: list[tuple[str, str]], gpu: bool = False) -> None: for i, (description, step_id) in enumerate(steps, 1): click.echo(f"[{i}/{len(steps)}] {description}...") _run_step(step_id, image_name, is_dev_mode, gpu=gpu) @@ -281,12 +281,12 @@ def _launch_demo_plain(image_name: str, is_dev_mode: bool, steps: list[tuple[str if is_dev_mode: click.echo("Flyte dev cluster is running.") else: - click.echo("Flyte demo cluster is ready!") + click.echo("Flyte devbox cluster is ready!") click.echo(" UI: http://localhost:30080/v2") click.echo(" Image Registry: localhost:30000") -def _launch_demo_rich(image_name: str, is_dev_mode: bool, steps: list[tuple[str, str]], gpu: bool = False) -> None: +def _launch_devbox_rich(image_name: str, is_dev_mode: bool, steps: list[tuple[str, str]], gpu: bool = False) -> None: with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), @@ -295,7 +295,7 @@ def _launch_demo_rich(image_name: str, is_dev_mode: bool, steps: list[tuple[str, TimeElapsedColumn(), console=console, ) as progress: - overall = progress.add_task("[bold cyan]Starting Flyte demo cluster", total=len(steps)) + overall = progress.add_task("[bold cyan]Starting Flyte devbox cluster", total=len(steps)) for description, step_id in steps: progress.update(overall, description=f"[bold cyan]{description}") @@ -307,10 +307,10 @@ def _launch_demo_rich(image_name: str, is_dev_mode: bool, steps: list[tuple[str, else: console.print( Panel( - "[green bold]Flyte demo cluster is ready![/green bold]\n\n" + "[green bold]Flyte devbox cluster is ready![/green bold]\n\n" " 🚀 UI: [link=http://localhost:30080/v2]http://localhost:30080/v2[/link]\n" " 🐳 Image Registry: localhost:30000", - title="[bold]Flyte Demo[/bold]", + title="[bold]Flyte Devbox[/bold]", border_style="green", ) ) diff --git a/src/flyte/cli/_start.py b/src/flyte/cli/_start.py index c9f8e8072..9e110396d 100644 --- a/src/flyte/cli/_start.py +++ b/src/flyte/cli/_start.py @@ -25,38 +25,38 @@ def tui(): launch_tui_explore() -_DEFAULT_DEMO_IMAGE = "ghcr.io/flyteorg/flyte-demo:nightly" -_DEFAULT_DEMO_GPU_IMAGE = "ghcr.io/flyteorg/flyte-demo:gpu-latest" +_DEFAULT_DEVBOX_IMAGE = "ghcr.io/flyteorg/flyte-devbox:nightly" +_DEFAULT_DEVBOX_GPU_IMAGE = "ghcr.io/flyteorg/flyte-devbox:gpu-nightly" @start.command() @click.option( "--image", default=None, - show_default=f"{_DEFAULT_DEMO_IMAGE} ({_DEFAULT_DEMO_GPU_IMAGE} when --gpu)", - help="Docker image to use for the demo cluster.", + show_default=f"{_DEFAULT_DEVBOX_IMAGE} ({_DEFAULT_DEVBOX_GPU_IMAGE} when --gpu)", + help="Docker image to use for the devbox cluster.", ) @click.option( "--dev", is_flag=True, default=False, - help="Enable dev mode inside the demo cluster (sets FLYTE_DEV=True).", + help="Enable dev mode inside the devbox cluster (sets FLYTE_DEV=True).", ) @click.option( "--gpu", is_flag=True, default=False, - help="Pass host GPUs into the demo container (adds --gpus all to docker run). " + help="Pass host GPUs into the devbox container (adds --gpus all to docker run). " "Requires an NVIDIA-enabled host. Defaults --image to a GPU-capable image " "if --image is not explicitly set.", ) @click.pass_context -def demo(ctx: click.Context, image: str | None, dev: bool, gpu: bool): - """Start a local Flyte demo cluster.""" - from flyte.cli._demo import launch_demo +def devbox(ctx: click.Context, image: str | None, dev: bool, gpu: bool): + """Start a local Flyte devbox cluster.""" + from flyte.cli._devbox import launch_devbox if image is None: - image = _DEFAULT_DEMO_GPU_IMAGE if gpu else _DEFAULT_DEMO_IMAGE + image = _DEFAULT_DEVBOX_GPU_IMAGE if gpu else _DEFAULT_DEVBOX_IMAGE log_format = getattr(ctx.obj, "log_format", "console") if ctx.obj else "console" - launch_demo(image, dev, gpu=gpu, log_format=log_format) + launch_devbox(image, dev, gpu=gpu, log_format=log_format) diff --git a/src/flyte/cli/_stop.py b/src/flyte/cli/_stop.py index 8ede92f98..d8394b5b9 100644 --- a/src/flyte/cli/_stop.py +++ b/src/flyte/cli/_stop.py @@ -7,8 +7,8 @@ def stop(): @stop.command() -def demo(): - """Pause the local Flyte demo cluster without removing it.""" - from flyte.cli._demo import stop_demo +def devbox(): + """Pause the local Flyte devbox cluster without removing it.""" + from flyte.cli._devbox import stop_devbox - stop_demo() + stop_devbox() diff --git a/tests/cli/test_demo.py b/tests/cli/test_devbox.py similarity index 59% rename from tests/cli/test_demo.py rename to tests/cli/test_devbox.py index d5bc0c84e..171a47d65 100644 --- a/tests/cli/test_demo.py +++ b/tests/cli/test_devbox.py @@ -1,7 +1,7 @@ """ -Unit tests for flyte.cli._demo. +Unit tests for flyte.cli._devbox. -Covers the `--gpu` plumbing on `flyte start demo` and the +Covers the `--gpu` plumbing on `flyte start devbox` and the kubeconfig chown-retry fallback when kubectl fails to read a root-owned kubeconfig on Linux bind mounts. """ @@ -13,8 +13,8 @@ import pytest from click.testing import CliRunner -from flyte.cli._demo import _merge_kubeconfig, _run_container -from flyte.cli._start import demo +from flyte.cli._devbox import _merge_kubeconfig, _run_container +from flyte.cli._start import devbox class TestRunContainerGpuFlag: @@ -22,15 +22,15 @@ class TestRunContainerGpuFlag: @staticmethod def _invoke(gpu: bool) -> list[str]: - with patch("flyte.cli._demo.subprocess.run") as mock_run: + with patch("flyte.cli._devbox.subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0, stderr="") _run_container( - image="ghcr.io/flyteorg/flyte-demo:gpu-latest", + image="ghcr.io/flyteorg/flyte-devbox:gpu-latest", is_dev_mode=False, - container_name="flyte-demo", + container_name="flyte-devbox", kube_dir=Path("/tmp/.kube"), - flyte_demo_config_dir=Path("/tmp/.flyte/demo"), - volume_name="flyte-demo", + flyte_devbox_config_dir=Path("/tmp/.flyte/devbox"), + volume_name="flyte-devbox", ports=["30080:30080"], gpu=gpu, ) @@ -47,9 +47,8 @@ def test_gpu_disabled_does_not_set_gpus(self): assert "--gpus" not in cmd def test_gpu_flag_precedes_image(self): - # `docker run [options] ` — --gpus must come before the image arg. cmd = self._invoke(gpu=True) - assert cmd.index("--gpus") < cmd.index("ghcr.io/flyteorg/flyte-demo:gpu-latest") + assert cmd.index("--gpus") < cmd.index("ghcr.io/flyteorg/flyte-devbox:gpu-latest") class TestMergeKubeconfigRetry: @@ -60,14 +59,14 @@ def test_success_on_first_try_does_not_chown(self, tmp_path): kubeconfig.write_text("") with ( - patch("flyte.cli._demo._flatten_kubeconfig") as mock_flatten, - patch("flyte.cli._demo.subprocess.run") as mock_run, - patch("flyte.cli._demo.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), - patch("flyte.cli._demo.Path.home", return_value=tmp_path), + patch("flyte.cli._devbox._flatten_kubeconfig") as mock_flatten, + patch("flyte.cli._devbox.subprocess.run") as mock_run, + patch("flyte.cli._devbox.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), + patch("flyte.cli._devbox.Path.home", return_value=tmp_path), ): mock_flatten.return_value = MagicMock(stdout="apiVersion: v1\n") - _merge_kubeconfig(kubeconfig, "flyte-demo") + _merge_kubeconfig(kubeconfig, "flyte-devbox") assert mock_flatten.call_count == 1 mock_run.assert_not_called() @@ -79,22 +78,22 @@ def test_called_process_error_triggers_chown_and_retry(self, tmp_path): kubeconfig.write_text("") with ( - patch("flyte.cli._demo._flatten_kubeconfig") as mock_flatten, - patch("flyte.cli._demo.subprocess.run") as mock_run, - patch("flyte.cli._demo.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), - patch("flyte.cli._demo.Path.home", return_value=tmp_path), + patch("flyte.cli._devbox._flatten_kubeconfig") as mock_flatten, + patch("flyte.cli._devbox.subprocess.run") as mock_run, + patch("flyte.cli._devbox.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), + patch("flyte.cli._devbox.Path.home", return_value=tmp_path), ): mock_flatten.side_effect = [ subprocess.CalledProcessError(1, ["kubectl", "config", "view", "--flatten"]), MagicMock(stdout="apiVersion: v1\n"), ] - _merge_kubeconfig(kubeconfig, "flyte-demo") + _merge_kubeconfig(kubeconfig, "flyte-devbox") assert mock_flatten.call_count == 2 assert mock_run.call_count == 1 docker_cmd = mock_run.call_args.args[0] - assert docker_cmd[:4] == ["docker", "exec", "flyte-demo", "chown"] + assert docker_cmd[:4] == ["docker", "exec", "flyte-devbox", "chown"] assert docker_cmd[-1] == "/.kube/kubeconfig" def test_permission_error_still_triggers_chown_and_retry(self, tmp_path): @@ -103,17 +102,17 @@ def test_permission_error_still_triggers_chown_and_retry(self, tmp_path): kubeconfig.write_text("") with ( - patch("flyte.cli._demo._flatten_kubeconfig") as mock_flatten, - patch("flyte.cli._demo.subprocess.run") as mock_run, - patch("flyte.cli._demo.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), - patch("flyte.cli._demo.Path.home", return_value=tmp_path), + patch("flyte.cli._devbox._flatten_kubeconfig") as mock_flatten, + patch("flyte.cli._devbox.subprocess.run") as mock_run, + patch("flyte.cli._devbox.shutil.move", side_effect=lambda src, dst: Path(dst).touch()), + patch("flyte.cli._devbox.Path.home", return_value=tmp_path), ): mock_flatten.side_effect = [ PermissionError("denied"), MagicMock(stdout="apiVersion: v1\n"), ] - _merge_kubeconfig(kubeconfig, "flyte-demo") + _merge_kubeconfig(kubeconfig, "flyte-devbox") assert mock_flatten.call_count == 2 assert mock_run.call_count == 1 @@ -124,61 +123,61 @@ def test_second_flatten_failure_propagates(self, tmp_path): kubeconfig.write_text("") with ( - patch("flyte.cli._demo._flatten_kubeconfig") as mock_flatten, - patch("flyte.cli._demo.subprocess.run"), - patch("flyte.cli._demo.Path.home", return_value=tmp_path), + patch("flyte.cli._devbox._flatten_kubeconfig") as mock_flatten, + patch("flyte.cli._devbox.subprocess.run"), + patch("flyte.cli._devbox.Path.home", return_value=tmp_path), ): err = subprocess.CalledProcessError(1, ["kubectl"]) mock_flatten.side_effect = [err, err] with pytest.raises(subprocess.CalledProcessError): - _merge_kubeconfig(kubeconfig, "flyte-demo") + _merge_kubeconfig(kubeconfig, "flyte-devbox") -class TestDemoCliGpuFlag: - """Verify the --gpu Click option is plumbed to launch_demo.""" +class TestDevboxCliGpuFlag: + """Verify the --gpu Click option is plumbed to launch_devbox.""" def test_gpu_flag_passed_through(self): runner = CliRunner() - with patch("flyte.cli._demo.launch_demo") as mock_launch: - result = runner.invoke(demo, ["--gpu", "--image", "flyte-demo:gpu-latest"]) + with patch("flyte.cli._devbox.launch_devbox") as mock_launch: + result = runner.invoke(devbox, ["--gpu", "--image", "flyte-devbox:gpu-latest"]) assert result.exit_code == 0, result.output mock_launch.assert_called_once() assert mock_launch.call_args.kwargs["gpu"] is True def test_gpu_defaults_to_false(self): runner = CliRunner() - with patch("flyte.cli._demo.launch_demo") as mock_launch: - result = runner.invoke(demo, ["--image", "flyte-demo:latest"]) + with patch("flyte.cli._devbox.launch_devbox") as mock_launch: + result = runner.invoke(devbox, ["--image", "flyte-devbox:latest"]) assert result.exit_code == 0, result.output mock_launch.assert_called_once() assert mock_launch.call_args.kwargs["gpu"] is False -class TestDemoCliDefaultImage: +class TestDevboxCliDefaultImage: """--gpu without --image should pick the GPU-capable default image.""" def test_gpu_without_image_uses_gpu_default(self): - from flyte.cli._start import _DEFAULT_DEMO_GPU_IMAGE + from flyte.cli._start import _DEFAULT_DEVBOX_GPU_IMAGE runner = CliRunner() - with patch("flyte.cli._demo.launch_demo") as mock_launch: - result = runner.invoke(demo, ["--gpu"]) + with patch("flyte.cli._devbox.launch_devbox") as mock_launch: + result = runner.invoke(devbox, ["--gpu"]) assert result.exit_code == 0, result.output - assert mock_launch.call_args.args[0] == _DEFAULT_DEMO_GPU_IMAGE + assert mock_launch.call_args.args[0] == _DEFAULT_DEVBOX_GPU_IMAGE def test_no_flags_uses_cpu_default(self): - from flyte.cli._start import _DEFAULT_DEMO_IMAGE + from flyte.cli._start import _DEFAULT_DEVBOX_IMAGE runner = CliRunner() - with patch("flyte.cli._demo.launch_demo") as mock_launch: - result = runner.invoke(demo, []) + with patch("flyte.cli._devbox.launch_devbox") as mock_launch: + result = runner.invoke(devbox, []) assert result.exit_code == 0, result.output - assert mock_launch.call_args.args[0] == _DEFAULT_DEMO_IMAGE + assert mock_launch.call_args.args[0] == _DEFAULT_DEVBOX_IMAGE def test_explicit_image_with_gpu_is_respected(self): runner = CliRunner() - with patch("flyte.cli._demo.launch_demo") as mock_launch: - result = runner.invoke(demo, ["--gpu", "--image", "myorg/custom:latest"]) + with patch("flyte.cli._devbox.launch_devbox") as mock_launch: + result = runner.invoke(devbox, ["--gpu", "--image", "myorg/custom:latest"]) assert result.exit_code == 0, result.output assert mock_launch.call_args.args[0] == "myorg/custom:latest"