mirror of
https://github.com/hanzoai/gateway.git
synced 2026-10-30 15:04:44 +00:00
* feat(cloud-mount): port gateway to HIP-0106 Mount(*zip.App, cloud.Deps)
Add pkg/gateway/mount.go (build tag: cloud) exposing the canonical
HIP-0106 contract so the unified hanzoai/cloud binary can co-resident
the gateway trust boundary without standing a second JWT validator up
in-process.
The mount does three things:
1. Installs the existing NewAuthMiddleware as a zip middleware via a
gin -> net/http -> zip adaptor. Same strip-list, same JWKS cache,
same X-* mint list as the standalone cmd/gateway binary — one
trust boundary, one implementation.
2. Registers a native zip /_/gateway/healthz endpoint independent of
the auth chain so probes work even with a misconfigured JWKS.
3. Best-effort loads the routes table from GATEWAY_ROUTES_FILE; a
missing file is non-fatal in cloud-mode (co-resident subsystems
own their own routes), unlike the standalone path which fails
closed.
Build tag isolation: the standalone gateway binary (KrakenD/Lura,
vendor mode, ghcr.io/hanzoai/gateway) builds with `-mod=vendor` and
must NOT pull hanzoai/cloud + hanzoai/zip into its vendor tree. The
`//go:build cloud` constraint on mount.go + mount_test.go keeps the
default build path clean. Build the cloud variant with `-tags=cloud`.
Wiring:
- init() registers ("gateway", order=80) in cloud.Registry — runs
before commerce(100) so identity headers are minted upstream of
every subsystem.
- go.mod gains the hanzoai/cloud + hanzoai/zip requires (gated by
tag at the import sites), pinned via local ../ replace.
Tests:
- TestMount_RegistersHealthAndRegistry boots a fresh zip.App, mounts
gateway with AUTH_ENABLED=false, asserts /_/gateway/healthz answers.
- cmd/gateway still builds clean with `-mod=vendor` (the standalone
KrakenD path).
Schema: schema/gateway.zap describes Health service for zapc codegen.
* refactor: collapse pkg/gateway/ → root for clean import path
Subsystem code lives in package gateway at repo root. Importers now use
`github.com/hanzoai/gateway` (no /pkg/gateway/ nesting). Standalone shim moved
to cmd/gateway/ where applicable.
Per Hanzo Go-stdlib pattern: repo IS the package.
* refactor: flatten import path — github.com/hanzoai/cloud (drop /pkg/cloud)
82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
|
|
//go:build cloud
|
|
// +build cloud
|
|
|
|
package gateway
|
|
|
|
import (
|
|
"io"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/cloud"
|
|
luxlog "github.com/luxfi/log"
|
|
|
|
"github.com/hanzoai/zip"
|
|
)
|
|
|
|
// TestMount_RegistersHealthAndRegistry asserts the gateway subsystem
|
|
// (1) registers itself in cloud.Registry via init(), (2) mounts a native
|
|
// zip /_/gateway/healthz endpoint that answers 200 + JSON, and (3) does
|
|
// not refuse to start when no GATEWAY_ROUTES_FILE is configured —
|
|
// co-resident subsystems own their own routes in cloud-mode.
|
|
func TestMount_RegistersHealthAndRegistry(t *testing.T) {
|
|
// Force AUTH_ENABLED off in the test: validating JWTs against a real
|
|
// JWKS URL would require network. The strip-list is exercised by
|
|
// auth_middleware_security_test.go at the gateway root.
|
|
t.Setenv("AUTH_ENABLED", "false")
|
|
// Ensure no routes file is referenced.
|
|
_ = os.Unsetenv("GATEWAY_ROUTES_FILE")
|
|
|
|
if !registryContains("gateway") {
|
|
t.Fatalf("cloud.Registry missing 'gateway'; Names=%v", registryNames())
|
|
}
|
|
|
|
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
|
deps := cloud.Deps{
|
|
Logger: luxlog.New("test"),
|
|
Brand: "hanzo",
|
|
Domain: "api.hanzo.ai",
|
|
DataDir: t.TempDir(),
|
|
}
|
|
if err := Mount(app, deps); err != nil {
|
|
t.Fatalf("Mount: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/_/gateway/healthz", nil)
|
|
resp, err := app.Fiber().Test(req)
|
|
if err != nil {
|
|
t.Fatalf("Fiber Test: %v", err)
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
t.Fatalf("status: got %d want 200", resp.StatusCode)
|
|
}
|
|
raw, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatalf("read body: %v", err)
|
|
}
|
|
if !strings.Contains(string(raw), `"service":"gateway"`) {
|
|
t.Fatalf("body: got %q, want service=gateway", string(raw))
|
|
}
|
|
}
|
|
|
|
func registryContains(name string) bool {
|
|
for _, s := range cloud.Registry {
|
|
if s.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func registryNames() []string {
|
|
out := make([]string, 0, len(cloud.Registry))
|
|
for _, s := range cloud.Registry {
|
|
out = append(out, s.Name)
|
|
}
|
|
return out
|
|
}
|