Skip to content

feat: structured log gathering from exporters (JEP-0013) - #930

Merged
bkhizgiy merged 7 commits into
jumpstarter-dev:mainfrom
bkhizgiy:exporter-logs
Aug 5, 2026
Merged

feat: structured log gathering from exporters (JEP-0013)#930
bkhizgiy merged 7 commits into
jumpstarter-dev:mainfrom
bkhizgiy:exporter-logs

Conversation

@bkhizgiy

Copy link
Copy Markdown
Member

Exporters now push structured logs to an optional jumpstarter-telemetry service, giving operators a single place to query what happened during any lease , which pipeline ran it, what driver operation failed, and with what result.

What changed:
New proto RPCs - GetServiceEndpoints lets exporters discover the telemetry service after registration. TelemetryService.PushLogs receives batched structured log entries.

Controller config - A new telemetry section in the controller ConfigMap (endpoint, certificate, min_severity) tells authenticated exporters where to send logs.

jumpstarter-telemetry service - A standalone Go binary that receives log entries and writes them to structured stdout. Ready to forward to Loki in a future phase without any protocol changes.

TelemetryLogHandler (Python) - A logging.Handler attached to the root logger after registration. It pulls correlation fields from structlog contextvars (lease_id, client, exporter) and spec.context fields (build_id, pipeline, etc.), batches them, and flushes every 500ms via PushLogs. Telemetry failures are silently dropped — device operations are never blocked.

What you can do with it:
A single log query like {lease="telemetry-test-4"} returns every event from that lease's full lifecycle. {pipeline="ci-main"} finds all leases across any run tagged with that pipeline. The correlation data answers questions like "which pipeline was running on this lease?" and "what driver operation failed?" without joining multiple sources.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

This PR adds telemetry protobuf contracts, controller endpoint discovery, authenticated Go telemetry ingestion, and Python exporter log forwarding. It also consolidates controller configuration and adds a standalone telemetry service command with graceful shutdown.

Changes

Telemetry collection

