Files
ingress/mount.go
T
41ec6d6f1a rebrand(ingress): finish de-Traefik + own yaegi/grpc-web framework deps (#29) (#8)
* rebrand: strip "traefik" branding from the ingress controller

Removes all "traefik" branding/paths/env/comments from the shipping
surface while keeping load-bearing config schema and external deps intact.

Paths / binary:
- cmd/traefik/ -> cmd/ingress/ (traefik.go->ingress.go, etc.); merged out
  the empty cmd/ingress/generate.go stub. Build refs updated in Dockerfile,
  Makefile, .goreleaser.yml.tmpl, script/crossbinary-default.sh, README.
- contrib/systemd/traefik.service -> ingress.service (Hanzo Ingress unit,
  /etc/ingress paths, hanzo-ingress binary).
- contrib/grafana/traefik*.json -> ingress*.json; metric queries
  traefik_* -> ingress_* (now match the engine's ingress_ prefix) + titles.
- script/gcg/traefik-*.toml -> ingress-*.toml; Owner/Repo -> hanzoai/ingress.

Code/config branding (comments + non-schema strings):
- doc.go, mount.go, client/router.go, zap_backend.go, forward_backend.go,
  register.go: "Traefik" -> "the ingress"/"Hanzo Ingress"; corrected the
  annotation example to ingress.kubernetes.io/.
- ingress.sample.{toml,yml}: header + log + entryPoint example.
- pkg/config/dynamic/fixtures/sample.toml comment.
- pkg/redactor/testdata example+expected: /etc/traefik/traefik.toml ->
  /etc/ingress/ingress.toml, TraefikLogsFile -> IngressLogsFile (both files
  kept identical so the redactor golden test stays green).
- .golangci.yml importas aliases traefik* -> ingress* and the stale
  crd/traefikio path -> crd/hanzoai; unused-var exclude ingressVersion.
- .dockerignore / .gitignore: drop stale /traefik artifacts.
- script/code-gen.sh: PROJECT_MODULE -> github.com/hanzoai/ingress,
  v1alpha1 path -> crd/hanzoai, CRD output -> hanzo.ai_*.yaml /
  01-ingress-crd.yml.
- script/deploy.sh: drop the upstream traefik-library-image push.
- Removed dead, disabled upstream docs-publish workflow
  (.github/workflows/documentation.yaml, pushed to traefik/doc).
- LLM.md: rewrote rebrand notes; documented load-bearing "traefik" that
  stays (external imports, hash-bound realm, upstream issue URL, license
  attribution) and vendored upstream docs/webui/changelog as out of scope.

Load-bearing "traefik" intentionally KEPT (not branding):
- external Go modules github.com/traefik/{oxy,yaegi,grpc-web};
- digest_auth_test.go htdigest realm "traefik" (invalidates the hashes);
- consul_test.go upstream issue URL #8092 (provenance citation);
- LICENSE.md + boilerplate.go.tmpl Containous/Traefik Labs copyright
  (MIT attribution; Hanzo AI appended);
- integration/ test fixtures (traefik/whoami images, rawdata-*.json
  golden entrypoint names, nomad traefik.* tags) — test-only, semantics-
  sensitive; out of scope for this pass.

Verify: `go build $(go list ./... | grep -v /webui)` passes; redactor /
config/dynamic / nomad / server/service tests pass. The webui go:embed
failure and the kubernetes/crd test (ingress.k8s.io group mismatch) are
pre-existing on main, not caused by this rebrand.

* build(ingress): own yaegi + grpc-web via hanzoai source-fork replaces (#29)

Task #29 — own the framework deps (Traefik half). The Traefik engine is
already fully source-forked into github.com/hanzoai/ingress and the
user-facing surface is debranded (cmd/ingress -> hanzo-ingress, CRD group
hanzo.ai, ingress_ metrics, X-Ingress-* headers). The last upstream
traefik/* runtime libraries are now pinned to hanzoai source-forks:

  github.com/traefik/grpc-web => github.com/hanzoai/grpc-web v0.16.0
  github.com/traefik/yaegi    => github.com/hanzoai/yaegi    v0.16.1

Both forks keep the upstream module path at the identical version tag, so
the import lines in pkg/plugins (yaegi Go-interpreter for plugins) and
pkg/middlewares/grpcweb are unchanged — only resolution moves to a path
hanzoai controls (immune to upstream re-tag/deletion; patchable in-house).

oxy has no hanzoai fork yet and stays on the existing traefik/oxy replace —
tracked next-wave (create hanzoai/oxy first).

Proof:
  go list -m github.com/traefik/{yaegi,grpc-web}  -> both => hanzoai/*
  go build ./...                                  -> exit 0 (webui assets staged)
  go test ./pkg/plugins/...                       -> ok (yaegi consumer)
  readonly (CI-mode) build of swapped pkgs        -> exit 0 (go.sum complete)

* docs(ingress): document traefik/* framework-dep ownership boundary (#29)

---------

Co-authored-by: Antje Worring <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-03 03:34:30 -07:00

130 lines
3.8 KiB
Go

// Copyright © 2026 Hanzo AI. MIT License.
//go:build cloud
// +build cloud
// Package ingress exposes the HIP-0106 unified-binary mount surface for
// Hanzo Ingress.
//
// Architecturally, the full reverse-proxy / TLS / provider machinery
// lives in `cmd/ingress` and stays out-of-process at the cluster edge —
// nothing about that changes. What Mount provides is the small
// in-process slice the unified cloud binary needs: a healthz endpoint
// (so probes work even when the edge proxy is reloading) and a
// read-only /_/ingress/config view of which providers + entrypoints
// the deployment expects. This lets cloud-mounted subsystems verify
// that an ingress is configured for the brand without standing the
// full proxy up inside the cloud binary.
package ingress
import (
"fmt"
"net/http"
"os"
"strings"
"github.com/hanzoai/cloud"
"github.com/zap-proto/zip"
)
// Mount registers ingress on app per HIP-0106. Order 90 — runs after
// gateway (80) so gateway's identity middleware is already installed
// when our health endpoint serves.
//
// The mount is intentionally LIGHT. Hanzo Ingress is k8s-native: a
// separate `hanzo-ingress` binary runs at the cluster edge with
// providers (Kubernetes, file, docker, etc.) discovering routes. The
// unified cloud binary co-resident with Ingress benefits from the
// /_/ingress/healthz endpoint for probe coverage and the
// /_/ingress/config endpoint to confirm the deployment's expected
// entrypoint set — nothing else here.
func Mount(app *zip.App, deps cloud.Deps) error {
if app == nil {
return fmt.Errorf("ingress.Mount: nil zip.App")
}
logger := deps.Logger
if logger == nil {
return fmt.Errorf("ingress.Mount: nil deps.Logger")
}
cfg := configFromEnv(deps)
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": deps.Brand,
"domain": deps.Domain,
"entrypoints": cfg.Entrypoints,
"providers": cfg.Providers,
"acme_enabled": cfg.ACMEEnabled,
})
})
logger.Info("ingress mounted",
"brand", deps.Brand,
"domain", deps.Domain,
"entrypoints", strings.Join(cfg.Entrypoints, ","),
"providers", strings.Join(cfg.Providers, ","),
"acme_enabled", cfg.ACMEEnabled,
"json_variant", zip.JSONVariant,
)
return nil
}
// runtimeConfig is a tiny env-driven projection of the full Ingress
// static config. We do NOT import pkg/config/static into the cloud
// 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(deps cloud.Deps) runtimeConfig {
cfg := runtimeConfig{
Entrypoints: splitCSV(getenv("INGRESS_ENTRYPOINTS", "web,websecure")),
Providers: splitCSV(getenv("INGRESS_PROVIDERS", "kubernetes")),
ACMEEnabled: getenv("INGRESS_ACME_ENABLED", "true") == "true",
}
_ = deps
return cfg
}
func init() {
cloud.Register("ingress", 90, func(app any, deps cloud.Deps) error {
zapp, ok := app.(*zip.App)
if !ok {
return fmt.Errorf("ingress.Mount: expected *zip.App, got %T", app)
}
return Mount(zapp, deps)
})
}
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
}