From e94dc3505d4f1517b4156d37b8d580a148e1a37c Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Fri, 3 Oct 2025 10:16:44 +0200 Subject: [PATCH 1/6] add per-method description and timeout to shell commands introducing new format in addition to the simple methods: method: "command" you can now specify per-method customization with: methods: method: description: "This shows up in the CLI" method: "echo Hello" timeout: 5 --- packages/jumpstarter-driver-shell/README.md | 107 ++++++++-- .../jumpstarter_driver_shell/client.py | 24 ++- .../jumpstarter_driver_shell/driver.py | 51 ++++- .../jumpstarter_driver_shell/driver_test.py | 187 +++++++++++++----- 4 files changed, 286 insertions(+), 83 deletions(-) diff --git a/packages/jumpstarter-driver-shell/README.md b/packages/jumpstarter-driver-shell/README.md index d80f497bf..aa552cec5 100644 --- a/packages/jumpstarter-driver-shell/README.md +++ b/packages/jumpstarter-driver-shell/README.md @@ -11,7 +11,9 @@ $ pip3 install --extra-index-url {{index_url}} jumpstarter-driver-shell ## Configuration -Example configuration: +The shell driver supports two configuration formats for methods: + +### Format 1: Simple String e.g. for self-descriptive short commands ```yaml export: @@ -20,12 +22,40 @@ export: config: methods: ls: "ls" - method2: "echo 'Hello World 2'" - #multi line method - method3: | - echo 'Hello World $1' - echo 'Hello World $2' - env_var: "echo $1,$2,$ENV_VAR" + echo_hello: "echo 'Hello World'" +``` + +### Format 2: Unified Format with Descriptions + +```yaml +export: + shell: + type: jumpstarter_driver_shell.driver.Shell + config: + methods: + ls: + command: "ls -la" + description: "List directory contents with details" + deploy: + command: "ansible-playbook deploy.yml" + description: "Deploy application using Ansible" + # Multi-line commands work too + setup: + command: | + echo 'Setting up environment' + export PATH=$PATH:/usr/local/bin + ./setup.sh + description: "Set up the development environment" + # Description-only (uses default "echo Hello" command) + placeholder: + description: "Placeholder method for testing" + # Custom timeout for long-running operations + long_backup: + command: "tar -czf backup.tar.gz /data && rsync backup.tar.gz remote:/backups/" + description: "Create and sync backup (may take a while)" + timeout: 1800 # 30 minutes instead of default 5 minutes + # You can mix both formats + simple_echo: "echo 'simple'" # optional parameters cwd: "/tmp" log_level: "INFO" @@ -34,6 +64,25 @@ export: - "-c" ``` +### Configuration Parameters + +| Parameter | Description | Type | Required | Default | +|-----------|-------------|------|----------|---------| +| `methods` | Dictionary of methods. Values can be:
- String: just the command
- Dict: `{command: "...", description: "...", timeout: ...}` | `dict[str, str \| dict]` | Yes | - | +| `cwd` | Working directory for shell commands | `str` | No | `None` | +| `log_level` | Logging level | `str` | No | `"INFO"` | +| `shell` | Shell command to execute scripts | `list[str]` | No | `["bash", "-c"]` | +| `timeout` | Command timeout in seconds | `int` | No | `300` | + +**Method Configuration Options:** + +For the dict format, each method supports: +- `command`: The shell command to execute (optional, defaults to `"echo Hello"`) +- `description`: CLI help text (optional, defaults to `"Execute the {method_name} shell method"`) +- `timeout`: Command-specific timeout in seconds (optional, defaults to global `timeout` value) + +**Note:** You can mix both formats in the same configuration - use string format for simple commands and dict format when you want custom descriptions or timeouts. + ## API Reference Assuming the exporter driver is configured as in the example above, the client @@ -66,7 +115,11 @@ methods will be generated dynamically, and they will be available as follows: ## CLI Usage -The shell driver also provides a CLI when using `jmp shell`. All configured methods become available as CLI commands, except for methods starting with `_` which are considered private and hidden from the end user: +The shell driver also provides a CLI when using `jmp shell`. All configured methods become available as CLI commands, except for methods starting with `_` which are considered private and hidden from the end user. + +### CLI Help Output + +With unified format (custom descriptions): ```console $ jmp shell --exporter shell-exporter @@ -76,10 +129,40 @@ Usage: j shell [OPTIONS] COMMAND [ARGS]... Shell command executor Commands: - env_var Execute the env_var shell method - ls Execute the ls shell method - method2 Execute the method2 shell method - method3 Execute the method3 shell method + deploy Deploy application using Ansible + ls List directory contents with details + setup Set up the development environment +``` + +With simple string format (default descriptions): + +```console +$ j shell +Usage: j shell [OPTIONS] COMMAND [ARGS]... + + Shell command executor + +Commands: + deploy Execute the deploy shell method + ls Execute the ls shell method + setup Execute the setup shell method +``` + +**Mixed format example:** + +```yaml +methods: + deploy: + command: "ansible-playbook deploy.yml" + description: "Deploy using Ansible" + restart: "systemctl restart myapp" # Simple format +``` + +Results in: +```console +Commands: + deploy Deploy using Ansible + restart Execute the restart shell method ``` ### CLI Command Usage diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py index ef8cee2f6..ea54137ed 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py @@ -58,13 +58,6 @@ def base(): def _add_method_command(self, group, method_name): """Add a Click command for a specific shell method""" - @group.command( - name=method_name, - context_settings={"ignore_unknown_options": True, "allow_interspersed_args": False}, - ) - @click.argument('args', nargs=-1, type=click.UNPROCESSED) - @click.option('--env', '-e', multiple=True, - help='Environment variables in KEY=VALUE format') def method_command(args, env): # Parse environment variables env_dict = {} @@ -81,5 +74,18 @@ def method_command(args, env): if returncode != 0: raise click.exceptions.Exit(returncode) - # Update the docstring dynamically - method_command.__doc__ = f"Execute the {method_name} shell method" + # Try to get custom description, fall back to default for older than 0.7 servers + try: + description = self.call("get_method_description", method_name) + except Exception: + description = f"Execute the {method_name} shell method" + + # Decorate and register the command + method_command.__doc__ = description + method_command = click.argument('args', nargs=-1, type=click.UNPROCESSED)(method_command) + method_command = click.option('--env', '-e', multiple=True, + help='Environment variables in KEY=VALUE format')(method_command) + method_command = group.command( + name=method_name, + context_settings={"ignore_unknown_options": True, "allow_interspersed_args": False}, + )(method_command) diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py index f790a30f2..f504b1730 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py @@ -12,13 +12,36 @@ class Shell(Driver): """shell driver for Jumpstarter""" - # methods field is used to define the methods exported, and the shell script - # to be executed by each method - methods: dict[str, str] + # methods field defines the methods exported and their shell scripts + # Supports two formats: + # 1. Simple string: method_name: "command" + # 2. Dict with description: method_name: {command: "...", description: "...", timeout: ...} + methods: dict[str, str | dict[str, str | int]] shell: list[str] = field(default_factory=lambda: ["bash", "-c"]) timeout: int = 300 cwd: str | None = None + def _get_method_command(self, method: str) -> str: + """Extract the command string from a method configuration""" + method_config = self.methods[method] + if isinstance(method_config, str): + return method_config + return method_config.get("command", "echo Hello") + + def _get_method_description(self, method: str) -> str: + """Extract the description from a method configuration""" + method_config = self.methods[method] + if isinstance(method_config, str): + return f"Execute the {method} shell method" + return method_config.get("description", f"Execute the {method} shell method") + + def _get_method_timeout(self, method: str) -> int: + """Extract the timeout from a method configuration, fallback to global timeout""" + method_config = self.methods[method] + if isinstance(method_config, str): + return self.timeout + return method_config.get("timeout", self.timeout) + @classmethod def client(cls) -> str: return "jumpstarter_driver_shell.client.ShellClient" @@ -29,6 +52,11 @@ def get_methods(self) -> list[str]: self.logger.debug(f"get_methods called, returning methods: {methods}") return methods + @export + def get_method_description(self, method: str) -> str: + """Get the description for a specific method""" + return self._get_method_description(method) + @export async def call_method(self, method: str, env, *args) -> AsyncGenerator[tuple[str, str, int | None], None]: """ @@ -39,12 +67,13 @@ async def call_method(self, method: str, env, *args) -> AsyncGenerator[tuple[str self.logger.info(f"calling {method} with args: {args} and kwargs as env: {env}") if method not in self.methods: raise ValueError(f"Method '{method}' not found in available methods: {list(self.methods.keys())}") - script = self.methods[method] - self.logger.debug(f"running script: {script}") + script = self._get_method_command(method) + timeout = self._get_method_timeout(method) + self.logger.debug(f"running script: {script} with timeout: {timeout}") try: async for stdout_chunk, stderr_chunk, returncode in self._run_inline_shell_script( - method, script, *args, env_vars=env + method, script, *args, env_vars=env, timeout=timeout ): if stdout_chunk: self.logger.debug(f"{method} stdout:\n{stdout_chunk.rstrip()}") @@ -121,7 +150,7 @@ async def _read_process_output(self, process, read_all=False): return stdout_data, stderr_data async def _run_inline_shell_script( - self, method, script, *args, env_vars=None + self, method, script, *args, env_vars=None, timeout=None ) -> AsyncGenerator[tuple[str, str, int | None], None]: """ Run the given shell script with live streaming output. @@ -130,6 +159,7 @@ async def _run_inline_shell_script( :param script: The shell script contents as a string. :param args: Arguments to pass to the script (mapped to $1, $2, etc. in the script). :param env_vars: A dict of environment variables to make available to the script. + :param timeout: Customized command timeout in seconds. If None, uses global timeout. :yields: Tuples of (stdout_chunk, stderr_chunk, returncode). returncode is None until the process completes. @@ -151,9 +181,12 @@ async def _run_inline_shell_script( # Create a task to monitor the process timeout start_time = asyncio.get_event_loop().time() + if timeout is None: + timeout = self.timeout + # Read output in real-time while process.returncode is None: - if asyncio.get_event_loop().time() - start_time > self.timeout: + if asyncio.get_event_loop().time() - start_time > timeout: # Send SIGTERM to entire process group for graceful termination try: os.killpg(process.pid, signal.SIGTERM) @@ -168,7 +201,7 @@ async def _run_inline_shell_script( self.logger.warning(f"SIGTERM failed to terminate {process.pid}, sending SIGKILL") except (ProcessLookupError, OSError): pass - raise subprocess.TimeoutExpired(cmd, self.timeout) from None + raise subprocess.TimeoutExpired(cmd, timeout) from None try: stdout_data, stderr_data = await self._read_process_output(process, read_all=False) diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py index 4e03b5f64..c3621b021 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py @@ -101,74 +101,155 @@ def test_cli_method_execution(client): """Test that CLI methods can be executed""" cli = client.cli() - # Test that we can get the echo command - echo_command = cli.commands.get('echo') - assert echo_command is not None + # Test that CLI commands exist and have the correct structure + echo_command = cli.commands['echo'] assert echo_command.name == 'echo' def test_cli_includes_all_methods(): - """Test that CLI includes all methods""" - from .driver import Shell - from jumpstarter.common.utils import serve - - shell_instance = Shell( - log_level="DEBUG", + """Test that CLI includes all configured methods with proper names""" + shell = Shell( methods={ - "method1": "echo method1", - "method2": "echo method2", - "method3": "echo method3", - }, + "echo": "echo", + "env": "echo $ENV_VAR", + "multi_line": "echo line1\necho line2", + "exit1": "exit 1", + "stderr": "echo 'error' 1>&2", + } ) - with serve(shell_instance) as test_client: - cli = test_client.cli() + with serve(shell) as client: + cli = client.cli() + + # Check that all methods are in the CLI + expected_methods = {"echo", "env", "multi_line", "exit1", "stderr"} available_commands = set(cli.commands.keys()) - # All methods should be available - expected_methods = {"method1", "method2", "method3"} assert available_commands == expected_methods, f"Expected {expected_methods}, got {available_commands}" def test_cli_exit_codes(): - """Test that CLI commands preserve shell command exit codes""" - import click + """Test that CLI methods correctly exit with shell command return codes""" + shell = Shell( + methods={ + "exit0": "exit 0", + "exit1": "exit 1", + "exit42": "exit 42", + } + ) - from .driver import Shell - from jumpstarter.common.utils import serve + with serve(shell) as client: + # Test successful command (exit 0) + returncode = client.exit0() + assert returncode == 0 - # Create a shell instance with methods that have different exit codes - shell_instance = Shell( - log_level="DEBUG", + # Test failed command (exit 1) + returncode = client.exit1() + assert returncode == 1 + + # Test custom exit code (exit 42) + returncode = client.exit42() + assert returncode == 42 + + +def test_cli_custom_descriptions_unified_format(): + """Test that CLI methods use custom descriptions with unified format""" + shell = Shell( methods={ - "success": "exit 0", - "fail_1": "exit 1", - "fail_42": "exit 42", - }, + "echo": { + "command": "echo", + "description": "Custom echo description" + }, + "test_method": { + "command": "echo 'test'", + "description": "Test method with custom description" + }, + } + ) + + with serve(shell) as client: + cli = client.cli() + + # Check that custom descriptions are used + echo_cmd = cli.commands['echo'] + assert echo_cmd.help == "Custom echo description", f"Expected custom description, got {echo_cmd.help}" + + test_cmd = cli.commands['test_method'] + assert test_cmd.help == "Test method with custom description" + + +def test_cli_default_descriptions(): + """Test that CLI methods use default descriptions when not configured""" + shell = Shell( + methods={ + "echo": "echo", + "test_method": "echo 'test'", + } + # No descriptions configured + ) + + with serve(shell) as client: + cli = client.cli() + + # Check that default descriptions are used + echo_cmd = cli.commands['echo'] + assert echo_cmd.help == "Execute the echo shell method" + + test_cmd = cli.commands['test_method'] + assert test_cmd.help == "Execute the test_method shell method" + + +def test_get_method_description_unified(): + """Test the get_method_description export method with unified format""" + shell = Shell( + methods={ + "method1": { + "command": "echo", + "description": "Custom description for method1" + }, + "method2": "ls", # String format, should use default description + } ) - with serve(shell_instance) as test_client: - cli = test_client.cli() - - # Test successful command (exit 0) - should not raise - success_cmd = cli.commands['success'] - try: - success_cmd.callback([], []) # Call with empty args and env - except click.exceptions.Exit: - raise AssertionError("Success command should not raise Exit exception") from None - - # Test command that exits with code 1 - should raise Exit(1) - fail1_cmd = cli.commands['fail_1'] - try: - fail1_cmd.callback([], []) - raise AssertionError("Command should have raised Exit exception") - except click.exceptions.Exit as e: - assert e.exit_code == 1 - - # Test command that exits with code 42 - should raise Exit(42) - fail42_cmd = cli.commands['fail_42'] - try: - fail42_cmd.callback([], []) - raise AssertionError("Command should have raised Exit exception") - except click.exceptions.Exit as e: - assert e.exit_code == 42 + with serve(shell) as client: + # Test with custom description (unified format) + assert client.call("get_method_description", "method1") == "Custom description for method1" + + # Test with default description (string format) + assert client.call("get_method_description", "method2") == "Execute the method2 shell method" + + +def test_mixed_format_methods(): + """Test that both string and dict formats work together""" + shell = Shell( + methods={ + "simple": "echo 'simple'", + "detailed": { + "command": "echo 'detailed'", + "description": "A detailed command with description" + }, + "default_cmd": { + # No command specified - should use default "echo Hello" + "description": "Method using default command" + } + } + ) + + with serve(shell) as client: + # Test string format works + returncode = client.simple() + assert returncode == 0 + + # Test dict format works + returncode = client.detailed() + assert returncode == 0 + + # Test default command works + returncode = client.default_cmd() + assert returncode == 0 + + # Test CLI descriptions + cli = client.cli() + assert cli.commands['simple'].help == "Execute the simple shell method" + assert cli.commands['detailed'].help == "A detailed command with description" + assert cli.commands['default_cmd'].help == "Method using default command" From c4271f3f01e5fc7055c19d82ad4ec6a9bac49018 Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Fri, 3 Oct 2025 15:57:40 +0200 Subject: [PATCH 2/6] regenerate protobuf for the new description fields from https://github.com/jumpstarter-dev/jumpstarter-protocol/pull/28 --- .../jumpstarter/client/v1/client_pb2.py | 62 +++---- .../jumpstarter/v1/common_pb2.py | 38 +++++ .../jumpstarter/v1/common_pb2_grpc.py | 4 + .../jumpstarter/v1/jumpstarter_pb2.py | 154 ++++++++++-------- .../jumpstarter/v1/jumpstarter_pb2_grpc.py | 88 ++++++++++ .../jumpstarter/v1/kubernetes_pb2.py | 5 +- .../jumpstarter/v1/router_pb2.py | 5 +- 7 files changed, 249 insertions(+), 107 deletions(-) create mode 100644 packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2.py create mode 100644 packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2_grpc.py diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py index 9682b4b6b..be56f7f16 100644 --- a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py @@ -31,22 +31,24 @@ from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 from ...v1 import kubernetes_pb2 as jumpstarter_dot_v1_dot_kubernetes__pb2 +from ...v1 import common_pb2 as jumpstarter_dot_v1_dot_common__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"jumpstarter/client/v1/client.proto\x12\x15jumpstarter.client.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\"\xa1\x02\n\x08\x45xporter\x12\x17\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x08R\x04name\x12\x43\n\x06labels\x18\x02 \x03(\x0b\x32+.jumpstarter.client.v1.Exporter.LabelsEntryR\x06labels\x12\x1b\n\x06online\x18\x03 \x01(\x08\x42\x03\xe0\x41\x03R\x06online\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01:_\xea\x41\\\n\x18jumpstarter.dev/Exporter\x12+namespaces/{namespace}/exporters/{exporter}*\texporters2\x08\x65xporter\"\xed\x06\n\x05Lease\x12\x17\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x08R\x04name\x12\"\n\x08selector\x18\x02 \x01(\tB\x06\xe0\x41\x02\xe0\x41\x05R\x08selector\x12:\n\x08\x64uration\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationB\x03\xe0\x41\x02R\x08\x64uration\x12M\n\x12\x65\x66\x66\x65\x63tive_duration\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationB\x03\xe0\x41\x03R\x11\x65\x66\x66\x65\x63tiveDuration\x12>\n\nbegin_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\tbeginTime\x88\x01\x01\x12V\n\x14\x65\x66\x66\x65\x63tive_begin_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03H\x01R\x12\x65\x66\x66\x65\x63tiveBeginTime\x88\x01\x01\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x02R\x07\x65ndTime\x88\x01\x01\x12R\n\x12\x65\x66\x66\x65\x63tive_end_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03H\x03R\x10\x65\x66\x66\x65\x63tiveEndTime\x88\x01\x01\x12;\n\x06\x63lient\x18\t \x01(\tB\x1e\xe0\x41\x03\xfa\x41\x18\n\x16jumpstarter.dev/ClientH\x04R\x06\x63lient\x88\x01\x01\x12\x41\n\x08\x65xporter\x18\n \x01(\tB \xe0\x41\x03\xfa\x41\x1a\n\x18jumpstarter.dev/ExporterH\x05R\x08\x65xporter\x88\x01\x01\x12>\n\nconditions\x18\x0b \x03(\x0b\x32\x19.jumpstarter.v1.ConditionB\x03\xe0\x41\x03R\nconditions:P\xea\x41M\n\x15jumpstarter.dev/Lease\x12%namespaces/{namespace}/leases/{lease}*\x06leases2\x05leaseB\r\n\x0b_begin_timeB\x17\n\x15_effective_begin_timeB\x0b\n\t_end_timeB\x15\n\x13_effective_end_timeB\t\n\x07_clientB\x0b\n\t_exporter\"J\n\x12GetExporterRequest\x12\x34\n\x04name\x18\x01 \x01(\tB \xe0\x41\x02\xfa\x41\x1a\n\x18jumpstarter.dev/ExporterR\x04name\"\xb3\x01\n\x14ListExportersRequest\x12\x38\n\x06parent\x18\x01 \x01(\tB \xe0\x41\x02\xfa\x41\x1a\x12\x18jumpstarter.dev/ExporterR\x06parent\x12 \n\tpage_size\x18\x02 \x01(\x05\x42\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x03 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"~\n\x15ListExportersResponse\x12=\n\texporters\x18\x01 \x03(\x0b\x32\x1f.jumpstarter.client.v1.ExporterR\texporters\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"D\n\x0fGetLeaseRequest\x12\x31\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15jumpstarter.dev/LeaseR\x04name\"\xad\x01\n\x11ListLeasesRequest\x12\x35\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\x12\x15jumpstarter.dev/LeaseR\x06parent\x12 \n\tpage_size\x18\x02 \x01(\x05\x42\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x03 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"r\n\x12ListLeasesResponse\x12\x34\n\x06leases\x18\x01 \x03(\x0b\x32\x1c.jumpstarter.client.v1.LeaseR\x06leases\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa4\x01\n\x12\x43reateLeaseRequest\x12\x35\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\x12\x15jumpstarter.dev/LeaseR\x06parent\x12\x1e\n\x08lease_id\x18\x02 \x01(\tB\x03\xe0\x41\x01R\x07leaseId\x12\x37\n\x05lease\x18\x03 \x01(\x0b\x32\x1c.jumpstarter.client.v1.LeaseB\x03\xe0\x41\x02R\x05lease\"\x8f\x01\n\x12UpdateLeaseRequest\x12\x37\n\x05lease\x18\x01 \x01(\x0b\x32\x1c.jumpstarter.client.v1.LeaseB\x03\xe0\x41\x02R\x05lease\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"G\n\x12\x44\x65leteLeaseRequest\x12\x31\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15jumpstarter.dev/LeaseR\x04name2\xa7\x08\n\rClientService\x12\x8d\x01\n\x0bGetExporter\x12).jumpstarter.client.v1.GetExporterRequest\x1a\x1f.jumpstarter.client.v1.Exporter\"2\xda\x41\x04name\x82\xd3\xe4\x93\x02%\x12#/v1/{name=namespaces/*/exporters/*}\x12\xa0\x01\n\rListExporters\x12+.jumpstarter.client.v1.ListExportersRequest\x1a,.jumpstarter.client.v1.ListExportersResponse\"4\xda\x41\x06parent\x82\xd3\xe4\x93\x02%\x12#/v1/{parent=namespaces/*}/exporters\x12\x81\x01\n\x08GetLease\x12&.jumpstarter.client.v1.GetLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"/\xda\x41\x04name\x82\xd3\xe4\x93\x02\"\x12 /v1/{name=namespaces/*/leases/*}\x12\x94\x01\n\nListLeases\x12(.jumpstarter.client.v1.ListLeasesRequest\x1a).jumpstarter.client.v1.ListLeasesResponse\"1\xda\x41\x06parent\x82\xd3\xe4\x93\x02\"\x12 /v1/{parent=namespaces/*}/leases\x12\x9f\x01\n\x0b\x43reateLease\x12).jumpstarter.client.v1.CreateLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"G\xda\x41\x15parent,lease,lease_id\x82\xd3\xe4\x93\x02)\" /v1/{parent=namespaces/*}/leases:\x05lease\x12\xa1\x01\n\x0bUpdateLease\x12).jumpstarter.client.v1.UpdateLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"I\xda\x41\x11lease,update_mask\x82\xd3\xe4\x93\x02/2&/v1/{lease.name=namespaces/*/leases/*}:\x05lease\x12\x81\x01\n\x0b\x44\x65leteLease\x12).jumpstarter.client.v1.DeleteLeaseRequest\x1a\x16.google.protobuf.Empty\"/\xda\x41\x04name\x82\xd3\xe4\x93\x02\"* /v1/{name=namespaces/*/leases/*}B\x9e\x01\n\x19\x63om.jumpstarter.client.v1B\x0b\x43lientProtoP\x01\xa2\x02\x03JCX\xaa\x02\x15Jumpstarter.Client.V1\xca\x02\x15Jumpstarter\\Client\\V1\xe2\x02!Jumpstarter\\Client\\V1\\GPBMetadata\xea\x02\x17Jumpstarter::Client::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"jumpstarter/client/v1/client.proto\x12\x15jumpstarter.client.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\x1a\x1bjumpstarter/v1/common.proto\"\xe0\x02\n\x08\x45xporter\x12\x17\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x08R\x04name\x12\x43\n\x06labels\x18\x02 \x03(\x0b\x32+.jumpstarter.client.v1.Exporter.LabelsEntryR\x06labels\x12\x1d\n\x06online\x18\x03 \x01(\x08\x42\x05\x18\x01\xe0\x41\x03R\x06online\x12;\n\x06status\x18\x04 \x01(\x0e\x32\x1e.jumpstarter.v1.ExporterStatusB\x03\xe0\x41\x03R\x06status\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01:_\xea\x41\\\n\x18jumpstarter.dev/Exporter\x12+namespaces/{namespace}/exporters/{exporter}*\texporters2\x08\x65xporter\"\xed\x06\n\x05Lease\x12\x17\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x08R\x04name\x12\"\n\x08selector\x18\x02 \x01(\tB\x06\xe0\x41\x02\xe0\x41\x05R\x08selector\x12:\n\x08\x64uration\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationB\x03\xe0\x41\x02R\x08\x64uration\x12M\n\x12\x65\x66\x66\x65\x63tive_duration\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationB\x03\xe0\x41\x03R\x11\x65\x66\x66\x65\x63tiveDuration\x12>\n\nbegin_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\tbeginTime\x88\x01\x01\x12V\n\x14\x65\x66\x66\x65\x63tive_begin_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03H\x01R\x12\x65\x66\x66\x65\x63tiveBeginTime\x88\x01\x01\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x02R\x07\x65ndTime\x88\x01\x01\x12R\n\x12\x65\x66\x66\x65\x63tive_end_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03H\x03R\x10\x65\x66\x66\x65\x63tiveEndTime\x88\x01\x01\x12;\n\x06\x63lient\x18\t \x01(\tB\x1e\xe0\x41\x03\xfa\x41\x18\n\x16jumpstarter.dev/ClientH\x04R\x06\x63lient\x88\x01\x01\x12\x41\n\x08\x65xporter\x18\n \x01(\tB \xe0\x41\x03\xfa\x41\x1a\n\x18jumpstarter.dev/ExporterH\x05R\x08\x65xporter\x88\x01\x01\x12>\n\nconditions\x18\x0b \x03(\x0b\x32\x19.jumpstarter.v1.ConditionB\x03\xe0\x41\x03R\nconditions:P\xea\x41M\n\x15jumpstarter.dev/Lease\x12%namespaces/{namespace}/leases/{lease}*\x06leases2\x05leaseB\r\n\x0b_begin_timeB\x17\n\x15_effective_begin_timeB\x0b\n\t_end_timeB\x15\n\x13_effective_end_timeB\t\n\x07_clientB\x0b\n\t_exporter\"J\n\x12GetExporterRequest\x12\x34\n\x04name\x18\x01 \x01(\tB \xe0\x41\x02\xfa\x41\x1a\n\x18jumpstarter.dev/ExporterR\x04name\"\xb3\x01\n\x14ListExportersRequest\x12\x38\n\x06parent\x18\x01 \x01(\tB \xe0\x41\x02\xfa\x41\x1a\x12\x18jumpstarter.dev/ExporterR\x06parent\x12 \n\tpage_size\x18\x02 \x01(\x05\x42\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x03 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"~\n\x15ListExportersResponse\x12=\n\texporters\x18\x01 \x03(\x0b\x32\x1f.jumpstarter.client.v1.ExporterR\texporters\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"D\n\x0fGetLeaseRequest\x12\x31\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15jumpstarter.dev/LeaseR\x04name\"\xad\x01\n\x11ListLeasesRequest\x12\x35\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\x12\x15jumpstarter.dev/LeaseR\x06parent\x12 \n\tpage_size\x18\x02 \x01(\x05\x42\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x03 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"r\n\x12ListLeasesResponse\x12\x34\n\x06leases\x18\x01 \x03(\x0b\x32\x1c.jumpstarter.client.v1.LeaseR\x06leases\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa4\x01\n\x12\x43reateLeaseRequest\x12\x35\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\x12\x15jumpstarter.dev/LeaseR\x06parent\x12\x1e\n\x08lease_id\x18\x02 \x01(\tB\x03\xe0\x41\x01R\x07leaseId\x12\x37\n\x05lease\x18\x03 \x01(\x0b\x32\x1c.jumpstarter.client.v1.LeaseB\x03\xe0\x41\x02R\x05lease\"\x8f\x01\n\x12UpdateLeaseRequest\x12\x37\n\x05lease\x18\x01 \x01(\x0b\x32\x1c.jumpstarter.client.v1.LeaseB\x03\xe0\x41\x02R\x05lease\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"G\n\x12\x44\x65leteLeaseRequest\x12\x31\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15jumpstarter.dev/LeaseR\x04name2\xa7\x08\n\rClientService\x12\x8d\x01\n\x0bGetExporter\x12).jumpstarter.client.v1.GetExporterRequest\x1a\x1f.jumpstarter.client.v1.Exporter\"2\xda\x41\x04name\x82\xd3\xe4\x93\x02%\x12#/v1/{name=namespaces/*/exporters/*}\x12\xa0\x01\n\rListExporters\x12+.jumpstarter.client.v1.ListExportersRequest\x1a,.jumpstarter.client.v1.ListExportersResponse\"4\xda\x41\x06parent\x82\xd3\xe4\x93\x02%\x12#/v1/{parent=namespaces/*}/exporters\x12\x81\x01\n\x08GetLease\x12&.jumpstarter.client.v1.GetLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"/\xda\x41\x04name\x82\xd3\xe4\x93\x02\"\x12 /v1/{name=namespaces/*/leases/*}\x12\x94\x01\n\nListLeases\x12(.jumpstarter.client.v1.ListLeasesRequest\x1a).jumpstarter.client.v1.ListLeasesResponse\"1\xda\x41\x06parent\x82\xd3\xe4\x93\x02\"\x12 /v1/{parent=namespaces/*}/leases\x12\x9f\x01\n\x0b\x43reateLease\x12).jumpstarter.client.v1.CreateLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"G\xda\x41\x15parent,lease,lease_id\x82\xd3\xe4\x93\x02)\" /v1/{parent=namespaces/*}/leases:\x05lease\x12\xa1\x01\n\x0bUpdateLease\x12).jumpstarter.client.v1.UpdateLeaseRequest\x1a\x1c.jumpstarter.client.v1.Lease\"I\xda\x41\x11lease,update_mask\x82\xd3\xe4\x93\x02/2&/v1/{lease.name=namespaces/*/leases/*}:\x05lease\x12\x81\x01\n\x0b\x44\x65leteLease\x12).jumpstarter.client.v1.DeleteLeaseRequest\x1a\x16.google.protobuf.Empty\"/\xda\x41\x04name\x82\xd3\xe4\x93\x02\"* /v1/{name=namespaces/*/leases/*}b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jumpstarter.client.v1.client_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\031com.jumpstarter.client.v1B\013ClientProtoP\001\242\002\003JCX\252\002\025Jumpstarter.Client.V1\312\002\025Jumpstarter\\Client\\V1\342\002!Jumpstarter\\Client\\V1\\GPBMetadata\352\002\027Jumpstarter::Client::V1' + DESCRIPTOR._loaded_options = None _globals['_EXPORTER_LABELSENTRY']._loaded_options = None _globals['_EXPORTER_LABELSENTRY']._serialized_options = b'8\001' _globals['_EXPORTER'].fields_by_name['name']._loaded_options = None _globals['_EXPORTER'].fields_by_name['name']._serialized_options = b'\340A\010' _globals['_EXPORTER'].fields_by_name['online']._loaded_options = None - _globals['_EXPORTER'].fields_by_name['online']._serialized_options = b'\340A\003' + _globals['_EXPORTER'].fields_by_name['online']._serialized_options = b'\030\001\340A\003' + _globals['_EXPORTER'].fields_by_name['status']._loaded_options = None + _globals['_EXPORTER'].fields_by_name['status']._serialized_options = b'\340A\003' _globals['_EXPORTER']._loaded_options = None _globals['_EXPORTER']._serialized_options = b'\352A\\\n\030jumpstarter.dev/Exporter\022+namespaces/{namespace}/exporters/{exporter}*\texporters2\010exporter' _globals['_LEASE'].fields_by_name['name']._loaded_options = None @@ -115,30 +117,30 @@ _globals['_CLIENTSERVICE'].methods_by_name['UpdateLease']._serialized_options = b'\332A\021lease,update_mask\202\323\344\223\002/2&/v1/{lease.name=namespaces/*/leases/*}:\005lease' _globals['_CLIENTSERVICE'].methods_by_name['DeleteLease']._loaded_options = None _globals['_CLIENTSERVICE'].methods_by_name['DeleteLease']._serialized_options = b'\332A\004name\202\323\344\223\002\"* /v1/{name=namespaces/*/leases/*}' - _globals['_EXPORTER']._serialized_start=338 - _globals['_EXPORTER']._serialized_end=627 - _globals['_EXPORTER_LABELSENTRY']._serialized_start=473 - _globals['_EXPORTER_LABELSENTRY']._serialized_end=530 - _globals['_LEASE']._serialized_start=630 - _globals['_LEASE']._serialized_end=1507 - _globals['_GETEXPORTERREQUEST']._serialized_start=1509 - _globals['_GETEXPORTERREQUEST']._serialized_end=1583 - _globals['_LISTEXPORTERSREQUEST']._serialized_start=1586 - _globals['_LISTEXPORTERSREQUEST']._serialized_end=1765 - _globals['_LISTEXPORTERSRESPONSE']._serialized_start=1767 - _globals['_LISTEXPORTERSRESPONSE']._serialized_end=1893 - _globals['_GETLEASEREQUEST']._serialized_start=1895 - _globals['_GETLEASEREQUEST']._serialized_end=1963 - _globals['_LISTLEASESREQUEST']._serialized_start=1966 - _globals['_LISTLEASESREQUEST']._serialized_end=2139 - _globals['_LISTLEASESRESPONSE']._serialized_start=2141 - _globals['_LISTLEASESRESPONSE']._serialized_end=2255 - _globals['_CREATELEASEREQUEST']._serialized_start=2258 - _globals['_CREATELEASEREQUEST']._serialized_end=2422 - _globals['_UPDATELEASEREQUEST']._serialized_start=2425 - _globals['_UPDATELEASEREQUEST']._serialized_end=2568 - _globals['_DELETELEASEREQUEST']._serialized_start=2570 - _globals['_DELETELEASEREQUEST']._serialized_end=2641 - _globals['_CLIENTSERVICE']._serialized_start=2644 - _globals['_CLIENTSERVICE']._serialized_end=3707 + _globals['_EXPORTER']._serialized_start=367 + _globals['_EXPORTER']._serialized_end=719 + _globals['_EXPORTER_LABELSENTRY']._serialized_start=565 + _globals['_EXPORTER_LABELSENTRY']._serialized_end=622 + _globals['_LEASE']._serialized_start=722 + _globals['_LEASE']._serialized_end=1599 + _globals['_GETEXPORTERREQUEST']._serialized_start=1601 + _globals['_GETEXPORTERREQUEST']._serialized_end=1675 + _globals['_LISTEXPORTERSREQUEST']._serialized_start=1678 + _globals['_LISTEXPORTERSREQUEST']._serialized_end=1857 + _globals['_LISTEXPORTERSRESPONSE']._serialized_start=1859 + _globals['_LISTEXPORTERSRESPONSE']._serialized_end=1985 + _globals['_GETLEASEREQUEST']._serialized_start=1987 + _globals['_GETLEASEREQUEST']._serialized_end=2055 + _globals['_LISTLEASESREQUEST']._serialized_start=2058 + _globals['_LISTLEASESREQUEST']._serialized_end=2231 + _globals['_LISTLEASESRESPONSE']._serialized_start=2233 + _globals['_LISTLEASESRESPONSE']._serialized_end=2347 + _globals['_CREATELEASEREQUEST']._serialized_start=2350 + _globals['_CREATELEASEREQUEST']._serialized_end=2514 + _globals['_UPDATELEASEREQUEST']._serialized_start=2517 + _globals['_UPDATELEASEREQUEST']._serialized_end=2660 + _globals['_DELETELEASEREQUEST']._serialized_start=2662 + _globals['_DELETELEASEREQUEST']._serialized_end=2733 + _globals['_CLIENTSERVICE']._serialized_start=2736 + _globals['_CLIENTSERVICE']._serialized_end=3799 # @@protoc_insertion_point(module_scope) diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2.py new file mode 100644 index 000000000..6349b917c --- /dev/null +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: jumpstarter/v1/common.proto +# Protobuf Python Version: 6.30.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 30, + 1, + '', + 'jumpstarter/v1/common.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bjumpstarter/v1/common.proto\x12\x0ejumpstarter.v1*\xb6\x02\n\x0e\x45xporterStatus\x12\x1f\n\x1b\x45XPORTER_STATUS_UNSPECIFIED\x10\x00\x12\x1b\n\x17\x45XPORTER_STATUS_OFFLINE\x10\x01\x12\x1d\n\x19\x45XPORTER_STATUS_AVAILABLE\x10\x02\x12%\n!EXPORTER_STATUS_BEFORE_LEASE_HOOK\x10\x03\x12\x1f\n\x1b\x45XPORTER_STATUS_LEASE_READY\x10\x04\x12$\n EXPORTER_STATUS_AFTER_LEASE_HOOK\x10\x05\x12,\n(EXPORTER_STATUS_BEFORE_LEASE_HOOK_FAILED\x10\x06\x12+\n\'EXPORTER_STATUS_AFTER_LEASE_HOOK_FAILED\x10\x07*\x98\x01\n\tLogSource\x12\x1a\n\x16LOG_SOURCE_UNSPECIFIED\x10\x00\x12\x15\n\x11LOG_SOURCE_DRIVER\x10\x01\x12 \n\x1cLOG_SOURCE_BEFORE_LEASE_HOOK\x10\x02\x12\x1f\n\x1bLOG_SOURCE_AFTER_LEASE_HOOK\x10\x03\x12\x15\n\x11LOG_SOURCE_SYSTEM\x10\x04\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jumpstarter.v1.common_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_EXPORTERSTATUS']._serialized_start=48 + _globals['_EXPORTERSTATUS']._serialized_end=358 + _globals['_LOGSOURCE']._serialized_start=361 + _globals['_LOGSOURCE']._serialized_end=513 +# @@protoc_insertion_point(module_scope) diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2_grpc.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2_grpc.py new file mode 100644 index 000000000..2daafffeb --- /dev/null +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/common_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py index d75ddd9d4..10892f748 100644 --- a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py @@ -27,88 +27,100 @@ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 from . import kubernetes_pb2 as jumpstarter_dot_v1_dot_kubernetes__pb2 +from . import common_pb2 as jumpstarter_dot_v1_dot_common__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n jumpstarter/v1/jumpstarter.proto\x12\x0ejumpstarter.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\"\xd1\x01\n\x0fRegisterRequest\x12\x43\n\x06labels\x18\x01 \x03(\x0b\x32+.jumpstarter.v1.RegisterRequest.LabelsEntryR\x06labels\x12>\n\x07reports\x18\x02 \x03(\x0b\x32$.jumpstarter.v1.DriverInstanceReportR\x07reports\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xe5\x01\n\x14\x44riverInstanceReport\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12$\n\x0bparent_uuid\x18\x02 \x01(\tH\x00R\nparentUuid\x88\x01\x01\x12H\n\x06labels\x18\x03 \x03(\x0b\x32\x30.jumpstarter.v1.DriverInstanceReport.LabelsEntryR\x06labels\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\x0e\n\x0c_parent_uuid\"&\n\x10RegisterResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\"+\n\x11UnregisterRequest\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\"\x14\n\x12UnregisterResponse\".\n\rListenRequest\x12\x1d\n\nlease_name\x18\x01 \x01(\tR\tleaseName\"\\\n\x0eListenResponse\x12\'\n\x0frouter_endpoint\x18\x01 \x01(\tR\x0erouterEndpoint\x12!\n\x0crouter_token\x18\x02 \x01(\tR\x0brouterToken\"\x0f\n\rStatusRequest\"\x91\x01\n\x0eStatusResponse\x12\x16\n\x06leased\x18\x01 \x01(\x08R\x06leased\x12\"\n\nlease_name\x18\x02 \x01(\tH\x00R\tleaseName\x88\x01\x01\x12$\n\x0b\x63lient_name\x18\x03 \x01(\tH\x01R\nclientName\x88\x01\x01\x42\r\n\x0b_lease_nameB\x0e\n\x0c_client_name\",\n\x0b\x44ialRequest\x12\x1d\n\nlease_name\x18\x01 \x01(\tR\tleaseName\"Z\n\x0c\x44ialResponse\x12\'\n\x0frouter_endpoint\x18\x01 \x01(\tR\x0erouterEndpoint\x12!\n\x0crouter_token\x18\x02 \x01(\tR\x0brouterToken\"\xa1\x01\n\x12\x41uditStreamRequest\x12#\n\rexporter_uuid\x18\x01 \x01(\tR\x0c\x65xporterUuid\x12\x30\n\x14\x64river_instance_uuid\x18\x02 \x01(\tR\x12\x64riverInstanceUuid\x12\x1a\n\x08severity\x18\x03 \x01(\tR\x08severity\x12\x18\n\x07message\x18\x04 \x01(\tR\x07message\"\xb8\x02\n\x11GetReportResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x45\n\x06labels\x18\x02 \x03(\x0b\x32-.jumpstarter.v1.GetReportResponse.LabelsEntryR\x06labels\x12>\n\x07reports\x18\x03 \x03(\x0b\x32$.jumpstarter.v1.DriverInstanceReportR\x07reports\x12M\n\x15\x61lternative_endpoints\x18\x04 \x03(\x0b\x32\x18.jumpstarter.v1.EndpointR\x14\x61lternativeEndpoints\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xa5\x01\n\x08\x45ndpoint\x12\x1a\n\x08\x65ndpoint\x18\x01 \x01(\tR\x08\x65ndpoint\x12 \n\x0b\x63\x65rtificate\x18\x02 \x01(\tR\x0b\x63\x65rtificate\x12-\n\x12\x63lient_certificate\x18\x03 \x01(\tR\x11\x63lientCertificate\x12,\n\x12\x63lient_private_key\x18\x04 \x01(\tR\x10\x63lientPrivateKey\"k\n\x11\x44riverCallRequest\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12*\n\x04\x61rgs\x18\x03 \x03(\x0b\x32\x16.google.protobuf.ValueR\x04\x61rgs\"X\n\x12\x44riverCallResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12.\n\x06result\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x06result\"t\n\x1aStreamingDriverCallRequest\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12*\n\x04\x61rgs\x18\x03 \x03(\x0b\x32\x16.google.protobuf.ValueR\x04\x61rgs\"a\n\x1bStreamingDriverCallResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12.\n\x06result\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x06result\"]\n\x11LogStreamResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1a\n\x08severity\x18\x02 \x01(\tR\x08severity\x12\x18\n\x07message\x18\x03 \x01(\tR\x07message\"\x0e\n\x0cResetRequest\"\x0f\n\rResetResponse\"%\n\x0fGetLeaseRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\x93\x03\n\x10GetLeaseResponse\x12\x35\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x39\n\x08selector\x18\x02 \x01(\x0b\x32\x1d.jumpstarter.v1.LabelSelectorR\x08selector\x12>\n\nbegin_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\tbeginTime\x88\x01\x01\x12:\n\x08\x65nd_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x01R\x07\x65ndTime\x88\x01\x01\x12(\n\rexporter_uuid\x18\x05 \x01(\tH\x02R\x0c\x65xporterUuid\x88\x01\x01\x12\x39\n\nconditions\x18\x06 \x03(\x0b\x32\x19.jumpstarter.v1.ConditionR\nconditionsB\r\n\x0b_begin_timeB\x0b\n\t_end_timeB\x10\n\x0e_exporter_uuid\"\x87\x01\n\x13RequestLeaseRequest\x12\x35\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x39\n\x08selector\x18\x02 \x01(\x0b\x32\x1d.jumpstarter.v1.LabelSelectorR\x08selector\"*\n\x14RequestLeaseResponse\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\")\n\x13ReleaseLeaseRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\x16\n\x14ReleaseLeaseResponse\"\x13\n\x11ListLeasesRequest\"*\n\x12ListLeasesResponse\x12\x14\n\x05names\x18\x01 \x03(\tR\x05names2\xb7\x06\n\x11\x43ontrollerService\x12M\n\x08Register\x12\x1f.jumpstarter.v1.RegisterRequest\x1a .jumpstarter.v1.RegisterResponse\x12S\n\nUnregister\x12!.jumpstarter.v1.UnregisterRequest\x1a\".jumpstarter.v1.UnregisterResponse\x12I\n\x06Listen\x12\x1d.jumpstarter.v1.ListenRequest\x1a\x1e.jumpstarter.v1.ListenResponse0\x01\x12I\n\x06Status\x12\x1d.jumpstarter.v1.StatusRequest\x1a\x1e.jumpstarter.v1.StatusResponse0\x01\x12\x41\n\x04\x44ial\x12\x1b.jumpstarter.v1.DialRequest\x1a\x1c.jumpstarter.v1.DialResponse\x12K\n\x0b\x41uditStream\x12\".jumpstarter.v1.AuditStreamRequest\x1a\x16.google.protobuf.Empty(\x01\x12M\n\x08GetLease\x12\x1f.jumpstarter.v1.GetLeaseRequest\x1a .jumpstarter.v1.GetLeaseResponse\x12Y\n\x0cRequestLease\x12#.jumpstarter.v1.RequestLeaseRequest\x1a$.jumpstarter.v1.RequestLeaseResponse\x12Y\n\x0cReleaseLease\x12#.jumpstarter.v1.ReleaseLeaseRequest\x1a$.jumpstarter.v1.ReleaseLeaseResponse\x12S\n\nListLeases\x12!.jumpstarter.v1.ListLeasesRequest\x1a\".jumpstarter.v1.ListLeasesResponse2\xb0\x03\n\x0f\x45xporterService\x12\x46\n\tGetReport\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.GetReportResponse\x12S\n\nDriverCall\x12!.jumpstarter.v1.DriverCallRequest\x1a\".jumpstarter.v1.DriverCallResponse\x12p\n\x13StreamingDriverCall\x12*.jumpstarter.v1.StreamingDriverCallRequest\x1a+.jumpstarter.v1.StreamingDriverCallResponse0\x01\x12H\n\tLogStream\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.LogStreamResponse0\x01\x12\x44\n\x05Reset\x12\x1c.jumpstarter.v1.ResetRequest\x1a\x1d.jumpstarter.v1.ResetResponseB\x7f\n\x12\x63om.jumpstarter.v1B\x10JumpstarterProtoP\x01\xa2\x02\x03JXX\xaa\x02\x0eJumpstarter.V1\xca\x02\x0eJumpstarter\\V1\xe2\x02\x1aJumpstarter\\V1\\GPBMetadata\xea\x02\x0fJumpstarter::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n jumpstarter/v1/jumpstarter.proto\x12\x0ejumpstarter.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fjumpstarter/v1/kubernetes.proto\x1a\x1bjumpstarter/v1/common.proto\"\xd1\x01\n\x0fRegisterRequest\x12\x43\n\x06labels\x18\x01 \x03(\x0b\x32+.jumpstarter.v1.RegisterRequest.LabelsEntryR\x06labels\x12>\n\x07reports\x18\x02 \x03(\x0b\x32$.jumpstarter.v1.DriverInstanceReportR\x07reports\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xd2\x03\n\x14\x44riverInstanceReport\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12$\n\x0bparent_uuid\x18\x02 \x01(\tH\x00R\nparentUuid\x88\x01\x01\x12H\n\x06labels\x18\x03 \x03(\x0b\x32\x30.jumpstarter.v1.DriverInstanceReport.LabelsEntryR\x06labels\x12%\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01R\x0b\x64\x65scription\x88\x01\x01\x12m\n\x13methods_description\x18\x05 \x03(\x0b\x32<.jumpstarter.v1.DriverInstanceReport.MethodsDescriptionEntryR\x12methodsDescription\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x1a\x45\n\x17MethodsDescriptionEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\x0e\n\x0c_parent_uuidB\x0e\n\x0c_description\"&\n\x10RegisterResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\"+\n\x11UnregisterRequest\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\"\x14\n\x12UnregisterResponse\".\n\rListenRequest\x12\x1d\n\nlease_name\x18\x01 \x01(\tR\tleaseName\"\\\n\x0eListenResponse\x12\'\n\x0frouter_endpoint\x18\x01 \x01(\tR\x0erouterEndpoint\x12!\n\x0crouter_token\x18\x02 \x01(\tR\x0brouterToken\"\x0f\n\rStatusRequest\"\x91\x01\n\x0eStatusResponse\x12\x16\n\x06leased\x18\x01 \x01(\x08R\x06leased\x12\"\n\nlease_name\x18\x02 \x01(\tH\x00R\tleaseName\x88\x01\x01\x12$\n\x0b\x63lient_name\x18\x03 \x01(\tH\x01R\nclientName\x88\x01\x01\x42\r\n\x0b_lease_nameB\x0e\n\x0c_client_name\",\n\x0b\x44ialRequest\x12\x1d\n\nlease_name\x18\x01 \x01(\tR\tleaseName\"Z\n\x0c\x44ialResponse\x12\'\n\x0frouter_endpoint\x18\x01 \x01(\tR\x0erouterEndpoint\x12!\n\x0crouter_token\x18\x02 \x01(\tR\x0brouterToken\"\xa1\x01\n\x12\x41uditStreamRequest\x12#\n\rexporter_uuid\x18\x01 \x01(\tR\x0c\x65xporterUuid\x12\x30\n\x14\x64river_instance_uuid\x18\x02 \x01(\tR\x12\x64riverInstanceUuid\x12\x1a\n\x08severity\x18\x03 \x01(\tR\x08severity\x12\x18\n\x07message\x18\x04 \x01(\tR\x07message\"x\n\x13ReportStatusRequest\x12\x36\n\x06status\x18\x01 \x01(\x0e\x32\x1e.jumpstarter.v1.ExporterStatusR\x06status\x12\x1d\n\x07message\x18\x02 \x01(\tH\x00R\x07message\x88\x01\x01\x42\n\n\x08_message\"\x16\n\x14ReportStatusResponse\"\xb8\x02\n\x11GetReportResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x45\n\x06labels\x18\x02 \x03(\x0b\x32-.jumpstarter.v1.GetReportResponse.LabelsEntryR\x06labels\x12>\n\x07reports\x18\x03 \x03(\x0b\x32$.jumpstarter.v1.DriverInstanceReportR\x07reports\x12M\n\x15\x61lternative_endpoints\x18\x04 \x03(\x0b\x32\x18.jumpstarter.v1.EndpointR\x14\x61lternativeEndpoints\x1a\x39\n\x0bLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xa5\x01\n\x08\x45ndpoint\x12\x1a\n\x08\x65ndpoint\x18\x01 \x01(\tR\x08\x65ndpoint\x12 \n\x0b\x63\x65rtificate\x18\x02 \x01(\tR\x0b\x63\x65rtificate\x12-\n\x12\x63lient_certificate\x18\x03 \x01(\tR\x11\x63lientCertificate\x12,\n\x12\x63lient_private_key\x18\x04 \x01(\tR\x10\x63lientPrivateKey\"k\n\x11\x44riverCallRequest\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12*\n\x04\x61rgs\x18\x03 \x03(\x0b\x32\x16.google.protobuf.ValueR\x04\x61rgs\"X\n\x12\x44riverCallResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12.\n\x06result\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x06result\"t\n\x1aStreamingDriverCallRequest\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12*\n\x04\x61rgs\x18\x03 \x03(\x0b\x32\x16.google.protobuf.ValueR\x04\x61rgs\"a\n\x1bStreamingDriverCallResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12.\n\x06result\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x06result\"\xa0\x01\n\x11LogStreamResponse\x12\x12\n\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1a\n\x08severity\x18\x02 \x01(\tR\x08severity\x12\x18\n\x07message\x18\x03 \x01(\tR\x07message\x12\x36\n\x06source\x18\x04 \x01(\x0e\x32\x19.jumpstarter.v1.LogSourceH\x00R\x06source\x88\x01\x01\x42\t\n\x07_source\"\x0e\n\x0cResetRequest\"\x0f\n\rResetResponse\"%\n\x0fGetLeaseRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\x93\x03\n\x10GetLeaseResponse\x12\x35\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x39\n\x08selector\x18\x02 \x01(\x0b\x32\x1d.jumpstarter.v1.LabelSelectorR\x08selector\x12>\n\nbegin_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\tbeginTime\x88\x01\x01\x12:\n\x08\x65nd_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x01R\x07\x65ndTime\x88\x01\x01\x12(\n\rexporter_uuid\x18\x05 \x01(\tH\x02R\x0c\x65xporterUuid\x88\x01\x01\x12\x39\n\nconditions\x18\x06 \x03(\x0b\x32\x19.jumpstarter.v1.ConditionR\nconditionsB\r\n\x0b_begin_timeB\x0b\n\t_end_timeB\x10\n\x0e_exporter_uuid\"\x87\x01\n\x13RequestLeaseRequest\x12\x35\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x39\n\x08selector\x18\x02 \x01(\x0b\x32\x1d.jumpstarter.v1.LabelSelectorR\x08selector\"*\n\x14RequestLeaseResponse\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\")\n\x13ReleaseLeaseRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\x16\n\x14ReleaseLeaseResponse\"\x13\n\x11ListLeasesRequest\"*\n\x12ListLeasesResponse\x12\x14\n\x05names\x18\x01 \x03(\tR\x05names\"\x12\n\x10GetStatusRequest\"v\n\x11GetStatusResponse\x12\x36\n\x06status\x18\x01 \x01(\x0e\x32\x1e.jumpstarter.v1.ExporterStatusR\x06status\x12\x1d\n\x07message\x18\x02 \x01(\tH\x00R\x07message\x88\x01\x01\x42\n\n\x08_message2\x92\x07\n\x11\x43ontrollerService\x12M\n\x08Register\x12\x1f.jumpstarter.v1.RegisterRequest\x1a .jumpstarter.v1.RegisterResponse\x12S\n\nUnregister\x12!.jumpstarter.v1.UnregisterRequest\x1a\".jumpstarter.v1.UnregisterResponse\x12Y\n\x0cReportStatus\x12#.jumpstarter.v1.ReportStatusRequest\x1a$.jumpstarter.v1.ReportStatusResponse\x12I\n\x06Listen\x12\x1d.jumpstarter.v1.ListenRequest\x1a\x1e.jumpstarter.v1.ListenResponse0\x01\x12I\n\x06Status\x12\x1d.jumpstarter.v1.StatusRequest\x1a\x1e.jumpstarter.v1.StatusResponse0\x01\x12\x41\n\x04\x44ial\x12\x1b.jumpstarter.v1.DialRequest\x1a\x1c.jumpstarter.v1.DialResponse\x12K\n\x0b\x41uditStream\x12\".jumpstarter.v1.AuditStreamRequest\x1a\x16.google.protobuf.Empty(\x01\x12M\n\x08GetLease\x12\x1f.jumpstarter.v1.GetLeaseRequest\x1a .jumpstarter.v1.GetLeaseResponse\x12Y\n\x0cRequestLease\x12#.jumpstarter.v1.RequestLeaseRequest\x1a$.jumpstarter.v1.RequestLeaseResponse\x12Y\n\x0cReleaseLease\x12#.jumpstarter.v1.ReleaseLeaseRequest\x1a$.jumpstarter.v1.ReleaseLeaseResponse\x12S\n\nListLeases\x12!.jumpstarter.v1.ListLeasesRequest\x1a\".jumpstarter.v1.ListLeasesResponse2\x82\x04\n\x0f\x45xporterService\x12\x46\n\tGetReport\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.GetReportResponse\x12S\n\nDriverCall\x12!.jumpstarter.v1.DriverCallRequest\x1a\".jumpstarter.v1.DriverCallResponse\x12p\n\x13StreamingDriverCall\x12*.jumpstarter.v1.StreamingDriverCallRequest\x1a+.jumpstarter.v1.StreamingDriverCallResponse0\x01\x12H\n\tLogStream\x12\x16.google.protobuf.Empty\x1a!.jumpstarter.v1.LogStreamResponse0\x01\x12\x44\n\x05Reset\x12\x1c.jumpstarter.v1.ResetRequest\x1a\x1d.jumpstarter.v1.ResetResponse\x12P\n\tGetStatus\x12 .jumpstarter.v1.GetStatusRequest\x1a!.jumpstarter.v1.GetStatusResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jumpstarter.v1.jumpstarter_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\022com.jumpstarter.v1B\020JumpstarterProtoP\001\242\002\003JXX\252\002\016Jumpstarter.V1\312\002\016Jumpstarter\\V1\342\002\032Jumpstarter\\V1\\GPBMetadata\352\002\017Jumpstarter::V1' + DESCRIPTOR._loaded_options = None _globals['_REGISTERREQUEST_LABELSENTRY']._loaded_options = None _globals['_REGISTERREQUEST_LABELSENTRY']._serialized_options = b'8\001' _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._loaded_options = None _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._serialized_options = b'8\001' + _globals['_DRIVERINSTANCEREPORT_METHODSDESCRIPTIONENTRY']._loaded_options = None + _globals['_DRIVERINSTANCEREPORT_METHODSDESCRIPTIONENTRY']._serialized_options = b'8\001' _globals['_GETREPORTRESPONSE_LABELSENTRY']._loaded_options = None _globals['_GETREPORTRESPONSE_LABELSENTRY']._serialized_options = b'8\001' - _globals['_REGISTERREQUEST']._serialized_start=210 - _globals['_REGISTERREQUEST']._serialized_end=419 - _globals['_REGISTERREQUEST_LABELSENTRY']._serialized_start=362 - _globals['_REGISTERREQUEST_LABELSENTRY']._serialized_end=419 - _globals['_DRIVERINSTANCEREPORT']._serialized_start=422 - _globals['_DRIVERINSTANCEREPORT']._serialized_end=651 - _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._serialized_start=362 - _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._serialized_end=419 - _globals['_REGISTERRESPONSE']._serialized_start=653 - _globals['_REGISTERRESPONSE']._serialized_end=691 - _globals['_UNREGISTERREQUEST']._serialized_start=693 - _globals['_UNREGISTERREQUEST']._serialized_end=736 - _globals['_UNREGISTERRESPONSE']._serialized_start=738 - _globals['_UNREGISTERRESPONSE']._serialized_end=758 - _globals['_LISTENREQUEST']._serialized_start=760 - _globals['_LISTENREQUEST']._serialized_end=806 - _globals['_LISTENRESPONSE']._serialized_start=808 - _globals['_LISTENRESPONSE']._serialized_end=900 - _globals['_STATUSREQUEST']._serialized_start=902 - _globals['_STATUSREQUEST']._serialized_end=917 - _globals['_STATUSRESPONSE']._serialized_start=920 - _globals['_STATUSRESPONSE']._serialized_end=1065 - _globals['_DIALREQUEST']._serialized_start=1067 - _globals['_DIALREQUEST']._serialized_end=1111 - _globals['_DIALRESPONSE']._serialized_start=1113 - _globals['_DIALRESPONSE']._serialized_end=1203 - _globals['_AUDITSTREAMREQUEST']._serialized_start=1206 - _globals['_AUDITSTREAMREQUEST']._serialized_end=1367 - _globals['_GETREPORTRESPONSE']._serialized_start=1370 - _globals['_GETREPORTRESPONSE']._serialized_end=1682 - _globals['_GETREPORTRESPONSE_LABELSENTRY']._serialized_start=362 - _globals['_GETREPORTRESPONSE_LABELSENTRY']._serialized_end=419 - _globals['_ENDPOINT']._serialized_start=1685 - _globals['_ENDPOINT']._serialized_end=1850 - _globals['_DRIVERCALLREQUEST']._serialized_start=1852 - _globals['_DRIVERCALLREQUEST']._serialized_end=1959 - _globals['_DRIVERCALLRESPONSE']._serialized_start=1961 - _globals['_DRIVERCALLRESPONSE']._serialized_end=2049 - _globals['_STREAMINGDRIVERCALLREQUEST']._serialized_start=2051 - _globals['_STREAMINGDRIVERCALLREQUEST']._serialized_end=2167 - _globals['_STREAMINGDRIVERCALLRESPONSE']._serialized_start=2169 - _globals['_STREAMINGDRIVERCALLRESPONSE']._serialized_end=2266 - _globals['_LOGSTREAMRESPONSE']._serialized_start=2268 - _globals['_LOGSTREAMRESPONSE']._serialized_end=2361 - _globals['_RESETREQUEST']._serialized_start=2363 - _globals['_RESETREQUEST']._serialized_end=2377 - _globals['_RESETRESPONSE']._serialized_start=2379 - _globals['_RESETRESPONSE']._serialized_end=2394 - _globals['_GETLEASEREQUEST']._serialized_start=2396 - _globals['_GETLEASEREQUEST']._serialized_end=2433 - _globals['_GETLEASERESPONSE']._serialized_start=2436 - _globals['_GETLEASERESPONSE']._serialized_end=2839 - _globals['_REQUESTLEASEREQUEST']._serialized_start=2842 - _globals['_REQUESTLEASEREQUEST']._serialized_end=2977 - _globals['_REQUESTLEASERESPONSE']._serialized_start=2979 - _globals['_REQUESTLEASERESPONSE']._serialized_end=3021 - _globals['_RELEASELEASEREQUEST']._serialized_start=3023 - _globals['_RELEASELEASEREQUEST']._serialized_end=3064 - _globals['_RELEASELEASERESPONSE']._serialized_start=3066 - _globals['_RELEASELEASERESPONSE']._serialized_end=3088 - _globals['_LISTLEASESREQUEST']._serialized_start=3090 - _globals['_LISTLEASESREQUEST']._serialized_end=3109 - _globals['_LISTLEASESRESPONSE']._serialized_start=3111 - _globals['_LISTLEASESRESPONSE']._serialized_end=3153 - _globals['_CONTROLLERSERVICE']._serialized_start=3156 - _globals['_CONTROLLERSERVICE']._serialized_end=3979 - _globals['_EXPORTERSERVICE']._serialized_start=3982 - _globals['_EXPORTERSERVICE']._serialized_end=4414 + _globals['_REGISTERREQUEST']._serialized_start=239 + _globals['_REGISTERREQUEST']._serialized_end=448 + _globals['_REGISTERREQUEST_LABELSENTRY']._serialized_start=391 + _globals['_REGISTERREQUEST_LABELSENTRY']._serialized_end=448 + _globals['_DRIVERINSTANCEREPORT']._serialized_start=451 + _globals['_DRIVERINSTANCEREPORT']._serialized_end=917 + _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._serialized_start=391 + _globals['_DRIVERINSTANCEREPORT_LABELSENTRY']._serialized_end=448 + _globals['_DRIVERINSTANCEREPORT_METHODSDESCRIPTIONENTRY']._serialized_start=816 + _globals['_DRIVERINSTANCEREPORT_METHODSDESCRIPTIONENTRY']._serialized_end=885 + _globals['_REGISTERRESPONSE']._serialized_start=919 + _globals['_REGISTERRESPONSE']._serialized_end=957 + _globals['_UNREGISTERREQUEST']._serialized_start=959 + _globals['_UNREGISTERREQUEST']._serialized_end=1002 + _globals['_UNREGISTERRESPONSE']._serialized_start=1004 + _globals['_UNREGISTERRESPONSE']._serialized_end=1024 + _globals['_LISTENREQUEST']._serialized_start=1026 + _globals['_LISTENREQUEST']._serialized_end=1072 + _globals['_LISTENRESPONSE']._serialized_start=1074 + _globals['_LISTENRESPONSE']._serialized_end=1166 + _globals['_STATUSREQUEST']._serialized_start=1168 + _globals['_STATUSREQUEST']._serialized_end=1183 + _globals['_STATUSRESPONSE']._serialized_start=1186 + _globals['_STATUSRESPONSE']._serialized_end=1331 + _globals['_DIALREQUEST']._serialized_start=1333 + _globals['_DIALREQUEST']._serialized_end=1377 + _globals['_DIALRESPONSE']._serialized_start=1379 + _globals['_DIALRESPONSE']._serialized_end=1469 + _globals['_AUDITSTREAMREQUEST']._serialized_start=1472 + _globals['_AUDITSTREAMREQUEST']._serialized_end=1633 + _globals['_REPORTSTATUSREQUEST']._serialized_start=1635 + _globals['_REPORTSTATUSREQUEST']._serialized_end=1755 + _globals['_REPORTSTATUSRESPONSE']._serialized_start=1757 + _globals['_REPORTSTATUSRESPONSE']._serialized_end=1779 + _globals['_GETREPORTRESPONSE']._serialized_start=1782 + _globals['_GETREPORTRESPONSE']._serialized_end=2094 + _globals['_GETREPORTRESPONSE_LABELSENTRY']._serialized_start=391 + _globals['_GETREPORTRESPONSE_LABELSENTRY']._serialized_end=448 + _globals['_ENDPOINT']._serialized_start=2097 + _globals['_ENDPOINT']._serialized_end=2262 + _globals['_DRIVERCALLREQUEST']._serialized_start=2264 + _globals['_DRIVERCALLREQUEST']._serialized_end=2371 + _globals['_DRIVERCALLRESPONSE']._serialized_start=2373 + _globals['_DRIVERCALLRESPONSE']._serialized_end=2461 + _globals['_STREAMINGDRIVERCALLREQUEST']._serialized_start=2463 + _globals['_STREAMINGDRIVERCALLREQUEST']._serialized_end=2579 + _globals['_STREAMINGDRIVERCALLRESPONSE']._serialized_start=2581 + _globals['_STREAMINGDRIVERCALLRESPONSE']._serialized_end=2678 + _globals['_LOGSTREAMRESPONSE']._serialized_start=2681 + _globals['_LOGSTREAMRESPONSE']._serialized_end=2841 + _globals['_RESETREQUEST']._serialized_start=2843 + _globals['_RESETREQUEST']._serialized_end=2857 + _globals['_RESETRESPONSE']._serialized_start=2859 + _globals['_RESETRESPONSE']._serialized_end=2874 + _globals['_GETLEASEREQUEST']._serialized_start=2876 + _globals['_GETLEASEREQUEST']._serialized_end=2913 + _globals['_GETLEASERESPONSE']._serialized_start=2916 + _globals['_GETLEASERESPONSE']._serialized_end=3319 + _globals['_REQUESTLEASEREQUEST']._serialized_start=3322 + _globals['_REQUESTLEASEREQUEST']._serialized_end=3457 + _globals['_REQUESTLEASERESPONSE']._serialized_start=3459 + _globals['_REQUESTLEASERESPONSE']._serialized_end=3501 + _globals['_RELEASELEASEREQUEST']._serialized_start=3503 + _globals['_RELEASELEASEREQUEST']._serialized_end=3544 + _globals['_RELEASELEASERESPONSE']._serialized_start=3546 + _globals['_RELEASELEASERESPONSE']._serialized_end=3568 + _globals['_LISTLEASESREQUEST']._serialized_start=3570 + _globals['_LISTLEASESREQUEST']._serialized_end=3589 + _globals['_LISTLEASESRESPONSE']._serialized_start=3591 + _globals['_LISTLEASESRESPONSE']._serialized_end=3633 + _globals['_GETSTATUSREQUEST']._serialized_start=3635 + _globals['_GETSTATUSREQUEST']._serialized_end=3653 + _globals['_GETSTATUSRESPONSE']._serialized_start=3655 + _globals['_GETSTATUSRESPONSE']._serialized_end=3773 + _globals['_CONTROLLERSERVICE']._serialized_start=3776 + _globals['_CONTROLLERSERVICE']._serialized_end=4690 + _globals['_EXPORTERSERVICE']._serialized_start=4693 + _globals['_EXPORTERSERVICE']._serialized_end=5207 # @@protoc_insertion_point(module_scope) diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py index d975d654f..641a345ba 100644 --- a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py @@ -26,6 +26,11 @@ def __init__(self, channel): request_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.UnregisterRequest.SerializeToString, response_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.UnregisterResponse.FromString, _registered_method=True) + self.ReportStatus = channel.unary_unary( + '/jumpstarter.v1.ControllerService/ReportStatus', + request_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusRequest.SerializeToString, + response_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusResponse.FromString, + _registered_method=True) self.Listen = channel.unary_stream( '/jumpstarter.v1.ControllerService/Listen', request_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ListenRequest.SerializeToString, @@ -89,6 +94,14 @@ def Unregister(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def ReportStatus(self, request, context): + """Exporter status report + Allows exporters to report their own status to the controller + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def Listen(self, request, context): """Exporter listening Returns stream tokens for accepting incoming client connections @@ -163,6 +176,11 @@ def add_ControllerServiceServicer_to_server(servicer, server): request_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.UnregisterRequest.FromString, response_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.UnregisterResponse.SerializeToString, ), + 'ReportStatus': grpc.unary_unary_rpc_method_handler( + servicer.ReportStatus, + request_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusRequest.FromString, + response_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusResponse.SerializeToString, + ), 'Listen': grpc.unary_stream_rpc_method_handler( servicer.Listen, request_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ListenRequest.FromString, @@ -269,6 +287,33 @@ def Unregister(request, metadata, _registered_method=True) + @staticmethod + def ReportStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/jumpstarter.v1.ControllerService/ReportStatus', + jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusRequest.SerializeToString, + jumpstarter_dot_v1_dot_jumpstarter__pb2.ReportStatusResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def Listen(request, target, @@ -522,6 +567,11 @@ def __init__(self, channel): request_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ResetRequest.SerializeToString, response_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ResetResponse.FromString, _registered_method=True) + self.GetStatus = channel.unary_unary( + '/jumpstarter.v1.ExporterService/GetStatus', + request_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusRequest.SerializeToString, + response_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusResponse.FromString, + _registered_method=True) class ExporterServiceServicer(object): @@ -560,6 +610,12 @@ def Reset(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetStatus(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_ExporterServiceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -588,6 +644,11 @@ def add_ExporterServiceServicer_to_server(servicer, server): request_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ResetRequest.FromString, response_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.ResetResponse.SerializeToString, ), + 'GetStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetStatus, + request_deserializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusRequest.FromString, + response_serializer=jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'jumpstarter.v1.ExporterService', rpc_method_handlers) @@ -735,3 +796,30 @@ def Reset(request, timeout, metadata, _registered_method=True) + + @staticmethod + def GetStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/jumpstarter.v1.ExporterService/GetStatus', + jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusRequest.SerializeToString, + jumpstarter_dot_v1_dot_jumpstarter__pb2.GetStatusResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/kubernetes_pb2.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/kubernetes_pb2.py index d7e6db9b4..fc7fcc649 100644 --- a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/kubernetes_pb2.py +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/kubernetes_pb2.py @@ -24,14 +24,13 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fjumpstarter/v1/kubernetes.proto\x12\x0ejumpstarter.v1\"`\n\x18LabelSelectorRequirement\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1a\n\x08operator\x18\x02 \x01(\tR\x08operator\x12\x16\n\x06values\x18\x03 \x03(\tR\x06values\"\xf9\x01\n\rLabelSelector\x12U\n\x11match_expressions\x18\x01 \x03(\x0b\x32(.jumpstarter.v1.LabelSelectorRequirementR\x10matchExpressions\x12Q\n\x0cmatch_labels\x18\x02 \x03(\x0b\x32..jumpstarter.v1.LabelSelector.MatchLabelsEntryR\x0bmatchLabels\x1a>\n\x10MatchLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"V\n\x04Time\x12\x1d\n\x07seconds\x18\x01 \x01(\x03H\x00R\x07seconds\x88\x01\x01\x12\x19\n\x05nanos\x18\x02 \x01(\x05H\x01R\x05nanos\x88\x01\x01\x42\n\n\x08_secondsB\x08\n\x06_nanos\"\xd6\x02\n\tCondition\x12\x17\n\x04type\x18\x01 \x01(\tH\x00R\x04type\x88\x01\x01\x12\x1b\n\x06status\x18\x02 \x01(\tH\x01R\x06status\x88\x01\x01\x12\x33\n\x12observedGeneration\x18\x03 \x01(\x03H\x02R\x12observedGeneration\x88\x01\x01\x12I\n\x12lastTransitionTime\x18\x04 \x01(\x0b\x32\x14.jumpstarter.v1.TimeH\x03R\x12lastTransitionTime\x88\x01\x01\x12\x1b\n\x06reason\x18\x05 \x01(\tH\x04R\x06reason\x88\x01\x01\x12\x1d\n\x07message\x18\x06 \x01(\tH\x05R\x07message\x88\x01\x01\x42\x07\n\x05_typeB\t\n\x07_statusB\x15\n\x13_observedGenerationB\x15\n\x13_lastTransitionTimeB\t\n\x07_reasonB\n\n\x08_messageB~\n\x12\x63om.jumpstarter.v1B\x0fKubernetesProtoP\x01\xa2\x02\x03JXX\xaa\x02\x0eJumpstarter.V1\xca\x02\x0eJumpstarter\\V1\xe2\x02\x1aJumpstarter\\V1\\GPBMetadata\xea\x02\x0fJumpstarter::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fjumpstarter/v1/kubernetes.proto\x12\x0ejumpstarter.v1\"`\n\x18LabelSelectorRequirement\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1a\n\x08operator\x18\x02 \x01(\tR\x08operator\x12\x16\n\x06values\x18\x03 \x03(\tR\x06values\"\xf9\x01\n\rLabelSelector\x12U\n\x11match_expressions\x18\x01 \x03(\x0b\x32(.jumpstarter.v1.LabelSelectorRequirementR\x10matchExpressions\x12Q\n\x0cmatch_labels\x18\x02 \x03(\x0b\x32..jumpstarter.v1.LabelSelector.MatchLabelsEntryR\x0bmatchLabels\x1a>\n\x10MatchLabelsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"V\n\x04Time\x12\x1d\n\x07seconds\x18\x01 \x01(\x03H\x00R\x07seconds\x88\x01\x01\x12\x19\n\x05nanos\x18\x02 \x01(\x05H\x01R\x05nanos\x88\x01\x01\x42\n\n\x08_secondsB\x08\n\x06_nanos\"\xd6\x02\n\tCondition\x12\x17\n\x04type\x18\x01 \x01(\tH\x00R\x04type\x88\x01\x01\x12\x1b\n\x06status\x18\x02 \x01(\tH\x01R\x06status\x88\x01\x01\x12\x33\n\x12observedGeneration\x18\x03 \x01(\x03H\x02R\x12observedGeneration\x88\x01\x01\x12I\n\x12lastTransitionTime\x18\x04 \x01(\x0b\x32\x14.jumpstarter.v1.TimeH\x03R\x12lastTransitionTime\x88\x01\x01\x12\x1b\n\x06reason\x18\x05 \x01(\tH\x04R\x06reason\x88\x01\x01\x12\x1d\n\x07message\x18\x06 \x01(\tH\x05R\x07message\x88\x01\x01\x42\x07\n\x05_typeB\t\n\x07_statusB\x15\n\x13_observedGenerationB\x15\n\x13_lastTransitionTimeB\t\n\x07_reasonB\n\n\x08_messageb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jumpstarter.v1.kubernetes_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\022com.jumpstarter.v1B\017KubernetesProtoP\001\242\002\003JXX\252\002\016Jumpstarter.V1\312\002\016Jumpstarter\\V1\342\002\032Jumpstarter\\V1\\GPBMetadata\352\002\017Jumpstarter::V1' + DESCRIPTOR._loaded_options = None _globals['_LABELSELECTOR_MATCHLABELSENTRY']._loaded_options = None _globals['_LABELSELECTOR_MATCHLABELSENTRY']._serialized_options = b'8\001' _globals['_LABELSELECTORREQUIREMENT']._serialized_start=51 diff --git a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/router_pb2.py b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/router_pb2.py index 0d86b315a..030a29cbb 100644 --- a/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/router_pb2.py +++ b/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/router_pb2.py @@ -24,14 +24,13 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bjumpstarter/v1/router.proto\x12\x0ejumpstarter.v1\"c\n\rStreamRequest\x12\x18\n\x07payload\x18\x01 \x01(\x0cR\x07payload\x12\x38\n\nframe_type\x18\x02 \x01(\x0e\x32\x19.jumpstarter.v1.FrameTypeR\tframeType\"d\n\x0eStreamResponse\x12\x18\n\x07payload\x18\x01 \x01(\x0cR\x07payload\x12\x38\n\nframe_type\x18\x02 \x01(\x0e\x32\x19.jumpstarter.v1.FrameTypeR\tframeType*g\n\tFrameType\x12\x13\n\x0f\x46RAME_TYPE_DATA\x10\x00\x12\x19\n\x15\x46RAME_TYPE_RST_STREAM\x10\x03\x12\x13\n\x0f\x46RAME_TYPE_PING\x10\x06\x12\x15\n\x11\x46RAME_TYPE_GOAWAY\x10\x07\x32\\\n\rRouterService\x12K\n\x06Stream\x12\x1d.jumpstarter.v1.StreamRequest\x1a\x1e.jumpstarter.v1.StreamResponse(\x01\x30\x01\x42z\n\x12\x63om.jumpstarter.v1B\x0bRouterProtoP\x01\xa2\x02\x03JXX\xaa\x02\x0eJumpstarter.V1\xca\x02\x0eJumpstarter\\V1\xe2\x02\x1aJumpstarter\\V1\\GPBMetadata\xea\x02\x0fJumpstarter::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bjumpstarter/v1/router.proto\x12\x0ejumpstarter.v1\"c\n\rStreamRequest\x12\x18\n\x07payload\x18\x01 \x01(\x0cR\x07payload\x12\x38\n\nframe_type\x18\x02 \x01(\x0e\x32\x19.jumpstarter.v1.FrameTypeR\tframeType\"d\n\x0eStreamResponse\x12\x18\n\x07payload\x18\x01 \x01(\x0cR\x07payload\x12\x38\n\nframe_type\x18\x02 \x01(\x0e\x32\x19.jumpstarter.v1.FrameTypeR\tframeType*g\n\tFrameType\x12\x13\n\x0f\x46RAME_TYPE_DATA\x10\x00\x12\x19\n\x15\x46RAME_TYPE_RST_STREAM\x10\x03\x12\x13\n\x0f\x46RAME_TYPE_PING\x10\x06\x12\x15\n\x11\x46RAME_TYPE_GOAWAY\x10\x07\x32\\\n\rRouterService\x12K\n\x06Stream\x12\x1d.jumpstarter.v1.StreamRequest\x1a\x1e.jumpstarter.v1.StreamResponse(\x01\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jumpstarter.v1.router_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\022com.jumpstarter.v1B\013RouterProtoP\001\242\002\003JXX\252\002\016Jumpstarter.V1\312\002\016Jumpstarter\\V1\342\002\032Jumpstarter\\V1\\GPBMetadata\352\002\017Jumpstarter::V1' + DESCRIPTOR._loaded_options = None _globals['_FRAMETYPE']._serialized_start=250 _globals['_FRAMETYPE']._serialized_end=353 _globals['_STREAMREQUEST']._serialized_start=47 From 46f94b1a40424cd54e2bcea9dae6e699e155ebf5 Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Mon, 6 Oct 2025 12:53:08 +0200 Subject: [PATCH 3/6] Add description and methods_description infrastructure this allows override of the default strings to show up in the jmp shell CLI. the Driver descriptions can be customized with: exporter: driver_x: type: x description: "this driver does x" methods_description: "method1": "this method does y" "method2": "this method does z" config: ... - Add driver_click_group decorator in client/decorators.py for CLI generation. Annotated method will be used as CLI group description(driver). - Add driver_click_command decorator to use when there is no group (a single command driver) - Add description and methods_description field to DriverClient, retrieved via GetReport call from server. - Add description and methods_description field to Driver base class, only when customized (i.e. defaults are not transmitted). The infrastructure is using a single DriverInstanceReport message to save on RPC calls. --- .../jumpstarter_driver_composite/client.py | 2 +- .../jumpstarter/jumpstarter/client/base.py | 6 + .../jumpstarter/jumpstarter/client/client.py | 3 + .../jumpstarter/client/decorators.py | 102 ++++++ .../jumpstarter/config/exporter.py | 9 +- .../jumpstarter/jumpstarter/driver/base.py | 8 + .../jumpstarter/exporter/session_test.py | 312 ++++++++++++++++++ 7 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 packages/jumpstarter/jumpstarter/client/decorators.py create mode 100644 packages/jumpstarter/jumpstarter/exporter/session_test.py diff --git a/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py b/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py index 468fd9450..de7bf0635 100644 --- a/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py +++ b/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py @@ -34,7 +34,7 @@ def close(self): v.close() def cli(self): - @click.group + @click_group @click.option( "--log-level", "log_level", diff --git a/packages/jumpstarter/jumpstarter/client/base.py b/packages/jumpstarter/jumpstarter/client/base.py index 1616d4c02..1420c52da 100644 --- a/packages/jumpstarter/jumpstarter/client/base.py +++ b/packages/jumpstarter/jumpstarter/client/base.py @@ -33,6 +33,12 @@ class DriverClient(AsyncDriverClient): portal: BlockingPortal stack: ExitStack + description: str | None = None + """Driver description from GetReport(), used for CLI help text""" + + methods_description: dict[str, str] = field(default_factory=dict) + """Map of method names to their help descriptions from GetReport()""" + def call(self, method, *args): """ Invoke driver call diff --git a/packages/jumpstarter/jumpstarter/client/client.py b/packages/jumpstarter/jumpstarter/client/client.py index e77f40a75..f4aaef824 100644 --- a/packages/jumpstarter/jumpstarter/client/client.py +++ b/packages/jumpstarter/jumpstarter/client/client.py @@ -51,6 +51,7 @@ async def client_from_channel( report = reports[index] client_class = import_class(report.labels["jumpstarter.dev/client"], allow, unsafe) + client = client_class( uuid=UUID(report.uuid), labels=report.labels, @@ -58,6 +59,8 @@ async def client_from_channel( portal=portal, stack=stack.enter_context(ExitStack()), children={reports[k].labels["jumpstarter.dev/name"]: clients[k] for k in topo[index]}, + description=getattr(report, 'description', None) or None, + methods_description=getattr(report, 'methods_description', {}) or {}, ) clients[index] = client diff --git a/packages/jumpstarter/jumpstarter/client/decorators.py b/packages/jumpstarter/jumpstarter/client/decorators.py new file mode 100644 index 000000000..2343f8355 --- /dev/null +++ b/packages/jumpstarter/jumpstarter/client/decorators.py @@ -0,0 +1,102 @@ +""" +Client-side Click group helpers for building driver CLIs. +""" + +from typing import TYPE_CHECKING, Any, Callable + +import click + +if TYPE_CHECKING: + from jumpstarter.client import DriverClient + + +def driver_click_group(client: "DriverClient", **kwargs: Any) -> Callable: + """ + Decorator factory for multi-command driver groups. + + Allows server-side description override, otherwise uses Click's default behavior. + + Usage: + def cli(self): + @driver_click_group(self) + def base(): + '''Generic power interface''' # ← Click uses this by default + pass + + @base.command() + def on(): + '''Power on''' + self.on() + + return base + + :param client: DriverClient instance (provides description and methods_description) + :param kwargs: Keyword arguments passed to DriverClickGroup + :return: Decorator that creates a DriverClickGroup + """ + def decorator(f: Callable) -> DriverClickGroup: + # Use function docstring if no help= provided + if 'help' not in kwargs or kwargs['help'] is None: + if f.__doc__: + kwargs['help'] = f.__doc__.strip() + + # Server description overrides Click defaults + if getattr(client, 'description', None): + kwargs['help'] = client.description + + group = DriverClickGroup(client, name=f.__name__, callback=f, **kwargs) + + # Transfer Click parameters attached by decorators like @click.option + group.params = getattr(f, '__click_params__', []) + + return group + + return decorator + + +def driver_click_command(client: "DriverClient", **kwargs: Any) -> Callable: + """ + Decorator factory for single-command drivers (e.g., SSH, TMT). + + Allows server-side description override, otherwise uses Click's default behavior. + + Usage: + def cli(self): + @driver_click_command(self) + @click.argument("args", nargs=-1) + def ssh(args): + '''Run SSH command''' # ← Click uses this by default + ... + return ssh + + :param client: DriverClient instance (provides description field) + :param kwargs: Keyword arguments passed to click.command + :return: click.command decorator + """ + # Server description overrides Click's defaults (help= parameter or docstring) + if getattr(client, 'description', None): + kwargs['help'] = client.description + + return click.command(**kwargs) + + +class DriverClickGroup(click.Group): + """Click Group with server-configurable help text via methods_description.""" + + def __init__(self, client: "DriverClient", *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.client = client + + def command(self, *args: Any, **kwargs: Any) -> Callable: + """Command decorator with server methods_description override support.""" + def decorator(f: Callable) -> click.Command: + name = kwargs.get('name') + if not name: + name = f.__name__.lower().replace('_', '-') + + if name in self.client.methods_description: + kwargs['help'] = self.client.methods_description[name] + + return super(DriverClickGroup, self).command(*args, **kwargs)(f) + + return decorator diff --git a/packages/jumpstarter/jumpstarter/config/exporter.py b/packages/jumpstarter/jumpstarter/config/exporter.py index 8cc8b9bd6..25a6781d7 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter.py +++ b/packages/jumpstarter/jumpstarter/config/exporter.py @@ -28,6 +28,8 @@ class ExporterConfigV1Alpha1DriverInstanceComposite(BaseModel): class ExporterConfigV1Alpha1DriverInstanceBase(BaseModel): type: str + description: str | None = None + methods_description: dict[str, str] = Field(default_factory=dict) config: dict[str, Any] = Field(default_factory=dict) children: dict[str, ExporterConfigV1Alpha1DriverInstance] = Field(default_factory=dict) @@ -46,7 +48,12 @@ def instantiate(self) -> Driver: children = {name: child.instantiate() for name, child in self.root.children.items()} - return driver_class(children=children, **self.root.config) + return driver_class( + description=self.root.description, + methods_description=self.root.methods_description, + children=children, + **self.root.config + ) case ExporterConfigV1Alpha1DriverInstanceComposite(): from jumpstarter_driver_composite.driver import Composite diff --git a/packages/jumpstarter/jumpstarter/driver/base.py b/packages/jumpstarter/jumpstarter/driver/base.py index eedb92a01..895b007c2 100644 --- a/packages/jumpstarter/jumpstarter/driver/base.py +++ b/packages/jumpstarter/jumpstarter/driver/base.py @@ -72,6 +72,12 @@ class Driver( resources: dict[UUID, Any] = field(default_factory=dict, init=False) """Dict of client side resources""" + description: str | None = None + """Custom description for the driver (shown in CLI help)""" + + methods_description: dict[str, str] = field(default_factory=dict) + """Map of method names to their help descriptions (configurable via server config)""" + log_level: str = "INFO" logger: logging.Logger = field(init=False) @@ -206,6 +212,8 @@ def report(self, *, root=None, parent=None, name=None): | self.extra_labels() | ({"jumpstarter.dev/client": self.client()}) | ({"jumpstarter.dev/name": name} if name else {}), + description=self.description or None, + methods_description=self.methods_description or {}, ) def enumerate(self, *, root=None, parent=None, name=None): diff --git a/packages/jumpstarter/jumpstarter/exporter/session_test.py b/packages/jumpstarter/jumpstarter/exporter/session_test.py new file mode 100644 index 000000000..664a2a02e --- /dev/null +++ b/packages/jumpstarter/jumpstarter/exporter/session_test.py @@ -0,0 +1,312 @@ +"""Tests for session GetReport with descriptions and methods_description""" + +from google.protobuf import empty_pb2 + +from jumpstarter.common.utils import serve +from jumpstarter.driver import Driver + + +class SimpleDriver(Driver): + """Simple test driver""" + + @classmethod + def client(cls): + return "jumpstarter.client.DriverClient" + + +class CompositeDriver_(Driver): + """Simple composite driver for testing""" + + @classmethod + def client(cls): + return "jumpstarter.client.DriverClient" + + +def test_get_report_includes_descriptions(): + """Test that GetReport includes descriptions for drivers that have them""" + # Create drivers with and without descriptions + driver_with_desc = SimpleDriver(description="Custom test driver") + driver_without_desc = SimpleDriver() + + root = CompositeDriver_( + children={ + "with_desc": driver_with_desc, + "without_desc": driver_without_desc, + } + ) + + with serve(root) as _: + # Get the raw report response + + from jumpstarter.exporter.session import Session + + # Create session manually to access GetReport + session = Session( + uuid=root.uuid, + labels=root.labels, + root_device=root, + ) + + # Call GetReport + import asyncio + response = asyncio.run(session.GetReport(empty_pb2.Empty(), None)) + + # Build a map of uuid -> report for easy lookup + reports_by_uuid = {r.uuid: r for r in response.reports} + + # Verify driver with description has it in its report + assert str(driver_with_desc.uuid) in reports_by_uuid + report_with_desc = reports_by_uuid[str(driver_with_desc.uuid)] + assert hasattr(report_with_desc, 'description') + assert report_with_desc.description == "Custom test driver" + + # Verify driver without description doesn't have the field set + assert str(driver_without_desc.uuid) in reports_by_uuid + report_without_desc = reports_by_uuid[str(driver_without_desc.uuid)] + # Optional field - either not set or empty string + assert not getattr(report_without_desc, 'description', None) + + +def test_client_receives_description(): + """Test that client receives description from GetReport""" + driver = SimpleDriver(description="Test description") + + with serve(driver) as client: + # Description is passed during init from GetReport + assert client.description == "Test description" + + +def test_cli_uses_description_or_default(): + """Test that CLI uses description from GetReport or falls back to default""" + # Test with description set + driver_with_desc = SimpleDriver(description="Custom CLI description") + with serve(driver_with_desc) as client: + # Simulate what cli() method would do + help_text = client.description or "Default help text" + assert help_text == "Custom CLI description" + + # Test without description + driver_without_desc = SimpleDriver() + with serve(driver_without_desc) as client: + help_text = client.description or "Default help text" + assert help_text == "Default help text" + + +def test_multiple_drivers_with_descriptions(): + """Test that multiple drivers can have different descriptions""" + power = SimpleDriver(description="Power control") + serial = SimpleDriver(description="Serial communication") + storage = SimpleDriver(description="Storage management") + plain = SimpleDriver() # No description + + root = CompositeDriver_( + children={ + "power": power, + "serial": serial, + "storage": storage, + "plain": plain, + } + ) + + with serve(root) as client: + # Each child should have its description from GetReport + assert client.children['power'].description == "Power control" + assert client.children['serial'].description == "Serial communication" + assert client.children['storage'].description == "Storage management" + assert client.children['plain'].description is None + + +def test_empty_description_not_included(): + """Test that empty strings are not included in descriptions map""" + driver = SimpleDriver(description="") + + with serve(driver) as _: + from jumpstarter.exporter.session import Session + + session = Session( + uuid=driver.uuid, + labels=driver.labels, + root_device=driver, + ) + + import asyncio + response = asyncio.run(session.GetReport(empty_pb2.Empty(), None)) + + # Empty string should not be included in the report + reports_by_uuid = {r.uuid: r for r in response.reports} + assert str(driver.uuid) in reports_by_uuid + report = reports_by_uuid[str(driver.uuid)] + # Empty description should not be set + assert not getattr(report, 'description', None) + + +def test_description_override_in_exporter_config(): + """Test that description in exporter config overrides default""" + # Create a driver with a custom description + custom_driver = SimpleDriver(description="Custom override description") + + with serve(custom_driver) as client: + # Client should receive the custom description + assert client.description == "Custom override description" + + +def test_description_available_to_cli(): + """Test that description is available for CLI group help text""" + # Test with custom description + driver_with_desc = SimpleDriver(description="Power management interface") + with serve(driver_with_desc) as client: + # Description should be available for CLI + assert client.description == "Power management interface" + + # This is what DriverClickGroup would use + cli_help = client.description or "Default CLI help" + assert cli_help == "Power management interface" + + # Test without description (falls back to default) + driver_no_desc = SimpleDriver() + with serve(driver_no_desc) as client: + assert client.description is None + + # DriverClickGroup falls back to provided default + cli_help = client.description or "Default CLI help" + assert cli_help == "Default CLI help" + + +def test_composite_children_each_have_own_description(): + """Test that each child in composite can have its own description""" + power = SimpleDriver(description="Power control interface") + serial = SimpleDriver(description="Serial communication interface") + storage = SimpleDriver(description="Storage management interface") + network = SimpleDriver() # No custom description + + root = CompositeDriver_( + description="Main composite device", + children={ + "power": power, + "serial": serial, + "storage": storage, + "network": network, + } + ) + + with serve(root) as client: + # Root has its own description + assert client.description == "Main composite device" + + # Each child maintains its own description + assert client.children['power'].description == "Power control interface" + assert client.children['serial'].description == "Serial communication interface" + assert client.children['storage'].description == "Storage management interface" + assert client.children['network'].description is None + + +def test_methods_description_set_via_config(): + """Test that methods_description can be set via server configuration""" + # Server can override method descriptions via config + driver = SimpleDriver( + description="Power management", + methods_description={ + "on": "Custom: Turn device power on", + "off": "Custom: Turn device power off", + "cycle": "Custom: Power cycle the device" + } + ) + + # methods_description should be set + assert "on" in driver.methods_description + assert driver.methods_description["on"] == "Custom: Turn device power on" + assert "off" in driver.methods_description + assert driver.methods_description["off"] == "Custom: Turn device power off" + + +def test_methods_description_included_in_getreport(): + """Test that GetReport includes methods_description for drivers""" + driver = SimpleDriver( + methods_description={ + "on": "Turn the device on", + "off": "Turn the device off", + } + ) + + with serve(driver) as _: + from jumpstarter.exporter.session import Session + + session = Session( + uuid=driver.uuid, + labels=driver.labels, + root_device=driver, + ) + + import asyncio + response = asyncio.run(session.GetReport(empty_pb2.Empty(), None)) + + # Find the driver's report + reports_by_uuid = {r.uuid: r for r in response.reports} + assert str(driver.uuid) in reports_by_uuid + report = reports_by_uuid[str(driver.uuid)] + + # Verify methods_description is in the report + assert hasattr(report, 'methods_description') + assert "on" in report.methods_description + assert report.methods_description["on"] == "Turn the device on" + assert "off" in report.methods_description + assert report.methods_description["off"] == "Turn the device off" + + +def test_client_receives_methods_description(): + """Test that client receives methods_description from GetReport""" + driver = SimpleDriver( + description="Test power driver", + methods_description={ + "on": "Turn the device on", + "off": "Turn the device off", + "read": "Stream power readings" + } + ) + + with serve(driver) as client: + # Client should have methods_description populated + assert "on" in client.methods_description + assert client.methods_description["on"] == "Turn the device on" + assert "off" in client.methods_description + assert client.methods_description["off"] == "Turn the device off" + assert "read" in client.methods_description + assert client.methods_description["read"] == "Stream power readings" + + +def test_driverclickgroup_uses_methods_description_as_override(): + """Test that DriverClickGroup uses methods_description to override client defaults""" + driver = SimpleDriver( + description="Power management", + methods_description={ + "on": "Server override: Power on", + } + ) + + with serve(driver) as client: + # Simulate what DriverClickGroup.command() does: + # Priority: server methods_description > client help= > empty + + # Method with server override + method_name = "on" + if method_name in client.methods_description: + help_text = client.methods_description[method_name] + elif "help" in {}: # Simulate client's help= parameter + help_text = {}["help"] + else: + help_text = "" + + # Should get server override + assert help_text == "Server override: Power on" + + # Method without server override + method_name = "off" + client_help = "Client default: Power off" + if method_name in client.methods_description: + help_text = client.methods_description[method_name] + else: + help_text = client_help + + # Should fall back to client default + assert help_text == "Client default: Power off" + From bedc72f48a8f9f659fbc4a8decc46ff1f71396dd Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Mon, 6 Oct 2025 13:04:02 +0200 Subject: [PATCH 4/6] Migrate all drivers to use driver_click_group or driver_click_command decorator Replace @click.group decorators with @driver_click_group and @click.command with @driver_click_command in all drivers, and use it to provide server-side driver description. The CLI client will retrieve customized driver's description via GetReport() from the exporter. When there's no customization it falls back to locally provided default in docstring or help=. --- .../jumpstarter_driver_composite/client.py | 3 ++- .../jumpstarter_driver_flashers/client.py | 3 ++- .../jumpstarter_driver_gpiod/client.py | 5 +++-- .../jumpstarter_driver_network/client.py | 3 ++- .../jumpstarter_driver_opendal/client.py | 11 ++++++++--- .../jumpstarter_driver_power/client.py | 3 ++- .../jumpstarter_driver_probe_rs/client.py | 3 ++- .../jumpstarter_driver_pyserial/client.py | 3 ++- .../jumpstarter_driver_ridesx/client.py | 6 ++++-- .../jumpstarter_driver_shell/client.py | 7 ++++--- .../jumpstarter_driver_snmp/client.py | 5 +++-- .../jumpstarter_driver_ssh/client.py | 8 ++++++-- .../jumpstarter_driver_tmt/client.py | 8 ++++++-- 13 files changed, 46 insertions(+), 22 deletions(-) diff --git a/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py b/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py index de7bf0635..d41ff92be 100644 --- a/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py +++ b/packages/jumpstarter-driver-composite/jumpstarter_driver_composite/client.py @@ -5,6 +5,7 @@ from rich import traceback from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group def _opt_log_level_callback(ctx, param, value): @@ -34,7 +35,7 @@ def close(self): v.close() def cli(self): - @click_group + @driver_click_group(self) @click.option( "--log-level", "log_level", diff --git a/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py b/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py index ac0082a6f..e9d9117b3 100644 --- a/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py +++ b/packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py @@ -21,6 +21,7 @@ from jumpstarter_driver_flashers.bundle import FlasherBundleManifestV1Alpha1 +from jumpstarter.client.decorators import driver_click_group from jumpstarter.common.exceptions import ArgumentError debug_console_option = click.option("--console-debug", is_flag=True, help="Enable console debug mode") @@ -750,7 +751,7 @@ def _validate_bearer_token(self, token: str | None) -> str | None: return token def cli(self): - @click.group + @driver_click_group(self) def base(): """Software-defined flasher interface""" pass diff --git a/packages/jumpstarter-driver-gpiod/jumpstarter_driver_gpiod/client.py b/packages/jumpstarter-driver-gpiod/jumpstarter_driver_gpiod/client.py index df6ef0f90..3c30f5c54 100644 --- a/packages/jumpstarter-driver-gpiod/jumpstarter_driver_gpiod/client.py +++ b/packages/jumpstarter-driver-gpiod/jumpstarter_driver_gpiod/client.py @@ -5,6 +5,7 @@ from jumpstarter_driver_power.client import PowerClient from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group class PinState(Enum): @@ -34,7 +35,7 @@ def read(self): return PinState(int(self.call("read_pin"))) def cli(self): - @click.group() + @driver_click_group(self) def gpio(): """GPIO power control commands.""" pass @@ -79,7 +80,7 @@ def read(self): return PinState(int(self.call("read_pin"))) def cli(self): - @click.group() + @driver_click_group(self) def gpio(): """GPIO input commands.""" pass diff --git a/packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py b/packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py index 85befd4e1..35f6cd3ef 100644 --- a/packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py +++ b/packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py @@ -12,6 +12,7 @@ from .driver import DbusNetwork from jumpstarter.client import DriverClient from jumpstarter.client.core import DriverMethodNotImplemented +from jumpstarter.client.decorators import driver_click_group class NetworkClient(DriverClient): @@ -20,7 +21,7 @@ def address(self): return self.call("address") def cli(self): - @click.group + @driver_click_group(self) def base(): """Generic Network Connection""" pass diff --git a/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py b/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py index 045484c5a..d56860ad1 100644 --- a/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py +++ b/packages/jumpstarter-driver-opendal/jumpstarter_driver_opendal/client.py @@ -19,6 +19,7 @@ from .adapter import OpendalAdapter from .common import Capability, HashAlgo, Metadata, Mode, PathBuf, PresignedRequest from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group from jumpstarter.common.exceptions import ArgumentError from jumpstarter.streams.encoding import Compression @@ -413,9 +414,10 @@ def cli(self): # noqa: C901 arg_dst = click.argument("dst", type=click.Path()) opt_expire_second = click.option("--expire-second", type=int, required=True) - @click.group + @driver_click_group(self) def base(): """Opendal Storage""" + pass @base.command @arg_path @@ -548,7 +550,7 @@ def dump( ... def cli(self): - @click.group + @driver_click_group(self) def base(): """Generic flasher interface""" pass @@ -716,7 +718,10 @@ def read_local_file(self, filepath): def cli(self, base=None): if base is None: - base = click.group(lambda: None) + @driver_click_group(self) + def base(): + """Storage operations""" + pass @base.command() def host(): diff --git a/packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py b/packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py index eb86b7b15..3fc25598e 100644 --- a/packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py +++ b/packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py @@ -5,6 +5,7 @@ from .common import PowerReading from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group class PowerClient(DriverClient): @@ -35,7 +36,7 @@ def read(self) -> Generator[PowerReading, None, None]: yield PowerReading.model_validate(v, strict=True) def cli(self): - @click.group + @driver_click_group(self) def base(): """Generic power""" pass diff --git a/packages/jumpstarter-driver-probe-rs/jumpstarter_driver_probe_rs/client.py b/packages/jumpstarter-driver-probe-rs/jumpstarter_driver_probe_rs/client.py index 126276fc0..6f6bbcf69 100644 --- a/packages/jumpstarter-driver-probe-rs/jumpstarter_driver_probe_rs/client.py +++ b/packages/jumpstarter-driver-probe-rs/jumpstarter_driver_probe_rs/client.py @@ -6,6 +6,7 @@ from opendal import Operator from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group from jumpstarter.common.exceptions import ArgumentError @@ -60,7 +61,7 @@ def read(self, width: int, address: int, words: int) -> list[int]: return [int(data, 16) for data in data_strs] def cli(self): # noqa: C901 - @click.group + @driver_click_group(self) def base(): """probe-rs client""" pass diff --git a/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py b/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py index 523168826..7a4f643eb 100644 --- a/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py +++ b/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py @@ -6,6 +6,7 @@ from .console import Console from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group class PySerialClient(DriverClient): @@ -36,7 +37,7 @@ def pexpect(self): yield adapter def cli(self): - @click.group + @driver_click_group(self) def base(): """Serial port client""" pass diff --git a/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py b/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py index f9a619647..978ba2ec0 100644 --- a/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py +++ b/packages/jumpstarter-driver-ridesx/jumpstarter_driver_ridesx/client.py @@ -2,12 +2,13 @@ from pathlib import Path from typing import Dict, Optional -import click from jumpstarter_driver_composite.client import CompositeClient from jumpstarter_driver_opendal.client import FlasherClient, operator_for_path from jumpstarter_driver_power.client import PowerClient from opendal import Operator +from jumpstarter.client.decorators import driver_click_group + PROMPT = "CMD >> " @@ -102,8 +103,9 @@ def flash( def cli(self): generic_cli = FlasherClient.cli(self) - @click.group() + @driver_click_group(self) def storage(): + """RideSX storage operations""" pass for name, cmd in generic_cli.commands.items(): diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py index ea54137ed..8899c71a1 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py @@ -4,6 +4,7 @@ import click from jumpstarter.client import DriverClient +from jumpstarter.client.decorators import driver_click_group @dataclass(kw_only=True) @@ -41,7 +42,7 @@ def execute(*args, **kwargs): def cli(self): """Create CLI interface for dynamically configured shell methods""" - @click.group + @driver_click_group(self) def base(): """Shell command executor""" pass @@ -80,12 +81,12 @@ def method_command(args, env): except Exception: description = f"Execute the {method_name} shell method" - # Decorate and register the command - method_command.__doc__ = description + # Decorate and register the command with help text method_command = click.argument('args', nargs=-1, type=click.UNPROCESSED)(method_command) method_command = click.option('--env', '-e', multiple=True, help='Environment variables in KEY=VALUE format')(method_command) method_command = group.command( name=method_name, + help=description, context_settings={"ignore_unknown_options": True, "allow_interspersed_args": False}, )(method_command) diff --git a/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py b/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py index 6eb84afa8..17777e46f 100644 --- a/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py +++ b/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py @@ -1,8 +1,9 @@ from dataclasses import dataclass -import click from jumpstarter_driver_power.client import PowerClient +from jumpstarter.client.decorators import driver_click_group + @dataclass(kw_only=True) class SNMPServerClient(PowerClient): @@ -17,7 +18,7 @@ def off(self): self.call("off") def cli(self): - @click.group() + @driver_click_group(self) def snmp(): """SNMP power control commands""" pass diff --git a/packages/jumpstarter-driver-ssh/jumpstarter_driver_ssh/client.py b/packages/jumpstarter-driver-ssh/jumpstarter_driver_ssh/client.py index 6f3b6f6ac..1bf239c85 100644 --- a/packages/jumpstarter-driver-ssh/jumpstarter_driver_ssh/client.py +++ b/packages/jumpstarter-driver-ssh/jumpstarter_driver_ssh/client.py @@ -10,6 +10,7 @@ from jumpstarter_driver_network.adapters import TcpPortforwardAdapter from jumpstarter.client.core import DriverMethodNotImplemented +from jumpstarter.client.decorators import driver_click_command @dataclass(kw_only=True) @@ -21,11 +22,14 @@ class SSHWrapperClient(CompositeClient): """ def cli(self): - @click.command(context_settings={"ignore_unknown_options": True}) + @driver_click_command( + self, + context_settings={"ignore_unknown_options": True}, + help="Run SSH command with arguments", + ) @click.option("--direct", is_flag=True, help="Use direct TCP address") @click.argument("args", nargs=-1) def ssh(direct, args): - """Run SSH command with arguments""" result = self.run(direct, args) self.logger.debug(f"SSH result: {result}") if result != 0: diff --git a/packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py b/packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py index b71490659..539a57e88 100644 --- a/packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py +++ b/packages/jumpstarter-driver-tmt/jumpstarter_driver_tmt/client.py @@ -7,6 +7,7 @@ from jumpstarter_driver_network.adapters import TcpPortforwardAdapter from jumpstarter.client.core import DriverMethodNotImplemented +from jumpstarter.client.decorators import driver_click_command def redact_password_in_args(args): @@ -33,7 +34,11 @@ class TMTClient(CompositeClient): def cli(self): - @click.command(context_settings={"ignore_unknown_options": True}) + @driver_click_command( + self, + context_settings={"ignore_unknown_options": True}, + help="Run TMT command with arguments", + ) @click.option("--forward-ssh", is_flag=True) @click.option("--tmt-username", default=None) @click.option("--tmt-password", default=None) @@ -41,7 +46,6 @@ def cli(self): @click.option("--tmt-on-exporter", is_flag=True) @click.argument("args", nargs=-1) def tmt(forward_ssh, tmt_username, tmt_password, tmt_cmd, tmt_on_exporter, args): - """Run TMT command with arguments""" if tmt_on_exporter: click.echo("TMT will be run on the exporter") raise click.Abort("Still not implemented") From c1ec0c8899c9098b95499b40152f67d110c6b2cb Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Tue, 7 Oct 2025 09:27:50 +0200 Subject: [PATCH 5/6] use the common method_description infrastructure for shell driver eliminate the extra RPC introduced in https://github.com/jumpstarter-dev/jumpstarter/pull/685 --- .../jumpstarter_driver_shell/client.py | 8 +------- .../jumpstarter_driver_shell/driver.py | 19 +++++++------------ .../jumpstarter_driver_shell/driver_test.py | 16 +++++++++------- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py index 8899c71a1..3f02905b5 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/client.py @@ -75,18 +75,12 @@ def method_command(args, env): if returncode != 0: raise click.exceptions.Exit(returncode) - # Try to get custom description, fall back to default for older than 0.7 servers - try: - description = self.call("get_method_description", method_name) - except Exception: - description = f"Execute the {method_name} shell method" - # Decorate and register the command with help text method_command = click.argument('args', nargs=-1, type=click.UNPROCESSED)(method_command) method_command = click.option('--env', '-e', multiple=True, help='Environment variables in KEY=VALUE format')(method_command) method_command = group.command( name=method_name, - help=description, + help=self.methods_description.get( method_name, f"Execute the {method_name} shell method"), context_settings={"ignore_unknown_options": True, "allow_interspersed_args": False}, )(method_command) diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py index f504b1730..d3bd613b3 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py @@ -21,6 +21,13 @@ class Shell(Driver): timeout: int = 300 cwd: str | None = None + def __post_init__(self): + super().__post_init__() + # Extract descriptions from methods configuration and populate methods_description + for method_name, method_config in self.methods.items(): + if isinstance(method_config, dict) and "description" in method_config: + self.methods_description[method_name] = method_config["description"] + def _get_method_command(self, method: str) -> str: """Extract the command string from a method configuration""" method_config = self.methods[method] @@ -28,13 +35,6 @@ def _get_method_command(self, method: str) -> str: return method_config return method_config.get("command", "echo Hello") - def _get_method_description(self, method: str) -> str: - """Extract the description from a method configuration""" - method_config = self.methods[method] - if isinstance(method_config, str): - return f"Execute the {method} shell method" - return method_config.get("description", f"Execute the {method} shell method") - def _get_method_timeout(self, method: str) -> int: """Extract the timeout from a method configuration, fallback to global timeout""" method_config = self.methods[method] @@ -52,11 +52,6 @@ def get_methods(self) -> list[str]: self.logger.debug(f"get_methods called, returning methods: {methods}") return methods - @export - def get_method_description(self, method: str) -> str: - """Get the description for a specific method""" - return self._get_method_description(method) - @export async def call_method(self, method: str, env, *args) -> AsyncGenerator[tuple[str, str, int | None], None]: """ diff --git a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py index c3621b021..20faad63d 100644 --- a/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py +++ b/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver_test.py @@ -199,24 +199,26 @@ def test_cli_default_descriptions(): assert test_cmd.help == "Execute the test_method shell method" -def test_get_method_description_unified(): - """Test the get_method_description export method with unified format""" +def test_methods_description_populated(): + """Test that methods_description is populated from methods configuration""" shell = Shell( methods={ "method1": { "command": "echo", "description": "Custom description for method1" }, - "method2": "ls", # String format, should use default description + "method2": "ls", # String format, no description in methods_description } ) with serve(shell) as client: - # Test with custom description (unified format) - assert client.call("get_method_description", "method1") == "Custom description for method1" + # Test that custom descriptions are in methods_description + assert "method1" in client.methods_description + assert client.methods_description["method1"] == "Custom description for method1" - # Test with default description (string format) - assert client.call("get_method_description", "method2") == "Execute the method2 shell method" + # Test that string-format methods are not in methods_description + # (will fall back to default in client) + assert "method2" not in client.methods_description def test_mixed_format_methods(): From 354d6d8760869a27e5d046f1008717ae2f3a6c71 Mon Sep 17 00:00:00 2001 From: Michal Skrivanek Date: Tue, 7 Oct 2025 13:46:08 +0200 Subject: [PATCH 6/6] add top level "description" for CLI customization of the root device --- packages/jumpstarter/jumpstarter/config/exporter.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/jumpstarter/jumpstarter/config/exporter.py b/packages/jumpstarter/jumpstarter/config/exporter.py index 25a6781d7..efd4724b6 100644 --- a/packages/jumpstarter/jumpstarter/config/exporter.py +++ b/packages/jumpstarter/jumpstarter/config/exporter.py @@ -91,6 +91,7 @@ class ExporterConfigV1Alpha1(BaseModel): token: str | None = Field(default=None) grpcOptions: dict[str, str | int] | None = Field(default_factory=dict) + description: str | None = None export: dict[str, ExporterConfigV1Alpha1DriverInstance] = Field(default_factory=dict) path: Path | None = Field(default=None) @@ -152,7 +153,11 @@ async def serve_unix_async(self): from jumpstarter.exporter import Session with Session( - root_device=ExporterConfigV1Alpha1DriverInstance(children=self.export).instantiate(), + root_device=ExporterConfigV1Alpha1DriverInstance( + type="jumpstarter_driver_composite.driver.Composite", + description=self.description, + children=self.export, + ).instantiate(), ) as session: async with session.serve_unix_async() as path: yield path @@ -185,7 +190,11 @@ async def channel_factory(): try: exporter = Exporter( channel_factory=channel_factory, - device_factory=ExporterConfigV1Alpha1DriverInstance(children=self.export).instantiate, + device_factory=ExporterConfigV1Alpha1DriverInstance( + type="jumpstarter_driver_composite.driver.Composite", + description=self.description, + children=self.export, + ).instantiate, tls=self.tls, grpc_options=self.grpcOptions, )