Skip to content

v0.5.22

Choose a tag to compare

@github-actions github-actions released this 05 May 11:15
· 575 commits to main since this release

What's new in v0.5.22

v0.5.22 ships a first-class CLI helper for the daemon ack API introduced in v0.5.20. Operators on call can now acknowledge findings from the terminal without typing curl by hand: perf-sentinel ack create --signature <SIG> --reason <TEXT> [--expires 7d], perf-sentinel ack revoke --signature <SIG>, perf-sentinel ack list [--output text|json]. The release is purely a UX layer on top of the existing v0.5.20 endpoints (POST / DELETE /api/findings/{signature}/ack and GET /api/acks) and the v0.5.21 Prometheus counters that track operator-driven activity. The daemon HTTP surface is byte-for-byte unchanged.

The CLI resolves the daemon API key in priority order: PERF_SENTINEL_DAEMON_API_KEY env var, then --api-key-file <path> (with O_NOFOLLOW and an opt-in stderr warning when the file is group/world readable), then a no-echo rpassword prompt if a 401 is received and stdin is a TTY. There is no --api-key <SECRET> flag, by design: passing secrets on the command line leaks them via the process list and shell history. The --expires flag accepts both ISO8601 datetimes (2026-05-11T00:00:00Z) and relative durations parsed by humantime (7d, 24h, 30m). Exit codes follow Unix conventions: 0 on success, 1 on generic errors (network, parse, file IO), 2 on HTTP 4xx (already acknowledged, unauthorized, invalid signature, not found on revoke), 3 on HTTP 5xx (ack store disabled, write failure, store full).

