Skip to content

Add exporter context env vars to jmp shell and env_with_metadata() helper#291

Open
mangelajo wants to merge 1 commit intomainfrom
feat/exporter-metadata-env
Open

Add exporter context env vars to jmp shell and env_with_metadata() helper#291
mangelajo wants to merge 1 commit intomainfrom
feat/exporter-metadata-env

Conversation

@mangelajo
Copy link
Copy Markdown
Member

@mangelajo mangelajo commented Mar 5, 2026

Summary

  • Sets JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS environment variables when launching a jmp shell with a remote lease, so scripts and commands running inside the shell can discover the exporter context.
  • Adds env_with_metadata() / env_with_metadata_async() context managers and ExporterMetadata dataclass that let Python code retrieve the exporter name, labels, and lease ID from within a shell session.
  • Fetches exporter labels from the controller during lease acquisition and stores them on the Lease object.

Usage

from jumpstarter.common.utils import env_with_metadata

with env_with_metadata() as (client, metadata):
    print(metadata.name)    # e.g. "my-board-exporter"
    print(metadata.labels)  # e.g. {"board": "rpi4", "location": "lab-1"}
    print(metadata.lease)   # e.g. "lease-abc123"

Or directly from the shell environment:

echo $JMP_EXPORTER        # my-board-exporter
echo $JMP_LEASE           # lease-abc123
echo $JMP_EXPORTER_LABELS # board=rpi4,location=lab-1

Closes #53

Test plan

  • Existing tests pass (162 jumpstarter + 39 jumpstarter-cli)
  • New test: test_launch_shell_sets_lease_env verifies env vars are set when a lease is provided
  • New test: test_exporter_metadata_from_env verifies parsing with all env vars set
  • New test: test_exporter_metadata_from_env_empty verifies graceful behavior with no env vars
  • New test: test_exporter_metadata_from_env_labels_with_equals_in_value verifies label values containing =
  • Lint passes cleanly

Made with Cursor

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exporter metadata propagation: new ExporterMetadata and env_with_metadata helpers; new JMP_EXPORTER and JMP_EXPORTER_LABELS env constants; Lease stores exporter_labels and fetches them after acquisition; launch_shell merges lease-derived env vars; tests added for parsing and shell behavior.

Changes

Cohort / File(s) Summary
Configuration & Constants
python/packages/jumpstarter/jumpstarter/config/env.py
Added JMP_EXPORTER and JMP_EXPORTER_LABELS constants.
Exporter Metadata & Environment Helpers
python/packages/jumpstarter/jumpstarter/utils/env.py
Added frozen ExporterMetadata dataclass with from_env(); added env_with_metadata_async() and env_with_metadata() context managers that yield (client, ExporterMetadata); imports updated to use new env constants.
Lease Acquisition
python/packages/jumpstarter/jumpstarter/client/lease.py
Added exporter_labels: dict[str,str] field and async _fetch_exporter_labels(); on successful acquire sets exporter_name and fetches exporter labels (clears on error and logs warning).
Shell Integration & Utils
python/packages/jumpstarter/jumpstarter/common/utils.py
Added _lease_env_vars(lease) helper and updated launch_shell() to merge lease-derived env vars (JMP_EXPORTER, optional JMP_LEASE, JMP_EXPORTER_LABELS).
Tests
python/packages/jumpstarter/jumpstarter/common/utils_test.py
Added tests for launch_shell lease env propagation and ExporterMetadata.from_env() parsing (including '=' in label values).

Sequence Diagram

