From ff6d9a44f99c6309b1b207500d1e04765a9039e7 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Wed, 5 Feb 2025 10:56:33 -0500 Subject: [PATCH 1/4] Add ObjectMeta field to exporter configuration --- .../jumpstarter_cli_admin/create_test.py | 4 ++- .../jumpstarter_cli_admin/delete_test.py | 4 ++- .../jumpstarter_cli_admin/import_res_test.py | 2 ++ .../exporter_config.py | 7 ++-- .../jumpstarter_cli_exporter/exporter_test.py | 32 +++++++++++++++++-- .../jumpstarter_kubernetes/exporters.py | 13 ++++++-- .../jumpstarter/config/__init__.py | 3 +- .../jumpstarter/jumpstarter/config/common.py | 7 ++++ .../jumpstarter/config/exporter.py | 2 ++ .../jumpstarter/config/exporter_test.py | 6 ++++ 10 files changed, 70 insertions(+), 10 deletions(-) diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py index 639c9feca..16a63f7df 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py @@ -18,6 +18,7 @@ ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ExporterConfigV1Alpha1, + ObjectMeta, ) # Generate a random client name @@ -116,11 +117,12 @@ async def test_create_client( EXPORTER_OBJECT = V1Alpha1Exporter( api_version="jumpstarter.dev/v1alpha1", kind="Exporter", - metadata=V1ObjectMeta(name=EXPORTER_NAME, namespace="default", creation_timestamp="2024-01-01T21:00:00Z"), + metadata=V1ObjectMeta(namespace="default", name=EXPORTER_NAME, creation_timestamp="2024-01-01T21:00:00Z"), status=V1Alpha1ExporterStatus(endpoint=EXPORTER_ENDPOINT, credential=None, devices=[]), ) EXPORTER_CONFIG = ExporterConfigV1Alpha1( alias=EXPORTER_NAME, + metadata=ObjectMeta(namespace="default", name=EXPORTER_NAME), endpoint=EXPORTER_ENDPOINT, token=EXPORTER_TOKEN, ) diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py index 8664dfa90..601f47625 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py @@ -15,6 +15,7 @@ ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ExporterConfigV1Alpha1, + ObjectMeta, UserConfigV1Alpha1, UserConfigV1Alpha1Config, ) @@ -133,11 +134,12 @@ async def test_delete_client( EXPORTER_OBJECT = V1Alpha1Exporter( api_version="jumpstarter.dev/v1alpha1", kind="Exporter", - metadata=V1ObjectMeta(name=EXPORTER_NAME, namespace="default", creation_timestamp="2024-01-01T21:00:00Z"), + metadata=V1ObjectMeta(namespace="default", name=EXPORTER_NAME, creation_timestamp="2024-01-01T21:00:00Z"), status=V1Alpha1ExporterStatus(endpoint=EXPORTER_ENDPOINT, credential=None, devices=[]), ) EXPORTER_CONFIG = ExporterConfigV1Alpha1( alias=EXPORTER_NAME, + metadata=ObjectMeta(namespace="default", name=EXPORTER_NAME), endpoint=EXPORTER_ENDPOINT, token=EXPORTER_TOKEN, ) diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py index c2c921100..9cb16b9b7 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py @@ -13,6 +13,7 @@ ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ExporterConfigV1Alpha1, + ObjectMeta, ) # Generate a random client name @@ -85,6 +86,7 @@ async def test_import_client(_load_kube_config_mock, get_client_config_mock: Asy # Create a test exporter config EXPORTER_CONFIG = ExporterConfigV1Alpha1( alias=EXPORTER_NAME, + metadata=ObjectMeta(namespace="default", name=EXPORTER_NAME), endpoint=EXPORTER_ENDPOINT, token=EXPORTER_TOKEN, ) diff --git a/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_config.py b/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_config.py index 6283f6098..673119f3e 100644 --- a/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_config.py +++ b/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_config.py @@ -1,16 +1,18 @@ import asyncclick as click from jumpstarter_cli_common import make_table -from jumpstarter.config.exporter import ExporterConfigV1Alpha1 +from jumpstarter.config.exporter import ExporterConfigV1Alpha1, ObjectMeta arg_alias = click.argument("alias", default="default") @click.command("create-config") +@click.option("--namespace", prompt=True) +@click.option("--name", prompt=True) @click.option("--endpoint", prompt=True) @click.option("--token", prompt=True) @arg_alias -def create_exporter_config(alias, endpoint, token): +def create_exporter_config(alias, namespace, name, endpoint, token): """Create an exporter config.""" try: ExporterConfigV1Alpha1.load(alias) @@ -21,6 +23,7 @@ def create_exporter_config(alias, endpoint, token): config = ExporterConfigV1Alpha1( alias=alias, + metadata=ObjectMeta(namespace=namespace, name=name), endpoint=endpoint, token=token, ) diff --git a/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_test.py b/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_test.py index 93c6ef563..8803b445f 100644 --- a/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_test.py +++ b/packages/jumpstarter-cli-exporter/jumpstarter_cli_exporter/exporter_test.py @@ -16,18 +16,44 @@ async def test_exporter(tmp_config_path): # create exporter non-interactively result = await runner.invoke( - exporter, ["create-config", "test1", "--endpoint", "example.com:443", "--token", "dummy"] + exporter, + [ + "create-config", + "test1", + "--namespace", + "default", + "--name", + "test1", + "--endpoint", + "example.com:443", + "--token", + "dummy", + ], ) assert result.exit_code == 0 # create duplicate exporter result = await runner.invoke( - exporter, ["create-config", "test1", "--endpoint", "example.com:443", "--token", "dummy"] + exporter, + [ + "create-config", + "test1", + "--namespace", + "default", + "--name", + "test1", + "--endpoint", + "example.com:443", + "--token", + "dummy", + ], ) assert result.exit_code != 0 # create exporter interactively - result = await runner.invoke(exporter, ["create-config", "test2"], input="example.org:443\ndummytoken\n") + result = await runner.invoke( + exporter, ["create-config", "test2"], input="default\ntest2\nexample.org:443\ndummytoken\n" + ) assert result.exit_code == 0 # list exporters diff --git a/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py b/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py index d1bfa7cde..f7f6f912e 100644 --- a/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py +++ b/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/exporters.py @@ -6,7 +6,7 @@ from kubernetes_asyncio.client.models import V1ObjectMeta, V1ObjectReference from .util import AbstractAsyncCustomObjectApi -from jumpstarter.config import ExporterConfigV1Alpha1 +from jumpstarter.config import ExporterConfigV1Alpha1, ObjectMeta CREATE_EXPORTER_DELAY = 1 CREATE_EXPORTER_COUNT = 10 @@ -110,7 +110,16 @@ async def get_exporter_config(self, name: str) -> ExporterConfigV1Alpha1: secret = await self.core_api.read_namespaced_secret(exporter.status.credential.name, self.namespace) endpoint = exporter.status.endpoint token = base64.b64decode(secret.data["token"]).decode("utf8") - return ExporterConfigV1Alpha1(alias=name, endpoint=endpoint, token=token, export={}) + return ExporterConfigV1Alpha1( + alias=name, + metadata=ObjectMeta( + namespace=exporter.metadata.namespace, + name=exporter.metadata.name, + ), + endpoint=endpoint, + token=token, + export={}, + ) async def delete_exporter(self, name: str): """Delete an exporter object""" diff --git a/packages/jumpstarter/jumpstarter/config/__init__.py b/packages/jumpstarter/jumpstarter/config/__init__.py index c81ab2f02..fbdb65b86 100644 --- a/packages/jumpstarter/jumpstarter/config/__init__.py +++ b/packages/jumpstarter/jumpstarter/config/__init__.py @@ -2,7 +2,7 @@ ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ) -from .common import CONFIG_API_VERSION, CONFIG_PATH +from .common import CONFIG_API_VERSION, CONFIG_PATH, ObjectMeta from .env import JMP_CLIENT_CONFIG, JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_TOKEN from .exporter import ExporterConfigV1Alpha1, ExporterConfigV1Alpha1DriverInstance from .user import UserConfigV1Alpha1, UserConfigV1Alpha1Config @@ -15,6 +15,7 @@ "JMP_TOKEN", "JMP_DRIVERS_ALLOW", "JMP_DRIVERS_ALLOW_UNSAFE", + "ObjectMeta", "UserConfigV1Alpha1", "UserConfigV1Alpha1Config", "ClientConfigV1Alpha1", diff --git a/packages/jumpstarter/jumpstarter/config/common.py b/packages/jumpstarter/jumpstarter/config/common.py index 4879ca890..4fb28ada8 100644 --- a/packages/jumpstarter/jumpstarter/config/common.py +++ b/packages/jumpstarter/jumpstarter/config/common.py @@ -1,4 +1,11 @@ from pathlib import Path +from pydantic import BaseModel + CONFIG_API_VERSION = "jumpstarter.dev/v1alpha1" CONFIG_PATH = Path.home() / ".config" / "jumpstarter" + + +class ObjectMeta(BaseModel): + namespace: str + name: str diff --git a/packages/jumpstarter/jumpstarter/config/exporter.py b/packages/jumpstarter/jumpstarter/config/exporter.py index de55b51e1..cf47a671c 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter.py +++ b/packages/jumpstarter/jumpstarter/config/exporter.py @@ -9,6 +9,7 @@ from anyio.from_thread import start_blocking_portal from pydantic import BaseModel, Field +from .common import ObjectMeta from .tls import TLSConfigV1Alpha1 from jumpstarter.common.grpc import aio_secure_channel, ssl_channel_credentials from jumpstarter.common.importlib import import_class @@ -35,6 +36,7 @@ class ExporterConfigV1Alpha1(BaseModel): apiVersion: Literal["jumpstarter.dev/v1alpha1"] = "jumpstarter.dev/v1alpha1" kind: Literal["ExporterConfig"] = "ExporterConfig" + metadata: ObjectMeta endpoint: str tls: TLSConfigV1Alpha1 = Field(default_factory=TLSConfigV1Alpha1) diff --git a/packages/jumpstarter/jumpstarter/config/exporter_test.py b/packages/jumpstarter/jumpstarter/config/exporter_test.py index ed15f49ac..9e74939ee 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter_test.py +++ b/packages/jumpstarter/jumpstarter/config/exporter_test.py @@ -5,6 +5,7 @@ from anyio.from_thread import start_blocking_portal from .client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers +from .common import ObjectMeta from .exporter import ExporterConfigV1Alpha1, ExporterConfigV1Alpha1DriverInstance from .tls import TLSConfigV1Alpha1 from jumpstarter.common import MetadataFilter @@ -16,6 +17,7 @@ async def test_exporter_serve(mock_controller): exporter = ExporterConfigV1Alpha1( apiVersion="jumpstarter.dev/v1alpha1", kind="ExporterConfig", + metadata=ObjectMeta(namespace="default", name="test"), endpoint=mock_controller, token="dummy-exporter-token", export={ @@ -63,6 +65,9 @@ def test_exporter_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path): text = """apiVersion: jumpstarter.dev/v1alpha1 kind: ExporterConfig +metadata: + namespace: default + name: test tls: ca: "cacertificatedata" insecure: true @@ -101,6 +106,7 @@ def test_exporter_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path): alias="test", apiVersion="jumpstarter.dev/v1alpha1", kind="ExporterConfig", + metadata=ObjectMeta(namespace="default", name="test"), endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", tls=TLSConfigV1Alpha1(ca="cacertificatedata", insecure=True), From 5bb915c0ed25dd393af8628b9dea94c532a076ad Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Wed, 5 Feb 2025 11:19:00 -0500 Subject: [PATCH 2/4] Add ObjectMeta field to client configuration --- .../jumpstarter_cli_admin/create_test.py | 4 +- .../jumpstarter_cli_admin/delete_test.py | 1 + .../jumpstarter_cli_admin/import_res_test.py | 2 + .../jumpstarter_cli_client/client_config.py | 25 ++++++++--- .../jumpstarter_cli_client/client_test.py | 32 ++++++++++++-- .../jumpstarter_kubernetes/clients.py | 6 ++- .../jumpstarter/jumpstarter/config/client.py | 10 ++++- .../jumpstarter/config/client_config_test.py | 42 ++++++++++++++++--- .../jumpstarter/jumpstarter/config/env.py | 2 + .../jumpstarter/config/exporter_test.py | 1 + .../jumpstarter/config/user_config_test.py | 28 +++++++++++-- 11 files changed, 132 insertions(+), 21 deletions(-) diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py index 16a63f7df..ee4aec2f6 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py @@ -33,12 +33,13 @@ CLIENT_OBJECT = V1Alpha1Client( api_version="jumpstarter.dev/v1alpha1", kind="Client", - metadata=V1ObjectMeta(name=CLIENT_NAME, namespace="default", creation_timestamp="2024-01-01T21:00:00Z"), + metadata=V1ObjectMeta(namespace="default", name=CLIENT_NAME, creation_timestamp="2024-01-01T21:00:00Z"), status=V1Alpha1ClientStatus(endpoint=CLIENT_ENDPOINT, credential=None), ) UNSAFE_CLIENT_CONFIG = ClientConfigV1Alpha1( name=CLIENT_NAME, + metadata=ObjectMeta(namespace="default", name=CLIENT_NAME), endpoint=CLIENT_ENDPOINT, token=CLIENT_TOKEN, drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), @@ -46,6 +47,7 @@ CLIENT_CONFIG = ClientConfigV1Alpha1( name=CLIENT_NAME, + metadata=ObjectMeta(namespace="default", name=CLIENT_NAME), endpoint=CLIENT_ENDPOINT, token=CLIENT_TOKEN, drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py index 601f47625..9f2d3ca65 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/delete_test.py @@ -30,6 +30,7 @@ CLIENT_CONFIG = ClientConfigV1Alpha1( name=CLIENT_NAME, + metadata=ObjectMeta(namespace="default", name=CLIENT_NAME), endpoint=CLIENT_ENDPOINT, token=CLIENT_TOKEN, drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), diff --git a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py index 9cb16b9b7..10e4c93af 100644 --- a/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py +++ b/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py @@ -24,12 +24,14 @@ # Create a test client config UNSAFE_CLIENT_CONFIG = ClientConfigV1Alpha1( name=CLIENT_NAME, + metadata=ObjectMeta(namespace="default", name=CLIENT_NAME), endpoint=CLIENT_ENDPOINT, token=CLIENT_TOKEN, drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), ) CLIENT_CONFIG = ClientConfigV1Alpha1( name=CLIENT_NAME, + metadata=ObjectMeta(namespace="default", name=CLIENT_NAME), endpoint=CLIENT_ENDPOINT, token=CLIENT_TOKEN, drivers=ClientConfigV1Alpha1Drivers(allow=[DRIVER_NAME], unsafe=False), diff --git a/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_config.py b/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_config.py index 5a08aa25f..f1654fef9 100644 --- a/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_config.py +++ b/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_config.py @@ -3,17 +3,29 @@ import asyncclick as click from jumpstarter_cli_common import make_table -from jumpstarter.config import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, UserConfigV1Alpha1 +from jumpstarter.config import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ObjectMeta, UserConfigV1Alpha1 @click.command("create-config", short_help="Create a client config.") -@click.argument("name") +@click.argument("alias") @click.option( "-o", "--out", type=click.Path(dir_okay=False, resolve_path=True, writable=True), help="Specify an output file for the client config.", ) +@click.option( + "--namespace", + type=str, + help="Enter the Jumpstarter client namespace.", + prompt="Enter a valid Jumpstarter client nespace", +) +@click.option( + "--name", + type=str, + help="Enter the Jumpstarter client name.", + prompt="Enter a valid Jumpstarter client name", +) @click.option( "-e", "--endpoint", @@ -39,6 +51,8 @@ ) @click.option("--unsafe", is_flag=True, help="Should all driver client packages be allowed to load (UNSAFE!).") def create_client_config( + alias: str, + namespace: str, name: str, endpoint: str, token: str, @@ -47,11 +61,12 @@ def create_client_config( out: Optional[str], ): """Create a Jumpstarter client configuration.""" - if out is None and ClientConfigV1Alpha1.exists(name): - raise click.ClickException(f"A client with the name '{name}' already exists.") + if out is None and ClientConfigV1Alpha1.exists(alias): + raise click.ClickException(f"A client with the name '{alias}' already exists.") config = ClientConfigV1Alpha1( - name=name, + name=alias, + metadata=ObjectMeta(namespace=namespace, name=name), endpoint=endpoint, token=token, drivers=ClientConfigV1Alpha1Drivers(allow=allow.split(","), unsafe=unsafe), diff --git a/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_test.py b/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_test.py index 593d51788..59d9ffc48 100644 --- a/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_test.py +++ b/packages/jumpstarter-cli-client/jumpstarter_cli_client/client_test.py @@ -18,14 +18,40 @@ async def test_client(tmp_config_path): # create client non-interactively result = await runner.invoke( client, - ["create-config", "test1", "--endpoint", "example.com:443", "--token", "dummy", "--allow", "jumpstarter.*"], + [ + "create-config", + "test1", + "--namespace", + "default", + "--name", + "test1", + "--endpoint", + "example.com:443", + "--token", + "dummy", + "--allow", + "jumpstarter.*", + ], ) assert result.exit_code == 0 # create duplicate client result = await runner.invoke( client, - ["create-config", "test1", "--endpoint", "example.com:443", "--token", "dummy", "--allow", "jumpstarter.*"], + [ + "create-config", + "test1", + "--namespace", + "default", + "--name", + "test1", + "--endpoint", + "example.com:443", + "--token", + "dummy", + "--allow", + "jumpstarter.*", + ], ) assert result.exit_code != 0 @@ -33,7 +59,7 @@ async def test_client(tmp_config_path): result = await runner.invoke( client, ["create-config", "test2"], - input="example.org:443\ndummytoken\njumpstarter.*,com.example.*\n", + input="default\ntest2\nexample.org:443\ndummytoken\njumpstarter.*,com.example.*\n", ) assert result.exit_code == 0 diff --git a/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py b/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py index 14c55f7ba..86eacc5e0 100644 --- a/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py +++ b/packages/jumpstarter-kubernetes/jumpstarter_kubernetes/clients.py @@ -7,7 +7,7 @@ from kubernetes_asyncio.client.models import V1ObjectMeta, V1ObjectReference from .util import AbstractAsyncCustomObjectApi -from jumpstarter.config import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers +from jumpstarter.config import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ObjectMeta logger = logging.getLogger(__name__) @@ -105,6 +105,10 @@ async def get_client_config(self, name: str, allow: list[str], unsafe=False) -> token = base64.b64decode(secret.data["token"]).decode("utf8") return ClientConfigV1Alpha1( name=name, + metadata=ObjectMeta( + namespace=client.metadata.namespace, + name=client.metadata.name, + ), endpoint=endpoint, token=token, drivers=ClientConfigV1Alpha1Drivers(allow=allow, unsafe=unsafe), diff --git a/packages/jumpstarter/jumpstarter/config/client.py b/packages/jumpstarter/jumpstarter/config/client.py index 110ed394a..75b726072 100644 --- a/packages/jumpstarter/jumpstarter/config/client.py +++ b/packages/jumpstarter/jumpstarter/config/client.py @@ -9,8 +9,8 @@ from jumpstarter_protocol import jumpstarter_pb2, jumpstarter_pb2_grpc from pydantic import BaseModel, Field, ValidationError -from .common import CONFIG_PATH -from .env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_LEASE, JMP_TOKEN +from .common import CONFIG_PATH, ObjectMeta +from .env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_LEASE, JMP_NAME, JMP_NAMESPACE, JMP_TOKEN from .tls import TLSConfigV1Alpha1 from jumpstarter.common import MetadataFilter from jumpstarter.common.grpc import aio_secure_channel, ssl_channel_credentials @@ -41,6 +41,8 @@ class ClientConfigV1Alpha1(BaseModel): apiVersion: Literal["jumpstarter.dev/v1alpha1"] = Field(default="jumpstarter.dev/v1alpha1") kind: Literal["ClientConfig"] = Field(default="ClientConfig") + metadata: ObjectMeta + endpoint: str tls: TLSConfigV1Alpha1 = Field(default_factory=TLSConfigV1Alpha1) token: str @@ -141,6 +143,10 @@ def try_from_env(cls): def from_env(cls): allow, unsafe = _allow_from_env() return cls( + metadata=ObjectMeta( + namespace=os.environ.get(JMP_NAMESPACE), + name=os.environ.get(JMP_NAME), + ), endpoint=os.environ.get(JMP_ENDPOINT), token=os.environ.get(JMP_TOKEN), drivers=ClientConfigV1Alpha1Drivers( diff --git a/packages/jumpstarter/jumpstarter/config/client_config_test.py b/packages/jumpstarter/jumpstarter/config/client_config_test.py index fbdc1e9ae..6e1bf07c8 100644 --- a/packages/jumpstarter/jumpstarter/config/client_config_test.py +++ b/packages/jumpstarter/jumpstarter/config/client_config_test.py @@ -7,11 +7,8 @@ import yaml from pydantic import ValidationError -from jumpstarter.config import ( - ClientConfigV1Alpha1, - ClientConfigV1Alpha1Drivers, -) -from jumpstarter.config.env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_TOKEN +from jumpstarter.config import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ObjectMeta +from jumpstarter.config.env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_NAME, JMP_NAMESPACE, JMP_TOKEN def test_client_ensure_exists_makes_dir(monkeypatch: pytest.MonkeyPatch): @@ -22,12 +19,16 @@ def test_client_ensure_exists_makes_dir(monkeypatch: pytest.MonkeyPatch): def test_client_config_try_from_env(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv(JMP_NAMESPACE, "default") + monkeypatch.setenv(JMP_NAME, "testclient") monkeypatch.setenv(JMP_TOKEN, "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz") monkeypatch.setenv(JMP_ENDPOINT, "jumpstarter.my-lab.com:1443") monkeypatch.setenv(JMP_DRIVERS_ALLOW, "jumpstarter.drivers.*,vendorpackage.*") config = ClientConfigV1Alpha1.try_from_env() assert config.name == "default" + assert config.metadata.namespace == "default" + assert config.metadata.name == "testclient" assert config.token == "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz" assert config.endpoint == "jumpstarter.my-lab.com:1443" assert config.drivers.allow == ["jumpstarter.drivers.*", "vendorpackage.*"] @@ -40,12 +41,16 @@ def test_client_config_try_from_env_not_set(): def test_client_config_from_env(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv(JMP_NAMESPACE, "default") + monkeypatch.setenv(JMP_NAME, "testclient") monkeypatch.setenv(JMP_TOKEN, "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz") monkeypatch.setenv(JMP_ENDPOINT, "jumpstarter.my-lab.com:1443") monkeypatch.setenv(JMP_DRIVERS_ALLOW, "jumpstarter.drivers.*,vendorpackage.*") config = ClientConfigV1Alpha1.from_env() assert config.name == "default" + assert config.metadata.namespace == "default" + assert config.metadata.name == "testclient" assert config.token == "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz" assert config.endpoint == "jumpstarter.my-lab.com:1443" assert config.drivers.allow == ["jumpstarter.drivers.*", "vendorpackage.*"] @@ -53,12 +58,16 @@ def test_client_config_from_env(monkeypatch: pytest.MonkeyPatch): def test_client_config_from_env_allow_unsafe(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv(JMP_NAMESPACE, "default") + monkeypatch.setenv(JMP_NAME, "testclient") monkeypatch.setenv(JMP_TOKEN, "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz") monkeypatch.setenv(JMP_ENDPOINT, "jumpstarter.my-lab.com:1443") monkeypatch.setenv(JMP_DRIVERS_ALLOW, "UNSAFE") config = ClientConfigV1Alpha1.from_env() assert config.name == "default" + assert config.metadata.namespace == "default" + assert config.metadata.name == "testclient" assert config.token == "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz" assert config.endpoint == "jumpstarter.my-lab.com:1443" assert config.drivers.allow == [] @@ -67,6 +76,8 @@ def test_client_config_from_env_allow_unsafe(monkeypatch: pytest.MonkeyPatch): @pytest.mark.parametrize("missing_field", [JMP_TOKEN, JMP_ENDPOINT]) def test_client_config_from_env_missing_field_raises(monkeypatch: pytest.MonkeyPatch, missing_field): + monkeypatch.setenv(JMP_NAMESPACE, "default") + monkeypatch.setenv(JMP_NAME, "testclient") monkeypatch.setenv(JMP_TOKEN, "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz") monkeypatch.setenv(JMP_ENDPOINT, "jumpstarter.my-lab.com:1443") monkeypatch.setenv(JMP_DRIVERS_ALLOW, "jumpstarter.drivers.*,vendorpackage.*") @@ -80,6 +91,9 @@ def test_client_config_from_env_missing_field_raises(monkeypatch: pytest.MonkeyP def test_client_config_from_file(): CLIENT_CONFIG = """apiVersion: jumpstarter.dev/v1alpha1 kind: ClientConfig +metadata: + namespace: default + name: testclient endpoint: jumpstarter.my-lab.com:1443 token: dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz drivers: @@ -92,6 +106,8 @@ def test_client_config_from_file(): f.close() config = ClientConfigV1Alpha1.from_file(f.name) assert config.name == f.name.split("/")[-1] + assert config.metadata.namespace == "default" + assert config.metadata.name == "testclient" assert config.endpoint == "jumpstarter.my-lab.com:1443" assert config.token == "dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz" assert config.drivers.allow == ["jumpstarter.drivers.*", "vendorpackage.*"] @@ -160,6 +176,7 @@ def test_client_config_load(): "from_file", return_value=ClientConfigV1Alpha1( name="another", + metadata=ObjectMeta(namespace="default", name="another"), endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), @@ -180,6 +197,9 @@ def test_client_config_load_not_found_raises(): def test_client_config_save(monkeypatch: pytest.MonkeyPatch): CLIENT_CONFIG = """apiVersion: jumpstarter.dev/v1alpha1 kind: ClientConfig +metadata: + namespace: default + name: testclient endpoint: jumpstarter.my-lab.com:1443 tls: ca: '' @@ -193,6 +213,7 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): """ config = ClientConfigV1Alpha1( name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), @@ -211,6 +232,9 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): def test_client_config_save_explicit_path(): CLIENT_CONFIG = """apiVersion: jumpstarter.dev/v1alpha1 kind: ClientConfig +metadata: + namespace: default + name: testclient endpoint: jumpstarter.my-lab.com:1443 tls: ca: '' @@ -224,6 +248,7 @@ def test_client_config_save_explicit_path(): """ config = ClientConfigV1Alpha1( name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), @@ -240,6 +265,9 @@ def test_client_config_save_explicit_path(): def test_client_config_save_unsafe_drivers(): CLIENT_CONFIG = """apiVersion: jumpstarter.dev/v1alpha1 kind: ClientConfig +metadata: + namespace: default + name: testclient endpoint: jumpstarter.my-lab.com:1443 tls: ca: '' @@ -251,6 +279,7 @@ def test_client_config_save_unsafe_drivers(): """ config = ClientConfigV1Alpha1( name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), @@ -275,6 +304,9 @@ def test_client_config_exists(): def test_client_config_list(monkeypatch: pytest.MonkeyPatch): CLIENT_CONFIG = """apiVersion: jumpstarter.dev/v1alpha1 kind: ClientConfig +metadata: + namespace: default + name: testclient endpoint: jumpstarter.my-lab.com:1443 token: dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz drivers: diff --git a/packages/jumpstarter/jumpstarter/config/env.py b/packages/jumpstarter/jumpstarter/config/env.py index 1cde72093..8a23678de 100644 --- a/packages/jumpstarter/jumpstarter/config/env.py +++ b/packages/jumpstarter/jumpstarter/config/env.py @@ -1,5 +1,7 @@ # Common environment variables for client/exporter config JMP_CLIENT_CONFIG = "JMP_CLIENT_CONFIG" +JMP_NAMESPACE = "JMP_NAMESPACE" +JMP_NAME = "JMP_NAME" JMP_ENDPOINT = "JMP_ENDPOINT" JMP_TOKEN = "JMP_TOKEN" JMP_DRIVERS_ALLOW = "JMP_DRIVERS_ALLOW" diff --git a/packages/jumpstarter/jumpstarter/config/exporter_test.py b/packages/jumpstarter/jumpstarter/config/exporter_test.py index 9e74939ee..33348cc0a 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter_test.py +++ b/packages/jumpstarter/jumpstarter/config/exporter_test.py @@ -40,6 +40,7 @@ async def test_exporter_serve(mock_controller): client = ClientConfigV1Alpha1( name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint=mock_controller, token="dummy-client-token", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), diff --git a/packages/jumpstarter/jumpstarter/config/user_config_test.py b/packages/jumpstarter/jumpstarter/config/user_config_test.py index d7daa9d06..a844f98fe 100644 --- a/packages/jumpstarter/jumpstarter/config/user_config_test.py +++ b/packages/jumpstarter/jumpstarter/config/user_config_test.py @@ -7,6 +7,7 @@ from jumpstarter.config import ( ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, + ObjectMeta, UserConfigV1Alpha1, UserConfigV1Alpha1Config, ) @@ -31,7 +32,11 @@ def test_user_config_load(monkeypatch: pytest.MonkeyPatch): ClientConfigV1Alpha1, "load", return_value=ClientConfigV1Alpha1( - name="testclient", endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False) + name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), + endpoint="abc", + token="123", + drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), ), ) as mock_load: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -59,7 +64,11 @@ def test_user_config_load_no_current_client(monkeypatch: pytest.MonkeyPatch): ClientConfigV1Alpha1, "load", return_value=ClientConfigV1Alpha1( - name="testclient", endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False) + name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), + endpoint="abc", + token="123", + drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), ), ) as mock_load: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -82,7 +91,11 @@ def test_user_config_load_current_client_empty(monkeypatch: pytest.MonkeyPatch): ClientConfigV1Alpha1, "load", return_value=ClientConfigV1Alpha1( - name="testclient", endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False) + name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), + endpoint="abc", + token="123", + drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), ), ) as mock_load: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -182,6 +195,7 @@ def test_user_config_save(monkeypatch: pytest.MonkeyPatch): config=UserConfigV1Alpha1Config( current_client=ClientConfigV1Alpha1( name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), @@ -221,7 +235,11 @@ def test_user_config_use_client(monkeypatch: pytest.MonkeyPatch): ClientConfigV1Alpha1, "load", return_value=ClientConfigV1Alpha1( - name="testclient", endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False) + name="testclient", + metadata=ObjectMeta(namespace="default", name="testclient"), + endpoint="abc", + token="123", + drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), ), ) as mock_load: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -230,6 +248,7 @@ def test_user_config_use_client(monkeypatch: pytest.MonkeyPatch): config=UserConfigV1Alpha1Config( current_client=ClientConfigV1Alpha1( name="another", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), @@ -257,6 +276,7 @@ def test_user_config_use_client_none(monkeypatch: pytest.MonkeyPatch): config=UserConfigV1Alpha1Config( current_client=ClientConfigV1Alpha1( name="another", + metadata=ObjectMeta(namespace="default", name="testclient"), endpoint="abc", token="123", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=False), From 546835f836b9be381782682d75a53ddc88a216eb Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Wed, 5 Feb 2025 11:44:32 -0500 Subject: [PATCH 3/4] Make ObjectMeta part of grpc context --- .../jumpstarter/jumpstarter/config/client.py | 3 ++- .../jumpstarter/config/exporter.py | 3 ++- .../jumpstarter/jumpstarter/config/grpc.py | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 packages/jumpstarter/jumpstarter/config/grpc.py diff --git a/packages/jumpstarter/jumpstarter/config/client.py b/packages/jumpstarter/jumpstarter/config/client.py index 75b726072..5b454d51b 100644 --- a/packages/jumpstarter/jumpstarter/config/client.py +++ b/packages/jumpstarter/jumpstarter/config/client.py @@ -11,6 +11,7 @@ from .common import CONFIG_PATH, ObjectMeta from .env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_LEASE, JMP_NAME, JMP_NAMESPACE, JMP_TOKEN +from .grpc import call_credentials from .tls import TLSConfigV1Alpha1 from jumpstarter.common import MetadataFilter from jumpstarter.common.grpc import aio_secure_channel, ssl_channel_credentials @@ -52,7 +53,7 @@ class ClientConfigV1Alpha1(BaseModel): async def channel(self): credentials = grpc.composite_channel_credentials( ssl_channel_credentials(self.endpoint, self.tls), - grpc.access_token_call_credentials(self.token), + call_credentials("client", self.metadata, self.token), ) return aio_secure_channel(self.endpoint, credentials) diff --git a/packages/jumpstarter/jumpstarter/config/exporter.py b/packages/jumpstarter/jumpstarter/config/exporter.py index cf47a671c..ef35f8b48 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter.py +++ b/packages/jumpstarter/jumpstarter/config/exporter.py @@ -10,6 +10,7 @@ from pydantic import BaseModel, Field from .common import ObjectMeta +from .grpc import call_credentials from .tls import TLSConfigV1Alpha1 from jumpstarter.common.grpc import aio_secure_channel, ssl_channel_credentials from jumpstarter.common.importlib import import_class @@ -118,7 +119,7 @@ async def serve(self): def channel_factory(): credentials = grpc.composite_channel_credentials( ssl_channel_credentials(self.endpoint, self.tls), - grpc.access_token_call_credentials(self.token), + call_credentials("exporter", self.metadata, self.token), ) return aio_secure_channel(self.endpoint, credentials) diff --git a/packages/jumpstarter/jumpstarter/config/grpc.py b/packages/jumpstarter/jumpstarter/config/grpc.py new file mode 100644 index 000000000..2d69a2f37 --- /dev/null +++ b/packages/jumpstarter/jumpstarter/config/grpc.py @@ -0,0 +1,20 @@ +import grpc + +from .common import ObjectMeta + + +def call_credentials(kind: str, metadata: ObjectMeta, token: str): + def metadata_call_credentials(context: grpc.AuthMetadataContext, callback: grpc.AuthMetadataPluginCallback): + callback( + [ + ("jumpstarter-kind", kind), + ("jumpstarter-namespace", metadata.namespace), + ("jumpstarter-name", metadata.name), + ], + None, + ) + + return grpc.composite_call_credentials( + grpc.metadata_call_credentials(metadata_call_credentials), + grpc.access_token_call_credentials(token), + ) From e5e41e79ddebdb33110e50395d28d46fe5dae2a1 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Wed, 5 Feb 2025 15:22:22 -0500 Subject: [PATCH 4/4] Fix casing of call credential --- packages/jumpstarter/jumpstarter/config/client.py | 2 +- packages/jumpstarter/jumpstarter/config/exporter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jumpstarter/jumpstarter/config/client.py b/packages/jumpstarter/jumpstarter/config/client.py index 5b454d51b..f5049588d 100644 --- a/packages/jumpstarter/jumpstarter/config/client.py +++ b/packages/jumpstarter/jumpstarter/config/client.py @@ -53,7 +53,7 @@ class ClientConfigV1Alpha1(BaseModel): async def channel(self): credentials = grpc.composite_channel_credentials( ssl_channel_credentials(self.endpoint, self.tls), - call_credentials("client", self.metadata, self.token), + call_credentials("Client", self.metadata, self.token), ) return aio_secure_channel(self.endpoint, credentials) diff --git a/packages/jumpstarter/jumpstarter/config/exporter.py b/packages/jumpstarter/jumpstarter/config/exporter.py index ef35f8b48..ac34fd450 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter.py +++ b/packages/jumpstarter/jumpstarter/config/exporter.py @@ -119,7 +119,7 @@ async def serve(self): def channel_factory(): credentials = grpc.composite_channel_credentials( ssl_channel_credentials(self.endpoint, self.tls), - call_credentials("exporter", self.metadata, self.token), + call_credentials("Exporter", self.metadata, self.token), ) return aio_secure_channel(self.endpoint, credentials)