mirror of
https://github.com/hanzoai/ingress.git
synced 2026-10-26 02:47:15 +00:00
Mount took a cloud.Deps in order to read two fields out of it, Brand and Domain, and handed both straight back on /_/ingress/config. Nothing else in that struct was ever touched, and carrying it cost the whole dependency: hanzoai/cloud ships two editions under one module path, so cloud.Deps is a different type in each and a package that names it can be composed by exactly one of them. This repo has already been on both sides of that. Days ago its go.mod pinned cloud v1.801.293, where Deps has no Logger field and there is no package-level Register at all; today it pins v0.1.1, where both exist. One file, two truths, depending on which edition happened to be pinned. So ingress takes the two strings now and cloud is out of go.mod. The other half of the coupling was direction. An init() pushed ingress into cloud.Registry, so the host had to be imported for the subsystem to announce itself, and the graph closed on itself. App builds and returns its own *zip.App instead: a host holds what it gets back and mounts it where it likes, and ingress no longer knows a registry exists. The logger came out of the same struct and is the process default now, which cannot be nil and so needs no check. None of this was ever compiled. It sat behind a `cloud` build tag that no make target, no CI job and no Dockerfile passed, which is how a file gets to be valid against one edition of its dependency and dead against the other without anyone noticing. The tag protected nothing either -- cmd/ingress does not import this package, and `go list -deps ./cmd/ingress` still shows no zip -- so it is gone, and `.` joins test-unit and the hanzo.yml test list. A surface that nothing builds is how this rotted the first time.
108 lines
3.5 KiB
Go
108 lines
3.5 KiB
Go
// Copyright © 2026 Hanzo AI. MIT License.
|
|
|
|
// Package ingress is the in-process slice of Hanzo Ingress.
|
|
//
|
|
// The reverse-proxy / TLS / provider machinery lives in cmd/ingress and stays
|
|
// out-of-process at the cluster edge — nothing about that changes here. What
|
|
// this package builds is the small surface a co-resident binary needs: a health
|
|
// endpoint that answers while the edge proxy reloads, and a read-only
|
|
// /_/ingress/config view of the entrypoints and providers the deployment
|
|
// expects. That is enough to confirm an ingress is configured for the brand
|
|
// without standing the whole proxy up in the same process.
|
|
package ingress
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
luxlog "github.com/luxfi/log"
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
// App builds ingress's in-process surface and returns it, for a host to
|
|
// compose.
|
|
//
|
|
// Brand and domain are the only two facts ingress needs from whoever runs it,
|
|
// and both are read straight back out on /_/ingress/config, so they arrive as
|
|
// two strings. They used to arrive as a cloud.Deps — the host's own dependency
|
|
// struct, carried across the boundary to reach two fields. That made ingress
|
|
// import its host, which costs more than the import line: hanzoai/cloud ships
|
|
// two editions that both declare the module path github.com/hanzoai/cloud, so
|
|
// cloud.Deps names a different type in each, and a package that takes one can
|
|
// be composed by exactly one of them.
|
|
//
|
|
// Returning the app rather than writing into one handed in turns the last arrow
|
|
// around too. Ingress no longer registers itself in a registry the host owns —
|
|
// the host holds what it gets back and mounts it wherever it likes.
|
|
func App(brand, domain string) (*zip.App, error) {
|
|
app := zip.New(zip.Config{AppName: "ingress"})
|
|
cfg := configFromEnv()
|
|
|
|
app.Get("/_/ingress/healthz", func(c *zip.Ctx) error {
|
|
return c.JSON(http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"service": "ingress",
|
|
})
|
|
})
|
|
|
|
app.Get("/_/ingress/config", func(c *zip.Ctx) error {
|
|
// Read-only view: brand, domain, expected entrypoints. No
|
|
// provider credentials, no TLS material, no upstream URLs.
|
|
return c.JSON(http.StatusOK, map[string]any{
|
|
"brand": brand,
|
|
"domain": domain,
|
|
"entrypoints": cfg.Entrypoints,
|
|
"providers": cfg.Providers,
|
|
"acme_enabled": cfg.ACMEEnabled,
|
|
})
|
|
})
|
|
|
|
luxlog.Default().Info("ingress mounted",
|
|
"brand", brand,
|
|
"domain", domain,
|
|
"entrypoints", strings.Join(cfg.Entrypoints, ","),
|
|
"providers", strings.Join(cfg.Providers, ","),
|
|
"acme_enabled", cfg.ACMEEnabled,
|
|
"json_variant", zip.JSONVariant,
|
|
)
|
|
return app, nil
|
|
}
|
|
|
|
// runtimeConfig is a tiny env-driven projection of the full Ingress
|
|
// static config. We do NOT import pkg/config/static into this build
|
|
// path — that pulls in the entire provider tree (Kubernetes, Docker,
|
|
// AWS, Consul, …). The full config still lives in cmd/ingress/ingress.go
|
|
// for the standalone binary.
|
|
type runtimeConfig struct {
|
|
Entrypoints []string
|
|
Providers []string
|
|
ACMEEnabled bool
|
|
}
|
|
|
|
func configFromEnv() runtimeConfig {
|
|
return runtimeConfig{
|
|
Entrypoints: splitCSV(getenv("INGRESS_ENTRYPOINTS", "web,websecure")),
|
|
Providers: splitCSV(getenv("INGRESS_PROVIDERS", "kubernetes")),
|
|
ACMEEnabled: getenv("INGRESS_ACME_ENABLED", "true") == "true",
|
|
}
|
|
}
|
|
|
|
func getenv(key, dflt string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return dflt
|
|
}
|
|
|
|
func splitCSV(s string) []string {
|
|
parts := strings.Split(s, ",")
|
|
out := parts[:0]
|
|
for _, p := range parts {
|
|
if t := strings.TrimSpace(p); t != "" {
|
|
out = append(out, t)
|
|
}
|
|
}
|
|
return out
|
|
}
|