sequenceDiagram
    participant User
    participant EnvSync as env_with_metadata()
    participant Portal as BlockingPortal
    participant EnvAsync as env_with_metadata_async()
    participant Client
    participant Lease
    participant OS as Environment
    participant Meta as ExporterMetadata

    User->>EnvSync: enter context
    EnvSync->>Portal: start blocking portal
    EnvSync->>EnvAsync: call async helper with portal
    EnvAsync->>Client: create client via env_async()
    EnvAsync->>OS: read JMP_EXPORTER, JMP_EXPORTER_LABELS, JMP_LEASE
    OS-->>Meta: raw env vars
    Meta->>Meta: ExporterMetadata.from_env()
    EnvAsync-->>EnvSync: yield (Client, ExporterMetadata)
    EnvSync-->>User: provide (client, metadata)
    User->>Client: request lease
    Client->>Lease: acquire()
    Lease->>Lease: set exporter_name from result
    Lease->>Lease: call _fetch_exporter_labels()
    Lease->>OS: read/write exporter labels (for shell env)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I nibble env vars, tidy and neat,
Exporter names and labels I keep,
Leases fetched, the shell now knows,
Small hops of code — the context grows,
I thump my paw — deployment's sweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: adding exporter context environment variables to jmp shell and introducing the env_with_metadata() helper.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, explaining the purpose, usage examples, test coverage, and implementation details.
Linked Issues check ✅ Passed The PR fulfills all coding objectives from issue #53: fetches exporter labels during lease acquisition [lease.py], provides env_with_metadata() helper returning client and ExporterMetadata [env.py], sets JMP_EXPORTER/JMP_LEASE/JMP_EXPORTER_LABELS environment variables in jmp shell [utils.py], and includes comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are directly related to the PR objectives: fetching exporter labels in lease acquisition, exposing them via environment variables and the env_with_metadata() helper, with comprehensive tests. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/exporter-metadata-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mangelajo mangelajo requested review from NickCao, evakhoni and kirkbrauer and removed request for kirkbrauer March 5, 2026 20:10
@mangelajo mangelajo force-pushed the feat/exporter-metadata-env branch from 555b95a to 3f784d1 Compare March 5, 2026 20:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/common/utils_test.py (1)

29-45: test_launch_shell_sets_lease_env currently doesn’t validate env propagation.

It only asserts process success; it should also assert that the three lease/exporter env vars are actually present.

Suggested test strengthening
-def test_launch_shell_sets_lease_env(tmp_path, monkeypatch):
+def test_launch_shell_sets_lease_env(tmp_path, monkeypatch, capsys):
     monkeypatch.setenv("SHELL", shutil.which("env"))
     lease = SimpleNamespace(
         exporter_name="my-exporter",
         name="lease-123",
         exporter_labels={"board": "rpi4", "location": "lab-1"},
         lease_ending_callback=None,
     )
     exit_code = launch_shell(
         host=str(tmp_path / "test.sock"),
         context="my-exporter",
         allow=["*"],
         unsafe=False,
         use_profiles=False,
         lease=lease,
     )
     assert exit_code == 0
+    out = capsys.readouterr().out
+    assert "JMP_EXPORTER=my-exporter" in out
+    assert "JMP_LEASE=lease-123" in out
+    assert "JMP_EXPORTER_LABELS=board=rpi4,location=lab-1" in out
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py` around lines 29
- 45, The test only checks exit code but must also verify the launched shell
received lease-related env vars: update test_launch_shell_sets_lease_env to
capture the launched process stdout (e.g., use capsys or patch
subprocess.run/capture_output) when SHELL is set to the env binary and assert
the output contains the lease.exporter_name, lease.name, and each key=value from
lease.exporter_labels so you verify the three lease/exporter env vars are
actually propagated by launch_shell.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/client/lease.py`:
- Around line 142-149: The _fetch_exporter_labels method can leave stale
self.exporter_labels when svc.GetExporter fails; modify the exception path in
_fetch_exporter_labels (the async method) to clear or reset self.exporter_labels
(e.g., set to {} or None) when an exception occurs and include the exporter name
in the debug log (use logger.debug("Could not fetch labels for exporter %s: %s",
self.exporter_name, e) or similar) so reused lease instances do not retain
previous labels.

In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py`:
- Around line 70-75: The test
test_exporter_metadata_from_env_labels_with_equals_in_value triggers typo-linter
noise because of the sample value; update the fixture to use a less typo-like
sample while preserving the edge case (an equals sign inside a label value),
e.g. replace "key=val=ue,other=ok" with something like "key=a=b,other=ok", keep
the monkeypatch calls and the assertion that ExporterMetadata.from_env() yields
meta.labels == {"key": "a=b", "other": "ok"}.

---

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py`:
- Around line 29-45: The test only checks exit code but must also verify the
launched shell received lease-related env vars: update
test_launch_shell_sets_lease_env to capture the launched process stdout (e.g.,
use capsys or patch subprocess.run/capture_output) when SHELL is set to the env
binary and assert the output contains the lease.exporter_name, lease.name, and
each key=value from lease.exporter_labels so you verify the three lease/exporter
env vars are actually propagated by launch_shell.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 250a74dd-bc0f-4834-8909-f4326472e1c6

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfa067 and 555b95a.

📒 Files selected for processing (5)
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
  • python/packages/jumpstarter/jumpstarter/config/env.py
  • python/packages/jumpstarter/jumpstarter/utils/env.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/utils/env.py (1)

13-35: Consider using immutable mapping for labels field to strengthen frozen dataclass contract.

ExporterMetadata is marked frozen=True to indicate immutable semantics, but labels: dict[str, str] can still be mutated via direct assignment (e.g., metadata.labels['key'] = 'value'). Since ExporterMetadata is exported as public API, wrapping it with MappingProxyType would prevent accidental mutation and clarify intent.

♻️ Suggested refactor
 import os
 from contextlib import ExitStack, asynccontextmanager, contextmanager
 from dataclasses import dataclass, field
+from types import MappingProxyType
+from typing import Mapping
@@
 `@dataclass`(frozen=True)
 class ExporterMetadata:
@@
-    labels: dict[str, str] = field(default_factory=dict)
+    labels: Mapping[str, str] = field(default_factory=lambda: MappingProxyType({}))
@@
-        return cls(name=name, labels=labels, lease=lease)
+        return cls(name=name, labels=MappingProxyType(labels), lease=lease)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/utils/env.py` around lines 13 - 35,
ExporterMetadata is frozen but exposes a mutable dict via the labels field;
change labels to an immutable mapping and wrap any dicts produced in from_env
with types.MappingProxyType (or change the annotation to Mapping[str,str] and
return MappingProxyType(labels)) so callers cannot mutate the labels after
construction; update the dataclass field default to produce an immutable empty
mapping via MappingProxyType({}) (and add the import for MappingProxyType and
typing.Mapping if needed) while keeping the class name ExporterMetadata and the
factory method from_env unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/utils/env.py`:
- Around line 13-35: ExporterMetadata is frozen but exposes a mutable dict via
the labels field; change labels to an immutable mapping and wrap any dicts
produced in from_env with types.MappingProxyType (or change the annotation to
Mapping[str,str] and return MappingProxyType(labels)) so callers cannot mutate
the labels after construction; update the dataclass field default to produce an
immutable empty mapping via MappingProxyType({}) (and add the import for
MappingProxyType and typing.Mapping if needed) while keeping the class name
ExporterMetadata and the factory method from_env unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96ff34ba-0502-43d4-b638-ac73844f7698

📥 Commits

Reviewing files that changed from the base of the PR and between 555b95a and 3f784d1.

📒 Files selected for processing (5)
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
  • python/packages/jumpstarter/jumpstarter/config/env.py
  • python/packages/jumpstarter/jumpstarter/utils/env.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py

@evakhoni
Copy link
Copy Markdown
Contributor

Or directly from the shell environment:
echo $JMP_EXPORTER # my-board-exporter
echo $JMP_LEASE # lease-abc123
echo $JMP_EXPORTER_LABELS # board=rpi4,location=lab-1

I wonder what would happen with complex selectors. are we fetching the selector we leased with, or the labels of the exporter that ended up matching that selector?

