Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
with:
version: "0.5.20"

- run: uv sync
- run: uv sync --extra dev
- run: uv run pytest .
- run: uv run mypy .
- run: uv run ruff check .
Expand Down
15 changes: 10 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel.hooks.mypyc]
dependencies = ["hatch-mypyc"]
require-runtime-dependencies = true
require-runtime-features = ["dev"]

[project]
name = "nyl"
version = "0.8.1"
Expand All @@ -28,11 +33,8 @@ requires-python = ">=3.11"
readme = "README.md"
license = {text = "MIT"}

[project.scripts]
nyl = "nyl.commands:app"

[tool.uv]
dev-dependencies = [
[project.optional-dependencies]
dev = [
"kubernetes-stubs>=22.6.0.post1",
"mypy>=1.13.0",
"pytest>=8.2.2",
Expand All @@ -41,6 +43,9 @@ dev-dependencies = [
"types-requests>=2.32.0.20240712",
]

[project.scripts]
nyl = "nyl.commands:app"

[tool.mypy]
explicit_package_bases = true
namespace_packages = true
Expand Down
8 changes: 4 additions & 4 deletions src/nyl/commands/tun.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from nyl.commands import PROVIDER
from nyl.profiles import get_tunnel_spec
from nyl.profiles.config import ProfileConfig
from nyl.profiles.tunnel import TunnelManager, TunnelSpec, TunnelStatus
from nyl.profiles.tunnel import TunnelManager, TunnelSpec, TunnelSpecForwarding, TunnelSpecLocator, TunnelStatus
from nyl.tools.fs import shorter_path
from nyl.tools.typer import new_typer

Expand Down Expand Up @@ -98,8 +98,8 @@ def start(profile_name: str = Argument("default", envvar="NYL_PROFILE")) -> None

# TODO: Know the Kubernetes host/port to forward.
spec = TunnelSpec(
locator=TunnelSpec.Locator(str(config.file), profile_name),
forwardings={"kubernetes": TunnelSpec.Forwarding(host="localhost", port=6443)},
locator=TunnelSpecLocator(str(config.file), profile_name),
forwardings={"kubernetes": TunnelSpecForwarding(host="localhost", port=6443)},
user=profile.tunnel.user,
host=profile.tunnel.host,
identity_file=profile.tunnel.identity_file,
Expand All @@ -123,4 +123,4 @@ def stop(profile_name: str = Argument("default", envvar="NYL_PROFILE"), all: boo

config = PROVIDER.get(ProfileConfig)
with TunnelManager() as manager:
manager.close_tunnel(TunnelSpec.Locator(str(config.file), profile_name))
manager.close_tunnel(TunnelSpecLocator(str(config.file), profile_name))
27 changes: 15 additions & 12 deletions src/nyl/generator/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,18 @@ def generate(self, /, resource: Manifest) -> Manifests:
if instance.remainder:
raise RuntimeError(f"unexpected fields in component {instance.metadata}: {instance.remainder.keys()}")

match component:
case HelmComponent(path):
chart = HelmChart(
metadata=instance.metadata,
spec=HelmChartSpec(
chart=ChartRef(path=str(path.resolve())),
values={"metadata": resource["metadata"], **instance.spec},
),
)
return self.helm_generator.generate(chart)
case _:
raise RuntimeError(f"unexpected component type: {component}")
# match component:
# case HelmComponent(path):
if isinstance(component, HelmComponent):
path = component.path
chart = HelmChart(
metadata=instance.metadata,
spec=HelmChartSpec(
chart=ChartRef(path=str(path.resolve())),
values={"metadata": resource["metadata"], **instance.spec},
),
)
return self.helm_generator.generate(chart)
else:
# case _:
raise RuntimeError(f"unexpected component type: {component}")
4 changes: 3 additions & 1 deletion src/nyl/generator/dispatch_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import importlib
import pkgutil
from pathlib import Path
from typing import cast
from unittest.mock import MagicMock

from loguru import logger
Expand All @@ -25,7 +26,8 @@ def test__DispatchingGenerator__default__creates_generator_for_every_nyl_inline_
isinstance(value, type)
and issubclass(value, NylResource)
and value != NylResource
and value.__module__ == info.name
and cast(object, value).__module__
== info.name # note: see https://github.com/python/typeshed/issues/12128
and value.API_VERSION == API_VERSION_INLINE
):
resource_kinds.add(value.KIND)
Expand Down
6 changes: 3 additions & 3 deletions src/nyl/profiles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from .config import ProfileConfig, SshTunnel
from .kubeconfig import KubeconfigManager
from .tunnel import TunnelManager, TunnelSpec
from .tunnel import TunnelManager, TunnelSpec, TunnelSpecForwarding, TunnelSpecLocator


@dataclass
Expand Down Expand Up @@ -159,8 +159,8 @@ def _wait_for_api_server(url: str, timeout: float) -> None:

def get_tunnel_spec(config_file: Path, profile: str, conf: SshTunnel) -> TunnelSpec:
return TunnelSpec(
locator=TunnelSpec.Locator(str(config_file), profile),
forwardings={"kubernetes": TunnelSpec.Forwarding(host="localhost", port=6443)},
locator=TunnelSpecLocator(str(config_file), profile),
forwardings={"kubernetes": TunnelSpecForwarding(host="localhost", port=6443)},
user=conf.user,
host=conf.host,
identity_file=conf.identity_file,
Expand Down
8 changes: 4 additions & 4 deletions src/nyl/profiles/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass, field
from pathlib import Path
from typing import Literal
from typing import ClassVar, Literal

from loguru import logger

Expand Down Expand Up @@ -126,9 +126,9 @@ class SshTunnel:

@dataclass
class ProfileConfig:
FILENAMES = ["nyl-profiles.yaml", "nyl-profiles.toml", "nyl-profiles.json"]
GLOBAL_CONFIG_DIR = Path.home() / ".config" / "nyl"
STATE_DIRNAME = ".nyl"
FILENAMES: ClassVar = ["nyl-profiles.yaml", "nyl-profiles.toml", "nyl-profiles.json"]
GLOBAL_CONFIG_DIR: ClassVar = Path.home() / ".config" / "nyl"
STATE_DIRNAME: ClassVar = ".nyl"

file: Path | None
profiles: dict[str, Profile]
Expand Down
40 changes: 21 additions & 19 deletions src/nyl/profiles/tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable, Literal
from typing import Any, ClassVar, Iterable, Literal

from loguru import logger
from stablehash import stablehash
Expand All @@ -13,29 +13,31 @@
from nyl.tools.shell import pretty_cmd


@dataclass
class TunnelSpecForwarding:
host: str
port: int


@dataclass(frozen=True)
class TunnelSpecLocator:
config_file: str
profile: str

def __str__(self) -> str:
return f"{self.config_file}:{self.profile}"


@dataclass
class TunnelSpec:
"""
Defines an SSH tunel that is to be opened.
"""

@dataclass
class Forwarding:
host: str
port: int

@dataclass(frozen=True)
class Locator:
config_file: str
profile: str

def __str__(self) -> str:
return f"{self.config_file}:{self.profile}"

locator: Locator
locator: TunnelSpecLocator
" Locator for where the tunnel spec is defined."

forwardings: dict[str, Forwarding]
forwardings: dict[str, TunnelSpecForwarding]
""" A map from forwarding alias to forwarding configuration. The local ports will be randomly assigned.
and must be obtained from the [TunnelStatus]. """

Expand Down Expand Up @@ -77,7 +79,7 @@ class TunnelManager:
Before the tunnel manager can be used, its context manager must be entered to lock the global state.
"""

DEFAULT_STATE_DIR = Path.home() / ".nyl" / "tunnels"
DEFAULT_STATE_DIR: ClassVar = Path.home() / ".nyl" / "tunnels"

def __init__(self, state_dir: Path | None = None) -> None:
"""
Expand Down Expand Up @@ -136,7 +138,7 @@ def get_tunnels(self) -> Iterable[tuple[TunnelSpec, TunnelStatus]]:
self._store.set(key, (spec, status))
yield spec, status

def get_tunnel(self, locator: TunnelSpec.Locator) -> tuple[TunnelSpec, TunnelStatus] | None:
def get_tunnel(self, locator: TunnelSpecLocator) -> tuple[TunnelSpec, TunnelStatus] | None:
"""
Retrieve the last known tunnel status and spec based on the tunnel locator.
"""
Expand Down Expand Up @@ -218,7 +220,7 @@ def open_tunnel(self, spec: TunnelSpec) -> TunnelStatus:

return status

def close_tunnel(self, locator: TunnelSpec.Locator) -> TunnelStatus:
def close_tunnel(self, locator: TunnelSpecLocator) -> TunnelStatus:
"""
Close a tunnel by it's locator.
"""
Expand Down
4 changes: 2 additions & 2 deletions src/nyl/project/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, Literal
from typing import Callable, ClassVar, Literal

from loguru import logger

Expand Down Expand Up @@ -91,7 +91,7 @@ class ProjectConfig:
Wrapper for the project configuration file.
"""

FILENAMES = ["nyl-project.yaml", "nyl-project.toml", "nyl-project.json"]
FILENAMES: ClassVar = ["nyl-project.yaml", "nyl-project.toml", "nyl-project.json"]

file: Path | None
config: Project
Expand Down
3 changes: 2 additions & 1 deletion src/nyl/secrets/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar

from loguru import logger

Expand All @@ -11,7 +12,7 @@

@dataclass
class SecretsConfig:
FILENAMES = ["nyl-secrets.yaml", "nyl-secrets.toml", "nyl-secrets.json"]
FILENAMES: ClassVar = ["nyl-secrets.yaml", "nyl-secrets.toml", "nyl-secrets.json"]

file: Path | None
providers: dict[str, SecretProvider]
Expand Down
2 changes: 1 addition & 1 deletion src/nyl/tools/di.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def get(self, object_type: type[T]) -> T:
raise DependencyNotSatisfiedError(object_type)


class DependencyNotSatisfiedError(RuntimeError):
class DependencyNotSatisfiedError(Exception):
def __init__(self, object_type: type[T]) -> None:
self.object_type = object_type

Expand Down
9 changes: 5 additions & 4 deletions src/nyl/tools/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@


class PatchedSafeLoader(SafeLoader):
yaml_implicit_resolvers = SafeLoader.yaml_implicit_resolvers.copy()

# See https://github.com/yaml/pyyaml/issues/89
yaml_implicit_resolvers.pop("=")
def __init__(self, *args: Any, **kwargs: Any) -> None:
# See https://github.com/yaml/pyyaml/issues/89
super().__init__(*args, **kwargs)
self.yaml_implicit_resolvers = self.yaml_implicit_resolvers.copy()
self.yaml_implicit_resolvers.pop("=")


class PatchedSafeDumper(SafeDumper):
Expand Down
20 changes: 8 additions & 12 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading