Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions pyisolate/operator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Kubernetes operator for PyIsolate sandboxes."""

from __future__ import annotations

import logging
Expand All @@ -15,8 +16,19 @@


def run_operator(namespace: str = "default") -> None:
"""Start the operator watch loop."""
from kubernetes import client, config, watch # type: ignore
"""Start the operator watch loop.

Requires the optional ``kubernetes`` client, which is not a core dependency.
Install it with ``pip install pyisolate[operator]``.
"""
try:
from kubernetes import client, config, watch # type: ignore
except ImportError as exc:
raise RuntimeError(
"the PyIsolate Kubernetes operator requires the 'kubernetes' "
"package, which is not installed. Install it with "
"`pip install pyisolate[operator]`."
) from exc

config.load_incluster_config()
api = client.CustomObjectsApi()
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pyisolate-doctor = "pyisolate.doctor:main"

[project.optional-dependencies]
pqcrypto = ["pqcrypto"]
operator = ["kubernetes"]
release = [
"build",
"twine",
Expand Down
14 changes: 14 additions & 0 deletions tests/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import types
from pathlib import Path

import pytest


def load_operator():
pkg = types.ModuleType("pyisolate")
Expand Down Expand Up @@ -34,6 +36,18 @@ def test_operator_module():
assert hasattr(op, "scale_sandboxes")


def test_run_operator_without_kubernetes_gives_actionable_error():
try:
import kubernetes # noqa: F401

pytest.skip("kubernetes is installed; missing-dependency path not exercised")
except ImportError:
pass
op = load_operator()
with pytest.raises(RuntimeError, match=r"pyisolate\[operator\]"):
op.run_operator()


def test_operator_handles_relisted_added_and_modified(monkeypatch):
op = load_operator()
events = [
Expand Down
Loading