Files
harmony/fleet/harmony-fleet-agent/Dockerfile
Jean-Gabriel Gill-Couture d013246a68 feat(fleet): request/reply commands over NATS — wire types, agent server, operator client, e2e harness
First slice of the device-commands.* protocol from
fleet/requests_over_nats.md. Lands `Verb::Ping` plus the harness that
proves it works against a real in-cluster agent.

Wire types (`harmony-reconciler-contracts::commands`):
- `Verb::Ping`, `CommandRequest`, `PingReply`, `ErrorReply`/`ErrorKind`
- `device_command_subject` / `device_command_subscription` helpers
- `X-Harmony-*` header constants

Agent:
- `command_server.rs` subscribes on `device-commands.<id>.>` and
  dispatches verbs; ping handler replies with `PingReply`
- New `[agent].runtime_enabled` config flag (default true). When
  false, podman init + reconciler loop are skipped so the agent can
  run as a Pod on containerd-only k3d nodes; command server +
  heartbeat still run
- `Dockerfile`: canonical multi-stage build for production registries

Operator:
- `commands::FleetCommandsClient` with typed `CommandError`
  (`DeviceOffline` via `no_responders`, `Timeout`, `BadReply`, `Nats`)

E2E harness (`harmony-fleet-e2e`):
- Library crate + integration test. `Stack::bring_up` provisions a
  fresh `e2e-<uuid8>` namespace in a shared `fleet-e2e` k3d cluster,
  deploys NATS (UserPass auth, JetStream on) + the agent Pod, returns
  a connected admin NATS client, and tears the namespace down on Drop
- v1 ships `AuthMode::UserPass` only; the `Callout` variant is
  reserved on the public API for the follow-up PR that adds the mock
  OIDC fixture + NatsAuthCalloutScore deployment
- Operator pod deployment is also follow-up — for ping the test
  process drives `FleetCommandsClient` directly against the cluster's
  NATS NodePort
- `HARMONY_FLEET_E2E=1` gates the integration test so default
  `cargo test --workspace` runs don't depend on k3d/podman
- Image build + sideload mirrors the `fleet_auth_callout` pattern:
  host `cargo build --release` → single-stage Dockerfile → `podman
  build` → `k3d image import`. ~12s warm bring-up, ~80s cold
2026-05-18 09:47:36 -04:00

50 lines
1.9 KiB
Docker

# Multi-stage container build for harmony-fleet-agent.
#
# Build context is the workspace root (the agent's Cargo.toml has
# `path = "../../harmony"` deps that only resolve when the whole
# workspace is in scope). Invoke from the repo root:
#
# docker build -f fleet/harmony-fleet-agent/Dockerfile \
# -t hub.nationtech.io/harmony/harmony-fleet-agent:<tag> .
#
# Both stages are pinned to bookworm for a matched glibc — the
# rust:slim image follows Debian's latest stable, and a binary built
# against trixie's glibc 2.40 fails to start on a bookworm runtime
# (`GLIBC_2.39 not found`). This is the same lesson the operator
# Dockerfile encodes; keep the two pinned to the same Debian release.
#
# The e2e harness uses a faster host-build + single-stage path
# (`fleet/harmony-fleet-e2e/src/images.rs`); this Dockerfile is the
# canonical recipe for production registries.
FROM docker.io/rust:1.94-slim-bookworm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config \
ca-certificates \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN cargo build --release --locked -p harmony-fleet-agent
FROM docker.io/library/debian:bookworm-slim
# ca-certificates: outbound TLS to NATS over wss:// when the agent is
# configured against a TLS-terminated NATS endpoint. kube-rs is not
# used at runtime on the agent; async-nats uses rustls.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/harmony-fleet-agent /usr/local/bin/harmony-fleet-agent
# Non-root runtime. 65532 is the `nonroot` UID convention from
# distroless. Pairs with `securityContext.runAsNonRoot: true` in
# whatever Pod spec the harness or production helm chart applies.
USER 65532:65532
ENTRYPOINT ["/usr/local/bin/harmony-fleet-agent"]