The --daemon <URL> validator hardens against four classes of typo-or-paste mistakes that would otherwise bubble as opaque transport errors: empty authority (http://), port without host (http://:8080), userinfo embedded in the URL (http://alice@host, redirected to --api-key-file / env), path component (https://api.example.com/v1/, the CLI builds /api/... itself), and query string (http://host?debug=1). Each rejection produces a one-line stderr error with an actionable hint. Inputs containing ASCII control characters (\n, \r mid-string) inside the API key file are rejected at read time, before the value reaches hyper's HeaderValue::from_str, so operators get a message naming the file rather than a generic build error.

A new fetch_with_body helper in crates/sentinel-core/src/http_client.rs serves the POST/DELETE traffic with a configurable timeout, the existing 8 MiB response-body cap, the X-API-Key header flagged sensitive (so hyper redacts it from Debug and HPACK), and a (StatusCode, Bytes) return shape so callers can discriminate non-2xx without ? short-circuit. The corresponding HttpClientWithBody type alias and build_client_with_body constructor are factored through a single private build_client_inner<B>() generic so the rustls TLS configuration cannot drift between the GET and POST clients.

Helm chart 0.2.25 ships in lockstep, bumping the default daemon image tag to ghcr.io/robintra/perf-sentinel:0.5.22. No chart-level config change.

Added

  • New perf-sentinel ack CLI subcommand with three subactions:
    • ack create --signature <SIG> --reason <TEXT> [--expires <ISO8601_OR_DURATION>] [--by <NAME>] [--api-key-file <PATH>] posts to /api/findings/{sig}/ack and prints a colored summary (signature, by, reason, expires with a relative hint like "in 7 days").
    • ack revoke --signature <SIG> [--api-key-file <PATH>] deletes via /api/findings/{sig}/ack and prints a confirmation line.
    • ack list [--output text|json] [--api-key-file <PATH>] enumerates active daemon acks via /api/acks. Text format renders an aligned table with a footer that quotes the daemon's 1000-entry cap and points users at .perf-sentinel-acknowledgments.toml for the CI TOML side. JSON format is a pretty-printed array of the raw AckEntry payload.
  • Daemon URL resolution via --daemon <URL> flag, PERF_SENTINEL_DAEMON_URL env var, or default http://localhost:4318 (matches perf-sentinel watch).
  • Auth resolution via PERF_SENTINEL_DAEMON_API_KEY env var, --api-key-file <path> (trailing newline stripped, embedded ASCII control characters rejected), or interactive rpassword prompt on 401 when stdin is a TTY. No --api-key <SECRET> flag.
  • Duration parsing on --expires accepts ISO8601 datetimes (2026-05-11T00:00:00Z) or relative durations parsed by humantime (7d, 24h, 30m). Omit for a permanent ack.
  • Conventional Unix exit codes: 0 success, 1 generic error (network, parse, file IO), 2 client error (HTTP 4xx), 3 server error (HTTP 5xx). Errors land on stderr with a one-line cause and an actionable hint when applicable (e.g., on 409 the hint points at ack revoke, on 401 it lists the env var and --api-key-file).
  • fetch_with_body helper in crates/sentinel-core/src/http_client.rs for POST/DELETE requests with X-API-Key (flagged sensitive), 10s timeout, 8 MiB body cap. Returns (hyper::StatusCode, bytes::Bytes) so callers discriminate non-2xx without short-circuit.
  • HttpClientWithBody type alias and build_client_with_body constructor in the same module, factored through a private build_client_inner<B>() generic so the rustls TLS configuration is shared with the existing HttpClient GET path.
  • call_with_tty_retry helper in the new ack module: builds the client once per CLI invocation and reuses it across the 401-prompt-retry path, so the second TLS init the previous draft would have paid is gone.
  • Hardened --daemon URL validator that rejects empty authority, port without host, userinfo, path components, and query strings before the first request goes out. Each rejection has its own actionable error message.
  • O_NOFOLLOW on --api-key-file (Unix only): the CLI refuses to follow symlinks at the path target, mirroring the daemon's crates/sentinel-core/src/daemon/ack.rs posture for the JSONL store. When the file mode is group/world readable (mode & 0o077 != 0), the CLI emits a one-line stderr warning suggesting chmod 600. The warning is gated behind stderr.is_terminal() so CI pipelines don't see it on every invocation.
  • MAX_ACKS_RESPONSE exposed pub in crates/sentinel-core/src/daemon/query_api.rs and re-imported in the CLI under the same name, so the ack list footer ("showing up to 1000") cannot drift from the daemon-side cap.
  • docs/CLI.md (English) and docs/FR/CLI-FR.md (French): new reference page covering the ack subcommand exhaustively (synopsis, three subactions, auth resolution, daemon URL resolution, duration parsing, exit codes). Other subcommands are stubbed with a pointer to --help, to be filled in incrementally.
  • docs/ACK-WORKFLOW.md (English) and docs/FR/ACK-WORKFLOW-FR.md (French): new workflow page that covers both ack mechanisms (CI TOML in .perf-sentinel-acknowledgments.toml from v0.5.17, daemon JSONL via the API from v0.5.20) plus a "Choosing between TOML and daemon" decision table and a note on the TOML-wins-on-conflict precedence.
  • 18 integration tests in crates/sentinel-cli/tests/cli_ack.rs covering create/revoke/list across success, conflict, unauthorized (with and without env API key), service unavailable, bad request, insufficient storage, signature from stdin, ISO8601 vs relative --expires, network error, invalid URL, missing key file. The tests spawn a hand-rolled HTTP/1.1 mock server on 127.0.0.1:0 to match the crates/sentinel-core/src/test_helpers.rs convention of avoiding wiremock / httpmock.
  • 30+ unit tests in crates/sentinel-cli/src/ack.rs for the URL validator, API-key file reader (including symlink refusal, group/world-readable warning, embedded-control-char rejection), expires parser, table renderer (empty list, multi-row alignment), exit-code mapping, and the resolve_by $USER fallback.
  • Five unit tests in crates/sentinel-core/src/http_client.rs for fetch_with_body: 201 success path, 409 surfaced without erroring, X-API-Key header attached, Content-Type: application/json attached, request body propagated.

Changed

  • build_client and build_client_with_body share a private generic constructor in crates/sentinel-core/src/http_client.rs. Same TLS configuration on both, no risk of rustls feature drift between the GET and POST/DELETE paths.
  • Helm chart 0.2.24 to 0.2.25, appVersion 0.5.21 to 0.5.22, default daemon image tag points at ghcr.io/robintra/perf-sentinel:0.5.22. The artifacthub.io/images annotation is updated in lockstep.

Behavior

  • No HTTP-shape change. The three ack endpoints (POST / DELETE /api/findings/{signature}/ack and GET /api/acks) plus the rest of the daemon API surface keep their v0.5.21 status codes and JSON shapes byte-for-byte.
  • No new daemon dependency. The CLI is the only crate gaining humantime, rpassword, and the explicit hyper / bytes dev-deps.
  • Operator activity stays observable. Every ack create and ack revoke invocation increments the v0.5.21 perf_sentinel_ack_operations_total{action} counter on the daemon's /metrics. The smoke run for this release verifies that 3 success-path action=ack calls plus 1 success-path action=unack call plus 1 reason=already_acked and 1 reason=not_acked failure match the expected /metrics deltas.
  • X-API-Key value is flagged sensitive on the wire. hyper redacts sensitive header values from Debug output and HPACK tables, mirroring the AuthHeader::set_sensitive(true) pattern used by fetch_get for Authorization headers.
  • --api-key-file does not follow symlinks on Unix. A symlinked path returns an open error before the file is read, so a path-flip race after the user types the flag cannot redirect the read to a different target.
  • No --api-key <SECRET> flag by design. Passing secrets on the command line leaks them via the process list (ps, /proc/<pid>/cmdline) and shell history (~/.bash_history, ~/.zsh_history). The three resolution paths (env var, file, interactive prompt) all avoid those leak vectors.

Documentation

  • New docs/CLI.md with a reference section for the ack subcommand and a stub for the other subcommands. The TTY-only nature of the --api-key-file permission warning is documented explicitly so operators in CI / Docker / systemd contexts know the runtime check is suppressed and they should set the file mode declaratively (Kubernetes Secret with defaultMode: 0o400, etc.).
  • New docs/ACK-WORKFLOW.md covering both ack mechanisms (TOML + JSONL), the TOML-wins-on-conflict precedence, the decision table for picking the right mechanism, and a pointer to the v0.5.21 Prometheus counters for observability.
  • French mirrors at docs/FR/CLI-FR.md and docs/FR/ACK-WORKFLOW-FR.md, same content with French accents and prose conventions.

Install

Prebuilt binaries (Linux amd64 / arm64, macOS arm64, Windows amd64):

curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.22/perf-sentinel-linux-amd64
chmod +x perf-sentinel-linux-amd64
sudo mv perf-sentinel-linux-amd64 /usr/local/bin/perf-sentinel

Linux binaries are statically linked against musl and run on any distribution (Alpine, Debian, RHEL, Ubuntu any version) regardless of glibc version, and inside FROM scratch images.

From crates.io:

cargo install perf-sentinel --version 0.5.22

Docker:

docker run --rm -p 4317:4317 -p 4318:4318 \
  ghcr.io/robintra/perf-sentinel:0.5.22 watch --listen-address 0.0.0.0

Helm chart 0.2.25 ships alongside, see the matching chart-v0.2.25 release for the chart-side details.

Full Changelog: v0.5.21...v0.5.22