@mangelajo mangelajo force-pushed the feat/exporter-metadata-env branch from 3f784d1 to 39b3926 Compare March 12, 2026 12:35
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 12, 2026

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit 91ca0d3
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69d6637b9d1dde0008471f78
😎 Deploy Preview https://deploy-preview-291--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@mangelajo mangelajo force-pushed the feat/exporter-metadata-env branch from 39b3926 to 8b28485 Compare March 12, 2026 12:38
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/common/utils_test.py (1)

29-45: Consider verifying the environment variables are actually set.

The test confirms launch_shell accepts a lease and exits successfully, but doesn't verify that JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS are actually propagated to the subprocess environment.

🧪 Optional enhancement to capture and verify env vars
 def test_launch_shell_sets_lease_env(tmp_path, monkeypatch):
-    monkeypatch.setenv("SHELL", shutil.which("env"))
+    # Use a script that outputs specific env vars to a file
+    env_output = tmp_path / "env_output.txt"
+    script = tmp_path / "capture_env.sh"
+    script.write_text(f"""#!/bin/sh
+echo "JMP_EXPORTER=$JMP_EXPORTER" >> {env_output}
+echo "JMP_LEASE=$JMP_LEASE" >> {env_output}
+echo "JMP_EXPORTER_LABELS=$JMP_EXPORTER_LABELS" >> {env_output}
+""")
+    script.chmod(0o755)
+    monkeypatch.setenv("SHELL", str(script))
     lease = SimpleNamespace(
         exporter_name="my-exporter",
         name="lease-123",
         exporter_labels={"board": "rpi4", "location": "lab-1"},
         lease_ending_callback=None,
     )
     exit_code = launch_shell(
         host=str(tmp_path / "test.sock"),
         context="my-exporter",
         allow=["*"],
         unsafe=False,
         use_profiles=False,
         lease=lease,
     )
     assert exit_code == 0
+    output = env_output.read_text()
+    assert "JMP_EXPORTER=my-exporter" in output
+    assert "JMP_LEASE=lease-123" in output
+    assert "board=rpi4" in output
+    assert "location=lab-1" in output
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py` around lines 29
- 45, Update test_launch_shell_sets_lease_env to verify the env vars passed to
the child process by mocking the subprocess invocation used inside launch_shell
(e.g., subprocess.run or subprocess.Popen depending on implementation) so you
can capture the env dict passed to it; call launch_shell with the same lease
fixture and then assert the captured env contains keys/values for JMP_EXPORTER
== lease.exporter_name, JMP_LEASE == lease.name, and JMP_EXPORTER_LABELS ==
json.dumps(lease.exporter_labels) (or the exact serialization used by
launch_shell). Ensure you restore/cleanup the mock so other tests are
unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py`:
- Around line 29-45: Update test_launch_shell_sets_lease_env to verify the env
vars passed to the child process by mocking the subprocess invocation used
inside launch_shell (e.g., subprocess.run or subprocess.Popen depending on
implementation) so you can capture the env dict passed to it; call launch_shell
with the same lease fixture and then assert the captured env contains
keys/values for JMP_EXPORTER == lease.exporter_name, JMP_LEASE == lease.name,
and JMP_EXPORTER_LABELS == json.dumps(lease.exporter_labels) (or the exact
serialization used by launch_shell). Ensure you restore/cleanup the mock so
other tests are unaffected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59d1ab79-7ea0-470d-844b-24ea3e260ccd

📥 Commits

Reviewing files that changed from the base of the PR and between 3f784d1 and 39b3926.

📒 Files selected for processing (5)
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
  • python/packages/jumpstarter/jumpstarter/config/env.py
  • python/packages/jumpstarter/jumpstarter/utils/env.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/common/utils.py (1)

87-97: Consider adding type hint for the lease parameter.

The function works correctly but lacks a type annotation for lease. Adding a type hint would improve IDE support and documentation.

📝 Suggested type annotation
-def _lease_env_vars(lease) -> dict[str, str]:
+def _lease_env_vars(lease: "Lease") -> dict[str, str]:

You would need to add the import under TYPE_CHECKING:

 if TYPE_CHECKING:
     from jumpstarter.driver import Driver
+    from jumpstarter.client.lease import Lease
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 87 -
97, The _lease_env_vars function is missing a type annotation for the lease
parameter; update its signature to include the lease type (e.g., lease: Lease)
and add a conditional import for that type under TYPE_CHECKING (from typing
import TYPE_CHECKING; if TYPE_CHECKING: from <module> import Lease) so you don't
create runtime import cycles; update any forward references if needed and keep
existing logic using JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 87-97: The _lease_env_vars function is missing a type annotation
for the lease parameter; update its signature to include the lease type (e.g.,
lease: Lease) and add a conditional import for that type under TYPE_CHECKING
(from typing import TYPE_CHECKING; if TYPE_CHECKING: from <module> import Lease)
so you don't create runtime import cycles; update any forward references if
needed and keep existing logic using JMP_EXPORTER, JMP_LEASE, and
JMP_EXPORTER_LABELS unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1fea5fdb-d1aa-4ef1-be79-9551a75efed5

📥 Commits

Reviewing files that changed from the base of the PR and between 39b3926 and 8b28485.

📒 Files selected for processing (5)
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
  • python/packages/jumpstarter/jumpstarter/config/env.py
  • python/packages/jumpstarter/jumpstarter/utils/env.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter/jumpstarter/client/lease.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

Copy link
Copy Markdown
Contributor

@raballew raballew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label serialization (key1=val1,key2=val2) will break on Kubernetes label values that contain commas -- e.g. env=dev,staging would get parsed as two separate labels. JSON would handle this cleanly, or at least reject/document the limitation.

Also the test test_launch_shell_sets_lease_env only checks exit_code == 0 but doesn't actually verify the env vars show up in the output. And env_with_metadata() / env_with_metadata_async() and _fetch_exporter_labels don't have tests yet.

Copy link
Copy Markdown
Contributor

@ambient-code ambient-code bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the comma concern: Kubernetes label values are restricted to alphanumerics, -, _, and . (regex: ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]). Commas are not valid in label keys or values, so env=dev,staging would be rejected by the Kubernetes API. The comma-separated serialization is therefore safe for valid Kubernetes labels.

The points about test coverage are valid though — will look into adding tests for env_with_metadata() / env_with_metadata_async() and _fetch_exporter_labels.

@ambient-code ambient-code bot force-pushed the feat/exporter-metadata-env branch from 8b28485 to 7347901 Compare April 6, 2026 17:03
@mangelajo mangelajo dismissed raballew’s stale review April 6, 2026 17:04

Kubernetes labels cannot have commas in key or values.

@mangelajo mangelajo enabled auto-merge (rebase) April 6, 2026 17:05
@mangelajo mangelajo requested a review from raballew April 7, 2026 07:27
use_profiles=False,
lease=lease,
)
assert exit_code == 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test proves the shell launched but doesn't actually check that JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS were set to the right values. Since SHELL is set to env, you could capture stdout and assert on the expected env var values - or use a small wrapper script that writes specific vars to a temp file for verification.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — rewrote the test to use a capture script that writes the env vars to a temp file, then asserts on JMP_EXPORTER=my-exporter, JMP_LEASE=lease-123, and the label values.

Comment on lines +184 to +191
async def _fetch_exporter_labels(self):
"""Fetch the exporter's labels after lease acquisition."""
try:
exporter = await self.svc.GetExporter(name=self.exporter_name)
self.exporter_labels = exporter.labels
except Exception as e:
self.exporter_labels = {}
logger.warning("Could not fetch labels for exporter %s: %s", self.exporter_name, e)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has two code paths (success + exception fallback) but neither is directly unit-tested. The happy path only runs during full lease acquisition against a real controller. Would be nice to have a couple of unit tests mocking self.svc.GetExporter - one for success, one for the exception fallback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test_fetch_exporter_labels_success and test_fetch_exporter_labels_failure — both mock svc.GetExporter. The failure test also verifies stale labels are cleared to {}.

Comment on lines +108 to +129
def _build_common_env(
host: str,
allow: list[str],
unsafe: bool,
*,
lease=None,
insecure: bool = False,
passphrase: str | None = None,
) -> dict[str, str]:
"""Build the base environment dict for shell/command processes."""
env = os.environ | {
JUMPSTARTER_HOST: host,
JMP_DRIVERS_ALLOW: "UNSAFE" if unsafe else ",".join(allow),
"_JMP_SUPPRESS_DRIVER_WARNINGS": "1", # Already warned during client initialization
}
if insecure:
env = env | {JMP_GRPC_INSECURE: "1"}
if passphrase:
env = env | {JMP_GRPC_PASSPHRASE: passphrase}
if lease is not None:
env.update(_lease_env_vars(lease))
return env
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New helper with a decent parameter matrix (insecure, passphrase, lease combos) but it's only tested indirectly through launch_shell(). Direct unit tests for this function would make it easier to catch regressions - especially edge cases like passphrase="" (falsy, so JMP_GRPC_PASSPHRASE won't be set - is that intentional?).

AI-generated, human reviewed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added direct unit tests for _build_common_env (minimal, unsafe, insecure, passphrase, empty passphrase, with lease) and _lease_env_vars. Re: passphrase="" — it's intentionally falsy so JMP_GRPC_PASSPHRASE won't be set, consistent with the str | None typing. Added a test to document this behavior.

Comment on lines +38 to +41
for pair in raw_labels.split(","):
if "=" in pair:
k, v = pair.split("=", 1)
labels[k] = v
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: if JMP_EXPORTER_LABELS contains something like =value, this creates {"": "value"} since split("=", 1) yields ["", "value"]. Unlikely with k8s labels but a quick if k: guard before the assignment would be cleaner.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The labels really come from k8s, they cannot have that form :) but an "if k:" really won't hurt, I am buying :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added the if k: guard. Also added a test (test_exporter_metadata_from_env_ignores_empty_key) to cover this case.

@ambient-code ambient-code bot force-pushed the feat/exporter-metadata-env branch 3 times, most recently from 1b0b739 to c209fb5 Compare April 7, 2026 09:25
Copy link
Copy Markdown
Contributor

@ambient-code ambient-code bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed all review comments in c209fb5:

  • env.py empty-key guard: Added if k: guard in label parsing as suggested
  • utils_test.py env var verification: Rewrote test_launch_shell_sets_lease_env to capture env vars via a shell script and assert on JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS values
  • lease.py _fetch_exporter_labels tests: Added test_fetch_exporter_labels_success and test_fetch_exporter_labels_failure that mock svc.GetExporter for both code paths (also verifies stale labels are cleared to {})
  • utils.py _build_common_env tests: Added direct unit tests covering minimal, unsafe, insecure, passphrase, empty passphrase, and lease scenarios. Also added tests for _lease_env_vars. Note: empty string passphrase ("") is intentionally falsy so JMP_GRPC_PASSPHRASE won't be set — this is consistent with how passphrase is handled elsewhere (typed as str | None).

@ambient-code ambient-code bot requested a review from raballew April 7, 2026 09:30
@ambient-code ambient-code bot force-pushed the feat/exporter-metadata-env branch from c209fb5 to 7cb3d5a Compare April 7, 2026 14:04
@mangelajo
Copy link
Copy Markdown
Member Author

@ambient-code please rebase this

…lper

Set JMP_EXPORTER, JMP_LEASE, and JMP_EXPORTER_LABELS environment variables
when launching a shell with a remote lease. Add ExporterMetadata dataclass
and env_with_metadata() context managers for retrieving exporter context
from within a shell session. Fetch exporter labels from the controller
during lease acquisition.

Closes #53

Co-Authored-By: Miguel Angel Ajo Pelayo <miguelangel@ajo.es>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ambient-code ambient-code bot force-pushed the feat/exporter-metadata-env branch from 7cb3d5a to 91ca0d3 Compare April 8, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

exporter context under a shell

3 participants