Layer / File(s) Summary
Telemetry and endpoint RPC contracts
protocol/proto/jumpstarter/v1/*, python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/*
Adds PushLogs and GetServiceEndpoints APIs with generated Python clients, servers, and type stubs.
Controller telemetry configuration and discovery
controller/internal/config/*, controller/internal/oidc/op.go, controller/internal/service/controller_service.go, controller/cmd/main.go
Consolidates configuration, validates telemetry settings, derives endpoints, authenticates exporters, and returns telemetry endpoint data.
Telemetry service process and log ingestion
controller/internal/service/telemetry_service.go, controller/internal/service/telemetry_service_test.go, controller/cmd/telemetry/main.go
Adds authenticated batch ingestion, structured logging, limits, gRPC startup, signal handling, and tests.
Exporter telemetry batching and lifecycle
python/packages/jumpstarter/jumpstarter/exporter/*, python/packages/jumpstarter/jumpstarter/config/exporter.py
Discovers endpoints, configures transport, converts log records, flushes batches, and closes telemetry resources.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: enhancement, go, python, protocol

Suggested reviewers: bennyz

Poem

A rabbit sends logs through the burrow-wide stream,
Tokens and batches align in a clean little scheme.
Endpoints are found, then messages take flight,
TLS guards the path through the soft moonlight.
Flush, close, and hop—telemetry is right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.94% 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 title clearly summarizes the primary change: structured log gathering from exporters under JEP-0013.
Description check ✅ Passed The description directly explains exporter telemetry, protocol changes, controller configuration, and the standalone telemetry service.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@bkhizgiy

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 9

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py (1)

1-267: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an end-to-end test alongside the mocked unit tests.

All tests here mock TelemetryServiceStub.PushLogs. Per repo guidelines for *_test.py, end-to-end tests that start a real server and client should be prioritized, with mocks reserved for cases where e2e is impractical. Since TelemetryServiceServicer can be run in-process with a local gRPC server, an e2e test validating a full TelemetryLogHandler → real stub → real servicer round trip would strengthen coverage of the wire contract (serialization, field mapping) beyond what mocks can verify.

As per coding guidelines, "Provide comprehensive package test coverage, prioritizing end-to-end tests that start a server and client; use mocks when system tools, services, or platform compatibility make end-to-end testing impractical."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py` around
lines 1 - 267, Add an end-to-end test in TestFlush that starts an in-process
local gRPC server with TelemetryServiceServicer, creates a real
TelemetryServiceStub and TelemetryLogHandler, emits a record, and flushes it.
Assert the servicer receives the serialized entry with the expected message,
severity, and mapped fields, while keeping mocked tests for failure and queue
behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/cmd/telemetry/main.go`:
- Around line 78-86: Update the signal-handling branch in main’s select to wait
for the telemetry service shutdown after calling cancel(). Receive from errCh
before returning so TelemetryService.Start() can execute GracefulStop() and
drain active RPCs; preserve the existing error logging and exit behavior for
service failures.
- Around line 66-72: Update the telemetry service startup around
TelemetryService and svc.Start to configure TLS for the ingress listener,
require and validate client certificates, and authorize authenticated clients
before accepting batches. Ensure discovery publishes the matching client
credentials and TLS settings so exporters connect securely without insecure
mode; preserve the existing service lifecycle and error propagation.

In `@controller/internal/service/telemetry_service.go`:
- Around line 85-111: Update TelemetryService.Start to configure grpc.NewServer
with TLS credentials derived from TelemetryConfig.Certificate and its
corresponding private key before registering the service, ensuring the server
uses the same certificate advertised by GetServiceEndpoints. Preserve the
existing lifecycle and graceful-shutdown behavior, and propagate
certificate-loading or server-setup errors.
- Around line 43-82: Bound unauthenticated batches in TelemetryService.PushLogs
by enforcing a server-side maximum number of entries before iterating and
logging them. Process only entries within the limit, report the remainder
through the response’s Dropped field, and preserve Accepted for processed
entries; use an established configuration or constant for the limit if
available.

In `@protocol/proto/jumpstarter/v1/telemetry.proto`:
- Around line 30-40: Update TelemetryService.PushLogs to preserve each log
entry’s Timestamp and Severity in the sink output. Use entry.Timestamp as the
emitted event timestamp rather than ingestion time, and retain entry.Severity as
a structured field so warning and debug entries remain distinguishable.

In
`@python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py`:
- Around line 33-34: Regenerate client_pb2.py and the related protobuf classes
using one consistent protoc/grpcio-tools configuration, ensuring its
kubernetes_pb2 and common_pb2 imports match the relative ..v1 imports used by
the other generated pb2 files. Do not hand-edit the generated output; update the
generation process or invocation if needed, then regenerate the affected files
consistently.

In `@python/packages/jumpstarter/jumpstarter/exporter/exporter.py`:
- Around line 1181-1188: Update the shutdown flow around
_telemetry_handler.close_async() to drain all queued telemetry batches before
closing _telemetry_channel. Repeat flushing until the handler’s queue is empty,
then proceed with the existing handler removal and channel closure.
- Around line 408-434: Update the telemetry setup around TelemetryLogHandler so
it receives ep.min_severity when constructed, and enforce that threshold when
processing exporter records. Preserve the existing channel selection and handler
registration, ensuring records below the controller-advertised minimum severity
are filtered out.
- Around line 393-397: Bound both best-effort telemetry RPCs with _RPC_TIMEOUT:
pass the deadline when awaiting GetServiceEndpoints in the exporter registration
flow, and apply the same deadline to PushLogs in TelemetryLogHandler._flush().
Preserve existing error handling while ensuring registration, periodic flushing,
and shutdown cannot wait indefinitely.

---

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py`:
- Around line 1-267: Add an end-to-end test in TestFlush that starts an
in-process local gRPC server with TelemetryServiceServicer, creates a real
TelemetryServiceStub and TelemetryLogHandler, emits a record, and flushes it.
Assert the servicer receives the serialized entry with the expected message,
severity, and mapped fields, while keeping mocked tests for failure and queue
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7210c4a5-f9a9-4494-a887-2484a984d27d

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb0364 and df77e4d.

⛔ Files ignored due to path filters (4)
  • controller/internal/protocol/jumpstarter/v1/jumpstarter.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/telemetry.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/telemetry_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • controller/cmd/main.go
  • controller/cmd/telemetry/main.go
  • controller/deploy/operator/config/crd/bases/virtualtarget.jumpstarter.dev_exportersets.yaml
  • controller/internal/config/config.go
  • controller/internal/config/types.go
  • controller/internal/service/controller_service.go
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go
  • protocol/proto/jumpstarter/v1/jumpstarter.proto
  • protocol/proto/jumpstarter/v1/telemetry.proto
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/__init__.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py

Comment thread controller/cmd/telemetry/main.go
Comment thread controller/cmd/telemetry/main.go
Comment thread controller/internal/service/telemetry_service.go
Comment thread controller/internal/service/telemetry_service.go
Comment thread protocol/proto/jumpstarter/v1/telemetry.proto
Comment thread python/packages/jumpstarter/jumpstarter/exporter/exporter.py
Comment thread python/packages/jumpstarter/jumpstarter/exporter/exporter.py
Comment thread python/packages/jumpstarter/jumpstarter/exporter/exporter.py Outdated
@bkhizgiy

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter/jumpstarter/exporter/exporter.py (1)

404-414: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep all optional telemetry setup failures non-fatal.

The handler catches only grpc.aio.AioRpcError from endpoint discovery. channel_factory() and the subsequent telemetry channel/handler setup can raise other exceptions; since _register_with_controller() awaits this after registration, those failures can abort exporter startup despite telemetry being optional. Wrap the complete setup in best-effort handling and clean up any partially created telemetry resources.

Also applies to: 429-447

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/exporter.py` around lines
404 - 414, Update _register_with_controller around endpoint discovery and
telemetry setup so every optional telemetry failure, including channel_factory
and channel/handler construction errors, is caught and cannot abort exporter
startup. Wrap the complete setup in best-effort exception handling and clean up
any partially created telemetry resources before returning on failure, while
preserving successful registration behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/internal/service/telemetry_service.go`:
- Around line 120-122: Update the context-cancellation branch around
srv.GracefulStop() to normalize grpc.ErrServerStopped from errCh as a successful
shutdown when ctx is canceled. Preserve and return other Serve errors unchanged.

---

Outside diff comments:
In `@python/packages/jumpstarter/jumpstarter/exporter/exporter.py`:
- Around line 404-414: Update _register_with_controller around endpoint
discovery and telemetry setup so every optional telemetry failure, including
channel_factory and channel/handler construction errors, is caught and cannot
abort exporter startup. Wrap the complete setup in best-effort exception
handling and clean up any partially created telemetry resources before returning
on failure, while preserving successful registration behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fea4c4f-3b1c-4e0a-818d-db584454eee2

📥 Commits

Reviewing files that changed from the base of the PR and between df77e4d and 2c998b6.

📒 Files selected for processing (6)
  • controller/cmd/telemetry/main.go
  • controller/internal/config/config.go
  • controller/internal/config/types.go
  • controller/internal/service/telemetry_service.go
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py

Comment thread controller/internal/service/telemetry_service.go Outdated
@bkhizgiy

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter/jumpstarter/exporter/exporter.py (1)

404-414: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep optional telemetry setup non-fatal.

Only AioRpcError is handled. An ordinary exception from _controller_stub() can escape after registration and abort the exporter, despite telemetry being best-effort. Catch Exception here as well, while allowing cancellation (BaseException) through.

Proposed fix
         except grpc.aio.AioRpcError as e:
             logger.debug("GetServiceEndpoints unavailable: %s", e.code())
             return
+        except Exception:
+            logger.debug("GetServiceEndpoints setup failed", exc_info=True)
+            return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/exporter.py` around lines
404 - 414, Update the GetServiceEndpoints setup around _controller_stub() to
catch ordinary Exception failures in addition to grpc.aio.AioRpcError, log the
failure at debug level, and return without aborting exporter registration. Keep
cancellation and other BaseException subclasses propagating, and preserve the
existing successful response flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/exporter/exporter_telemetry_test.py`:
- Around line 33-42: Update the telemetry setup tests using make_bare_exporter
to track the root logger handler created by each test, then add teardown cleanup
that removes and closes that handler after every test. Ensure cleanup runs even
when a test fails and does not affect unrelated root logger handlers.

In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry.py`:
- Around line 134-137: Update close_async so shutdown does not retry every
queued batch after telemetry becomes unavailable. Track whether _flush()
successfully pushed the batch and stop draining immediately after the first
failure, or enforce a single overall shutdown deadline while preserving the
existing flush behavior.

---

Outside diff comments:
In `@python/packages/jumpstarter/jumpstarter/exporter/exporter.py`:
- Around line 404-414: Update the GetServiceEndpoints setup around
_controller_stub() to catch ordinary Exception failures in addition to
grpc.aio.AioRpcError, log the failure at debug level, and return without
aborting exporter registration. Keep cancellation and other BaseException
subclasses propagating, and preserve the existing successful response flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f51c6ad4-021b-4305-b619-e4e28a24c309

📥 Commits

Reviewing files that changed from the base of the PR and between 2c998b6 and 264e112.

📒 Files selected for processing (6)
  • controller/cmd/telemetry/main.go
  • controller/internal/service/telemetry_service.go
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter_telemetry_test.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py

Comment on lines +33 to +42
def make_bare_exporter():
"""Minimal Exporter instance for testing _setup_telemetry in isolation."""
exp = Exporter.__new__(Exporter)
exp._telemetry_handler = None
exp._telemetry_channel = None

tls = MagicMock()
tls.insecure = False
exp.tls = tls
return exp

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clean up root handlers after each setup test.

Successful setup tests register a handler on the global root logger but never remove it. Handlers accumulate across the module and can capture later tests’ logs. Add teardown that removes and closes the handler created by each test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/exporter_telemetry_test.py`
around lines 33 - 42, Update the telemetry setup tests using make_bare_exporter
to track the root logger handler created by each test, then add teardown cleanup
that removes and closes that handler after every test. Ensure cleanup runs even
when a test fails and does not affect unrelated root logger handlers.

Comment on lines +134 to +137
async def close_async(self) -> None:
"""Flush all remaining entries and close the handler."""
while self._queue:
await self._flush()

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not multiply shutdown time by queued batches.

_flush() drops a failed batch but hides the failure. With telemetry unavailable, this loop waits up to 10 seconds for each queued batch before exit. Stop draining after the first failed push, or apply one total shutdown deadline.

Proposed fix
-    async def _flush(self) -> None:
+    async def _flush(self) -> bool:
         ...
         try:
             await self._stub.PushLogs(
                 telemetry_pb2.PushLogsRequest(entries=batch),
                 timeout=_PUSH_TIMEOUT,
             )
         except Exception as exc:
             print(f"[telemetry] PushLogs failed, {len(batch)} entries dropped: {exc}", file=sys.stderr)
+            return False
+        return True

     async def close_async(self) -> None:
         """Flush all remaining entries and close the handler."""
         while self._queue:
-            await self._flush()
+            if not await self._flush():
+                break
         self.close()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def close_async(self) -> None:
"""Flush all remaining entries and close the handler."""
while self._queue:
await self._flush()
async def close_async(self) -> None:
"""Flush all remaining entries and close the handler."""
while self._queue:
if not await self._flush():
break
self.close()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry.py` around lines
134 - 137, Update close_async so shutdown does not retry every queued batch
after telemetry becomes unavailable. Track whether _flush() successfully pushed
the batch and stop draining immediately after the first failure, or enforce a
single overall shutdown deadline while preserving the existing flush behavior.

@bkhizgiy
bkhizgiy marked this pull request as ready for review July 30, 2026 14:40

@RoddieKieley RoddieKieley left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mostly nit-pick type comments on minor details for the moment. May have more detailed comments after building the branch and testing it in-cluster.

Comment thread protocol/proto/jumpstarter/v1/telemetry.proto Outdated
Comment thread controller/internal/config/config.go Outdated
var configmap corev1.ConfigMap
if err := client.Get(ctx, key, &configmap); err != nil {
return nil, "", nil, nil, nil, nil, nil, err
return nil, "", nil, nil, nil, nil, nil, nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I've noticed that the LLM's don't mind not being dry, an in this case there's quite a few of this particular return value repeated. However prior to this code change it was only 1 nil different so for the moment it's probably fine without the refactor. This is probably one of those areas where adopting some clean code type rules to guide the coding agent up front could help.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, a few PRs ago we had the same conversation with HiddenLabels.

It could be worth to return all this as a config structure, and then just pass the structure to the controller later in https://github.com/jumpstarter-dev/jumpstarter/pull/930/changes#diff-032eafab48326b32dad354dd24c3da532106bde78b3589793bd8fdc47be0bb65R283 as a single thing, we could also return "nil" config :) single nil

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.

Done, added LoadConfiguration now returns (*LoadedConfig, error), a single struct that carries all the previously separate return values. All error paths return a single nil, err. The call site in main.go was updated accordingly.

Comment thread controller/cmd/telemetry/main.go Outdated
Comment thread controller/internal/service/telemetry_service.go Outdated
Comment thread controller/internal/service/telemetry_service_test.go Outdated
Comment thread python/packages/jumpstarter/jumpstarter/exporter/telemetry.py Outdated
Comment thread python/packages/jumpstarter/jumpstarter/exporter/telemetry.py
Comment thread controller/internal/config/types.go Outdated
Certificate string `json:"certificate,omitempty" yaml:"certificate,omitempty"`

// MinSeverity is the minimum log severity to forward to the telemetry service.
// Accepted values: debug, info, warning, error. Defaults to "info" when empty.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this correct? Is there also a critical that is mentioned elsewhere in the code?
"
"""Normalise Python logging level names to JEP-0013 severity strings."""
return {
"DEBUG": "debug",
"INFO": "info",
"WARNING": "warning",
"ERROR": "error",
"CRITICAL": "critical",
"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

def _severity_to_level(severity: str) -> int:

@RoddieKieley

Copy link
Copy Markdown

@bkhizgiy I ran up your exporter-logs branch on top of my local setup here and confirm local exporter logging output:
Screenshot From 2026-07-31 08-42-05

matches the log output as viewed via LogQL inside the local openshift sno cluster:
Screenshot From 2026-07-31 08-42-15

I did have to manually configure and expose a Service to connect to the jumpstarter-telemetry pods endpoint but I think that's expected at this point of the phased implementation of JEP-0013.

Comment thread controller/internal/config/config.go Outdated
var configmap corev1.ConfigMap
if err := client.Get(ctx, key, &configmap); err != nil {
return nil, "", nil, nil, nil, nil, nil, err
return nil, "", nil, nil, nil, nil, nil, nil, err

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, a few PRs ago we had the same conversation with HiddenLabels.

It could be worth to return all this as a config structure, and then just pass the structure to the controller later in https://github.com/jumpstarter-dev/jumpstarter/pull/930/changes#diff-032eafab48326b32dad354dd24c3da532106bde78b3589793bd8fdc47be0bb65R283 as a single thing, we could also return "nil" config :) single nil

Comment thread controller/internal/config/types.go Outdated
Certificate string `json:"certificate,omitempty" yaml:"certificate,omitempty"`

// MinSeverity is the minimum log severity to forward to the telemetry service.
// Accepted values: debug, info, warning, error. Defaults to "info" when empty.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

def _severity_to_level(severity: str) -> int:

Name is the key used in the ExporterConfig export map.
If omitted, derived from the Type (last segment after the last dot).
description: Name is the key used in the ExporterConfig
export map.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this change sneaked in? :)

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.

haha yep, good eye was removed.

// controller-runtime logger (structured JSON to stdout).
// Future phase: forward to Loki push API.
func (s *TelemetryService) PushLogs(ctx context.Context, req *pb.PushLogsRequest) (*pb.PushLogsResponse, error) {
logger := ctrl.Log.WithName("telemetry")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we need to authenticate the exporter from the call.

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.

Done, PushLogs now validates the bearer token and rejects any token whose subject is not in exporter:namespace:name:uid form (client tokens and other validly signed tokens are rejected with PermissionDenied). Entries whose exporter or namespace fields don't match the token's identity are counted as dropped rather than failing the whole batch.

LeasePolicy LeasePolicy `json:"leasePolicy,omitempty" yaml:"leasePolicy,omitempty"`
HiddenLabels HiddenLabels `json:"hiddenLabels,omitempty" yaml:"hiddenLabels,omitempty"`
Telemetry *Telemetry `json:"telemetry,omitempty" yaml:"telemetry,omitempty"`
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The proposed JEP configuration is quite different: https://jumpstarter.dev/main/contributing/jeps/JEP-0013-observability-telemetry-logs.html#operator-configuration

At least the logging related should match.

Also, the JEP was missing endpoint config, but we should re-use the structs from the router/controller/etc. And make it auto-calculate the endpoint name as we do with the others.

enabled/disabled must be controlled by the telemetry.enabled flag.

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.

good point, the telemetry.logging.filter structure now matches the JEP.

Endpoint auto-derivation was implemented when the endpoint is empty and enabled: true, the controller derives jumpstarter-telemetry.<namespace>:9093 (the same pattern as the router and controller services). The Certificate field is reserved for Phase 2 TLS support and is now clearly documented as such.

And regarding enabled/disabled, GetServiceEndpoints only advertises the telemetry endpoint when telemetry.enabled: true.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (2)
python/packages/jumpstarter/jumpstarter/exporter/telemetry.py (1)

154-158: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Shutdown can block for _PUSH_TIMEOUT per queued batch.

_flush returns None on both success and failure, so close_async cannot tell them apart. If the telemetry service is unreachable, each iteration waits up to _PUSH_TIMEOUT (10 s) before dropping its batch. With a full queue of 10,000 entries and _BATCH_SIZE of 50, that is 200 iterations.

The loop also has no termination guarantee while the handler stays attached to the root logger, because emit can refill self._queue during the drain.

Make _flush report success and stop the drain after the first failure, or apply one total deadline.

🛠️ Proposed fix
-    async def _flush(self) -> None:
+    async def _flush(self) -> bool:
         """Drain up to _BATCH_SIZE entries from the queue and push to telemetry."""
         batch: list[telemetry_pb2.LogEntry] = []
         while self._queue and len(batch) < _BATCH_SIZE:
             batch.append(self._queue.popleft())
 
         if not batch:
-            return
+            return True
 
         metadata = [("authorization", f"Bearer {self._token}")] if self._token else []
         try:
             await self._stub.PushLogs(
                 telemetry_pb2.PushLogsRequest(entries=batch),
                 timeout=_PUSH_TIMEOUT,
                 metadata=metadata,
             )
         except Exception as exc:
             # Avoid recursive logging: write directly to stderr.
             print(f"[telemetry] PushLogs failed, {len(batch)} entries dropped: {exc}", file=sys.stderr)
+            return False
+        return True
 
     async def close_async(self) -> None:
         """Flush all remaining entries and close the handler."""
-        while self._queue:
-            await self._flush()
+        pending = len(self._queue)
+        while self._queue and pending > 0:
+            if not await self._flush():
+                break
+            pending -= _BATCH_SIZE
         self.close()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry.py` around lines
154 - 158, Update close_async to guarantee bounded shutdown by stopping the
drain after the first failed _flush, using a success/failure result from _flush
to distinguish outcomes. Preserve flushing queued batches while delivery
succeeds, then close the handler even when the queue remains or emit refills it.
controller/internal/service/telemetry_service.go (1)

185-187: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

The server is still plaintext while the controller advertises a CA certificate.

grpc.NewServer() is created with no grpc.Creds(...). ControllerService.GetServiceEndpoints returns TelemetryConfig.Certificate, and _setup_telemetry in python/packages/jumpstarter/jumpstarter/exporter/exporter.py (Lines 449-455) builds grpc.aio.secure_channel whenever ep.certificate is set. In that configuration the TLS handshake fails and no logs reach this service.

Either configure server credentials here from the same certificate and its key, or do not advertise a certificate for this endpoint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/service/telemetry_service.go` around lines 185 - 187, The
telemetry gRPC server created in the service startup path must match the
certificate advertised by ControllerService.GetServiceEndpoints. Configure
grpc.NewServer with TLS credentials built from the corresponding certificate and
private key before registering TelemetryServiceServer, or stop returning
TelemetryConfig.Certificate for this endpoint so clients use plaintext
consistently.
🧹 Nitpick comments (1)
controller/internal/service/telemetry_service_test.go (1)

85-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the real handler instead of a copy of its logic.

buildTelemetryEndpointsResponse reimplements the body of ControllerService.GetServiceEndpoints. The tests at Lines 103-154 assert on this copy, so they cannot detect a regression in the production handler. For example, if the "info" default is removed from GetServiceEndpoints, TestGetServiceEndpoints_DefaultsMinSeverityToInfo still passes.

Add a passing authenticator fake and call svc.GetServiceEndpoints directly, in the same way telemetrySvcWithConfig builds a failing one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/internal/service/telemetry_service_test.go` around lines 85 - 101,
The tests currently exercise the duplicate buildTelemetryEndpointsResponse
helper instead of production behavior. Remove that helper and update the tests
to construct a ControllerService through telemetrySvcWithConfig using a passing
authenticator fake, then call svc.GetServiceEndpoints directly; preserve the
existing assertions, including the default "info" severity case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/internal/service/telemetry_service.go`:
- Around line 75-103: In the telemetry request handler around the subject
parsing and entries loop, reject any token whose subject is not exactly exporter
form before processing entries, returning PermissionDenied; do not allow empty
claimedName or claimedNamespace to bypass ownership checks. Also avoid
partial-batch failure by counting mismatched entries in dropped and continuing,
while stamping or validating each accepted entry’s Exporter and Namespace
against the token claims.

In `@protocol/proto/jumpstarter/v1/telemetry.proto`:
- Around line 40-41: Update PushLogs to prevent extra_fields from overriding
sink-owned telemetry identity fields such as component, exporter, severity, and
namespace by rejecting reserved keys or prefixing accepted keys before emission.
Preserve trusted field values in all output paths, and add a test covering
duplicate reserved keys.

---

Duplicate comments:
In `@controller/internal/service/telemetry_service.go`:
- Around line 185-187: The telemetry gRPC server created in the service startup
path must match the certificate advertised by
ControllerService.GetServiceEndpoints. Configure grpc.NewServer with TLS
credentials built from the corresponding certificate and private key before
registering TelemetryServiceServer, or stop returning
TelemetryConfig.Certificate for this endpoint so clients use plaintext
consistently.

In `@python/packages/jumpstarter/jumpstarter/exporter/telemetry.py`:
- Around line 154-158: Update close_async to guarantee bounded shutdown by
stopping the drain after the first failed _flush, using a success/failure result
from _flush to distinguish outcomes. Preserve flushing queued batches while
delivery succeeds, then close the handler even when the queue remains or emit
refills it.

---

Nitpick comments:
In `@controller/internal/service/telemetry_service_test.go`:
- Around line 85-101: The tests currently exercise the duplicate
buildTelemetryEndpointsResponse helper instead of production behavior. Remove
that helper and update the tests to construct a ControllerService through
telemetrySvcWithConfig using a passing authenticator fake, then call
svc.GetServiceEndpoints directly; preserve the existing assertions, including
the default "info" severity case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e1cc0689-349c-498d-bbf4-a7e4d1fdcbfa

📥 Commits

Reviewing files that changed from the base of the PR and between 264e112 and f8cc9be.

⛔ Files ignored due to path filters (4)
  • controller/internal/protocol/jumpstarter/v1/jumpstarter.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/jumpstarter_grpc.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/telemetry.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/telemetry_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (25)
  • controller/cmd/main.go
  • controller/cmd/telemetry/main.go
  • controller/internal/config/config.go
  • controller/internal/config/types.go
  • controller/internal/oidc/op.go
  • controller/internal/service/controller_service.go
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go
  • protocol/proto/jumpstarter/v1/jumpstarter.proto
  • protocol/proto/jumpstarter/v1/telemetry.proto
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/__init__.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi
  • python/packages/jumpstarter/jumpstarter/config/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter_telemetry_test.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/client/v1/client_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/init.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py
  • protocol/proto/jumpstarter/v1/jumpstarter.proto
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter_telemetry_test.py

Comment thread controller/internal/service/telemetry_service.go Outdated
Comment thread protocol/proto/jumpstarter/v1/telemetry.proto

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/internal/service/telemetry_service.go`:
- Around line 57-67: The telemetry gRPC server started by Start does not
configure TLS despite advertising TelemetryConfig.Certificate to secure
exporters. Add certificate and private-key fields to TelemetryService, load and
validate them with tls.LoadX509KeyPair, construct TLS credentials, and pass
grpc.Creds(credentials.NewTLS(...)) to grpc.NewServer; preserve the existing
BindAddr and Signer behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a569cb00-de58-48cc-b054-19aef42cd9e9

📥 Commits

Reviewing files that changed from the base of the PR and between f8cc9be and ad9986d.

⛔ Files ignored due to path filters (1)
  • controller/internal/protocol/jumpstarter/v1/telemetry.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (16)
  • controller/cmd/main.go
  • controller/cmd/telemetry/main.go
  • controller/internal/config/config.go
  • controller/internal/config/types.go
  • controller/internal/oidc/op.go
  • controller/internal/service/controller_service.go
  • controller/internal/service/telemetry_service.go
  • controller/internal/service/telemetry_service_test.go
  • protocol/proto/jumpstarter/v1/telemetry.proto
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi
  • python/packages/jumpstarter/jumpstarter/config/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • protocol/proto/jumpstarter/v1/telemetry.proto
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi
  • controller/cmd/telemetry/main.go
  • controller/internal/oidc/op.go
  • controller/cmd/main.go
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry_test.py
  • controller/internal/service/controller_service.go
  • controller/internal/config/config.go
  • python/packages/jumpstarter/jumpstarter/config/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.pyi
  • python/packages/jumpstarter/jumpstarter/exporter/telemetry.py

Comment thread controller/internal/service/telemetry_service.go
@mangelajo

Copy link
Copy Markdown
Member

@bkhizgiy sorry, some conflicts, I am reviewing in the meanwhile.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Bella Khizgiyaev <bkhizgiy@redhat.com>

@mangelajo mangelajo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only comments for follow up, specially the TLS and config-passing to the jumpstarter-telemetry container/deployment.

Comment thread controller/cmd/main.go

authenticator, prefix, router, option, provisioning, leasePolicy,
hiddenLabels, deprecatedLabels, err := config.LoadConfiguration(
cfg, err := config.LoadConfiguration(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!, it keep being tangential to every PR touching this, but in the end, there it goes!

// When Enabled is true the controller advertises the telemetry endpoint to
// exporters and clients via GetServiceEndpoints so they can push logs without
// holding cluster credentials.
type Telemetry struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

oh, I confused this with the operator config. :-/

This is the internal config we pass to the jumpstarter-controller, but anyway I think it may be ok, the controller needs to know if Telemetry is enabled/disabled, where is the endpoint, what will be the certificate, so it can announce it on the "Get telemetry endpoint" call.
:-D

phew :) I thought my confusion made it bad, it looks good actually, thanks Bella!

Comment on lines +77 to +84
svc := &service.TelemetryService{
BindAddr: bindAddr,
Signer: signer,
}

errCh := make(chan error, 1)
go func() {
errCh <- svc.Start(ctx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was investigating how the main controller configured TLS and the endpoint name (not sure if it's needed here really.., may be for the gRPC server), the telemetry service should follow the same established pattern for consistency, but can be done in follow up:

The existing controller and router services receive their TLS certificates via Secret volume mounts (not inline ConfigMap strings), and their endpoint addresses via environment variables on the Deployment pod spec.

  1. TLS: The operator should create (or reference) a TLS Secret for the telemetry service (either via cert-manager Certificate CR or a user-provided spec.telemetry.tls.certSecret), mount it at /tls/, and set EXTERNAL_CERT_PEM=/tls/tls.crt and EXTERNAL_KEY_PEM=/tls/tls.key env vars — exactly like createControllerDeployment and createRouterDeployment do today. Then Start() should read those files and configure grpc.Creds(credentials.NewTLS(...)) on the server, falling back to a self-signed cert when the env vars are absent.
    (let's not do plaintext in any case, that can be dangerous since the wire always carries authentication tokens, now for exporters, but later will be clients as well pushing logs).

  2. Endpoint address: The telemetry endpoint should be passed to the controller pod as an env var (e.g. GRPC_TELEMETRY_ENDPOINT) rather than auto-derived from the namespace in the ConfigMap loader. The operator already knows the address because it creates the Service/Route/Ingress for it — same as it does for GRPC_ENDPOINT and GRPC_ROUTER_ENDPOINT.

(AI made, human touch afterwards :D )

@bkhizgiy

bkhizgiy commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@mangelajo Thanks for the review! I’ll merge this one and follow up with a separate PR for the TLS setup and passing the telemetry endpoint through the deployment config/env vars.

@bkhizgiy
bkhizgiy added this pull request to the merge queue Aug 5, 2026
Merged via the queue into jumpstarter-dev:main with commit ea2d51a Aug 5, 2026
29 checks passed
@bkhizgiy
bkhizgiy deleted the exporter-logs branch August 5, 2026 08:16
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.

3 participants