Skip to content

feat: allow ingress to share a single port with pull_api (#183) - #184

Merged
7schmiede merged 2 commits into
mainfrom
feat/ingress-shared-listener
Jul 1, 2026
Merged

feat: allow ingress to share a single port with pull_api (#183)#184
7schmiede merged 2 commits into
mainfrom
feat/ingress-shared-listener

Conversation

@7schmiede

Copy link
Copy Markdown
Member

Summary

Lets the ingress listener share a single port with pull_api (and, transitively, admin_api) via path-prefix muxing — the same mechanism that already backs the pull_api == admin_api shared listener. Previously ingress.listen sharing an address with pull/admin was a hard compile error, forcing multi-port deployments even when a single published port would be simpler (reverse-proxy path-routing, single-published-port orchestrators).

When ingress.listen equals pull_api.listen (and/or admin_api.listen), the components are served on one net.Listener:

  • ingress serves its bare route paths (/webhooks/...) as the default handler;
  • pull/admin serve under their existing prefix values (/pull, /admin);
  • dispatch is by path prefix, ingress being the fallback.

Strictly opt-in (inferred from equal listen addresses); the multi-port topology remains the default and recommended posture. No new DSL keyword.

Related Issues

Closes #183

Scope

  • DSL / config behavior
  • Runtime behavior
  • MCP behavior
  • Documentation only

What changed

Config (internal/config/compile.go)

  • Removed the unconditional ingress.listen must not share … guard.
  • New validateSharedListeners generalizes shared-listener validation across ingress/pull/admin grouped by address. For every shared address it enforces:
    • co-listening API servers have non-empty, distinct, non-overlapping prefix values (reuses hasPathPrefix);
    • no ingress route path collides with (shadows or is shadowed by) a co-listening API prefix — the new check ingress introduces, since ingress paths are user-defined;
    • identical TLS across everything on the shared address.
  • pull_api.grpc_listen and observability.metrics.listen keep their existing dedicated-listener guards.
  • New Compiled.IngressShared flag; surfaced as ingress_shared in the MCP config_compile summary + runtime-control status; added to the restart-required reload set.

Runtime (internal/app/run.go)

  • HTTP components (ingress / pull / admin) are grouped by listen address; each group becomes one http.Server. Multi-member groups are muxed by a generalized prefixMux (replaces sharedPrefixMux) with ingress as the default handler. Per-component tracing/access-log wrapping preserved.

Validation

  • go test ./... passed locally (Go 1.26.4)
  • Added tests for behavioral changes
  • Updated docs for user-visible changes
  • Updated CHANGELOG.md
  • Updated BACKLOG.md / STATUS.md

Tests added:

  • internal/config: valid single-port config compiles and sets the flag; all-three-shared; ingress-route-path/prefix collision rejected; empty prefix rejected; TLS mismatch rejected. Repurposed the obsolete IngressMustNotShareListener test into IngressShareWithoutPrefixRejected.
  • internal/e2e: TestBinaryE2E_SinglePortIngressPull — POST to /webhooks/... and dequeue/ack via the Pull API on the same port.

Manual check against a real binary:

config validate (ingress :8080 + pull_api :8080 prefix /pull)  -> config ok            (exit 0)
config validate (ingress route /pull/... vs pull_api.prefix /pull)
  -> config invalid: shared listener: ingress route path "/pull/github"
     collides with pull_api.prefix "/pull" …                    (exit 1)

Security and Operations

  • No secrets or credentials committed
  • Auth/policy implications reviewed — Pull/Admin auth and TLS semantics unchanged; TLS must be identical across a shared address (enforced at compile time).
  • Backward compatibility considered — default topology and existing pull_api == admin_api shared listener behavior are unchanged; the feature only activates on explicitly equal listen addresses.

Notes for Reviewers

  • The runtime server construction was refactored from the branchy SharedListener/else into address-based grouping. It reproduces the previous topology for the default and pull_api == admin_api cases (verified by the existing e2e/unit suites) and adds the ingress-sharing cases.
  • Toggling the shared-listener mode remains restart-required (documented; also enforced via IngressShared in the reload gate).

🤖 Generated with Claude Code

7schmiede and others added 2 commits July 1, 2026 14:46
ingress.listen may now equal pull_api.listen (and, transitively,
admin_api.listen) so inbound webhooks and the Pull/Admin APIs can be
served on one prefix-muxed listener, mirroring the existing
pull_api==admin_api shared listener. Ingress serves its bare route paths
as the default handler; the API servers serve under their prefixes.
Strictly opt-in via matching listen addresses -- the multi-port topology
remains the default and recommended posture.

- config: generalized shared-listener validation (validateSharedListeners)
  requiring non-empty/distinct/non-overlapping API prefixes, rejecting any
  ingress route path that collides with a co-listening API prefix, and
  enforcing identical TLS across a shared address; pull_api.grpc_listen and
  observability.metrics.listen stay dedicated. New IngressShared compiled
  flag (restart-required on toggle), surfaced as ingress_shared in the MCP
  config summaries.
- runtime: group HTTP components by listen address and mux shared groups
  via a generalized prefixMux (replaces sharedPrefixMux).
- tests: 5 config validation cases + single-port e2e round-trip
  (TestBinaryE2E_SinglePortIngressPull).
- docs: configuration.md, DESIGN.md, CHANGELOG.md, BACKLOG.md, STATUS.md.

Motivation: single-port deployments behind reverse-proxy path-routing and
single-published-port orchestrators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CONTRIBUTING: development setup now says Go 1.26.x to match go.mod
  (go 1.26.4); the 1.25.x note was stale after the 2.8.1 toolchain bump.
- CHANGELOG: promote the ingress single-port shared-listener entry from
  [Unreleased] to [2.9.0] - 2026-07-01.
- STATUS: current release -> v2.9.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@7schmiede
7schmiede merged commit 95062e3 into main Jul 1, 2026
14 of 15 checks passed
@7schmiede
7schmiede deleted the feat/ingress-shared-listener branch July 1, 2026 15:19
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.

Allow ingress to share a single port with pull_api (single-port deployments)

1 participant