github netresearch/ofelia v0.25.1

latest releases: v1.0.1, v1.0.0, v0.30.0...
5 months ago

Highlights

A patch release that closes the docker hijack regression class that crept in alongside v0.25.0's TLS/scheme rework, plus a webhook multi-instance dedup bug and the long-standing url-only-webhook documentation gap. Anyone running run_exec jobs through tecnativa/docker-socket-proxy or any plain-TCP/HTTP Docker host should upgrade. Anyone wiring more than one webhook per job (typically one for success, one for error) needs the multi-webhook fix. Anyone who tried to configure a webhook with only a url = ... since v0.16.0 will find that it actually works now.

🐳 Docker daemon connectivity

  • DOCKER_HOST=tcp://... (plain HTTP) run_exec jobs work again. v0.25.0 regressed plain-tcp:// hijack APIs (ContainerExecAttach, ContainerAttach, ContainerLogs --follow) with the unhelpful error tls: first record does not look like a TLS handshake. Container discovery kept working because the regular HTTP path was unaffected; only run_exec and friends failed. Root cause was a Go stdlib × Docker SDK interaction: net/http lazily auto-configures HTTP/2 on the first request and allocates TLSClientConfig in place for ALPN — and the SDK's hijack dialer reads that as "TLS required" and dials tls.Dial against your plaintext daemon. The fix suppresses the lazy auto-config on non-TLS transports. TLS Docker hosts (https://, tcp+tls://) are unaffected — ALPN h2 negotiation still works there. (#681, fixes #668)
  • DOCKER_HOST=http://... hijack now works for the same APIs. Same SDK dialer, different failure mode: net.Dial("http", addr) is rejected by Go's net package ("http" isn't a valid network name) so the call surfaced as dial http: unknown network http. The fix installs an explicit TCP DialContext so the SDK picks our dialer instead of reaching the broken fallback. Path-bearing hosts (http://daemon:port/v1.43) and IPv6 literals (http://[::1]:2375) round-trip cleanly via url.Parse, matching how the SDK already handles tcp://. (#686, fixes #682)

🔁 Webhooks

  • Url-only webhooks finally work out of the box. The docs/webhooks.md "Custom Webhooks" section has been promising since v0.16.0 that you could declare a webhook with just url = ... and have Ofelia POST a JSON payload to it — but the code path actually returned preset specification cannot be empty and refused to attach. v0.25.1 ships a new bundled json-post preset and a [global] webhook-default-preset selector that ships with json-post as the default fallback. A webhook with only url = ... (or Docker label ofelia.webhook.<name>.url: ...) now attaches and fires, no custom preset YAML required.
    • Audit-and-pin upgrade impact: a stale URL-only config that previously sat inert will now actually POST to whatever's in url. Pin webhook-allowed-hosts to a specific allow-list if you want to restrict egress; set webhook-default-preset = (empty) to keep pre-upgrade behavior and require every webhook to declare preset explicitly. Three-state semantics: nil = "operator did not set" (use bundled fallback), non-nil empty = "explicit opt-out", non-nil non-empty = "operator's chosen fallback name". (#677, fixes #676)
  • Jobs that reference more than one webhook now fire every listed webhook. The middleware container deduplicated middlewares by Go type, so handing it two *Webhook instances (e.g. one with trigger: success, one with trigger: error) kept only the first and silently dropped the rest — the error webhook attached to nothing and never ran. The same dedup also shadowed scheduler-level webhooks against per-job webhooks during propagation, so any job declaring webhooks: silently lost global notifications too. The fix attaches a single per-job composite carrying the union of [global] webhook-webhooks and the job's own webhooks selector, deduplicated by name. The previously-silent failure path (unknown webhook name, preset-load failure, missing required variable) now emits an slog.Error keyed by job name and webhook list so misconfigurations show up in the log. (#671, fixes #670)
  • Webhook retries no longer hold the daemon hostage at shutdown. (*Webhook).sendWithRetry's inter-attempt backoff used a bare time.Sleep that ignored ctx cancellation, so SIGTERM mid-retry pinned a goroutine for up to retry-delay × retry-count. The in-flight HTTP request had the same shape — its context was derived from context.Background(), so even a hung POST blocked shutdown for the full timeout. Both halves now drain on scheduler cancellation. Worst-case shutdown contribution from a single webhook drops from retry-delay × retry-count + timeout (multi-minute on aggressive retry budgets) to roughly one network round-trip on cancel. Callers see an error chain wrapping context.Canceled / context.DeadlineExceeded rather than the previous "all N attempts failed" message after the full timeout elapsed. (#685, fixes #673)

📖 Docs

  • New docs/TROUBLESHOOTING.md sections for both hijack failure modes (tls: first record does not look like a TLS handshake on tcp://, dial http: unknown network http on http://), slotted alongside the existing #605 socket-proxy section so operators scanning by symptom land on the right entry.
  • docs/webhooks.md retry-config table now documents the shutdown drain semantics — retry-count × retry-delay + timeout bounds the worst-case daemon-shutdown contribution from any single webhook, and the retry loop observes scheduler cancellation since this release.
  • Webhook lifecycle flowchart added to docs/webhooks.md for the trigger / dedup / retry / shutdown story. (#680)

Dependencies

No direct dependency bumps in this release. The Go toolchain and module graph stay at v0.25.0's pinned versions.

Thanks

Special thanks to the external reporters whose issues drove the bugfixes in this release:

  • @groknt for #668 — reporting that run_exec jobs failed with the cryptic TLS handshake error on plain-tcp:// socket-proxy setups, and following up with the exact diagnostic dump (env, debug logs, container labels, full compose file) that let us reproduce the bug in a self-contained Go program and isolate it to the Go std-lib × Docker SDK interaction. The reproducer also surfaced the sibling http:// failure mode that ships in this release as #682 / #686.
  • @techsolo12 for #670 — reporting that error-trigger webhooks were silently being dropped from jobs that also declared a success-trigger webhook. The repro pointed straight at the type-keyed dedup in middlewareContainer.Use, which had been a latent footgun waiting for the first N-instance middleware to land.

Container image

ghcr.io/netresearch/ofelia:0.25.1
ghcr.io/netresearch/ofelia:0.25
ghcr.io/netresearch/ofelia:0
ghcr.io/netresearch/ofelia:latest

Verification

All release assets (binaries, SBOMs, checksums) are cosign-signed with keyless OIDC and include SLSA Level 3 build provenance. Verify with:

cosign verify-blob \
  --bundle ofelia-linux-amd64.bundle \
  --certificate-identity-regexp 'https://github\.com/netresearch/(\.github|ofelia)' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ofelia-linux-amd64

Full Changelog: v0.25.0...v0.25.1

Don't miss a new ofelia release

NewReleases is sending notifications on new releases.