Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18749656a2 |
+9
-9
@@ -1,7 +1,7 @@
|
||||
// Package apps is the composition root: the single, explicit list of which
|
||||
// Hanzo cloud subsystems are linked into the binary AND the order they mount in.
|
||||
//
|
||||
// Wire() returns []cloud.MountSpec in mount order (slice position == order). There
|
||||
// Wire() returns []cloud.AppSpec in mount order (slice position == order). There
|
||||
// is no init()-registry and no order-int: adding, removing, or reordering a
|
||||
// subsystem is a one-line edit to Wire(), read top-to-bottom. cmd/cloud and
|
||||
// cmd/hanzo both call Wire() and thread the slice into cloud.Serve — the set is
|
||||
@@ -196,13 +196,13 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
// Wire returns every linked subsystem as a cloud.MountSpec, in mount order. The
|
||||
// Wire returns every linked subsystem as a cloud.AppSpec, in mount order. The
|
||||
// slice position IS the order: cloud.MountAll iterates it as-given, registering each
|
||||
// subsystem's teardown as a zip shutdown hook so teardown runs in reverse (LIFO).
|
||||
// Enablement is a separate axis: cloud.Serve mounts only the specs cfg.Enabled(name)
|
||||
// admits, so a STAGED subsystem is linked but inert until named.
|
||||
func Wire() []cloud.MountSpec {
|
||||
return []cloud.MountSpec{
|
||||
func Wire() []cloud.AppSpec {
|
||||
return []cloud.AppSpec{
|
||||
// embedded NATS :4222 + JetStream.
|
||||
{Name: "pubsub", Mount: pubsub.Mount, Shutdown: pubsub.Shutdown},
|
||||
// embedded Kafka adaptor :9092.
|
||||
@@ -254,11 +254,11 @@ func Wire() []cloud.MountSpec {
|
||||
// otel-collector, prometheus and gonum are here and nowhere else — and it is
|
||||
// imported by NOTHING but this line, so unlinking it is a pure subtraction.
|
||||
//
|
||||
// KNOWN GAP, see the branch report: o11y also owns /v1/sentry/* (mountSentry),
|
||||
// which is a SECOND public prefix. zip.Load takes one, so /v1/sentry/* is not
|
||||
// mounted on the host by this line and 404s until zip.Plugin can name more than
|
||||
// one prefix. Do not merge this to main before that is closed.
|
||||
cloud.PluginSpec("o11y", "/v1/o11y", o11yPlugin()),
|
||||
// o11y owns TWO public prefixes — /v1/o11y and /v1/sentry/* (mountSentry,
|
||||
// the Sentry-protocol ingest). Both are named here: a prefix left out
|
||||
// would 404 silently rather than fail, which for Sentry ingest means
|
||||
// quietly dropping every error event in the fleet.
|
||||
cloud.PluginSpec("o11y", o11yPlugin(), "/v1/o11y", "/v1/sentry"),
|
||||
{Name: "authz", Mount: cloud.Global(authz.Mount), Global: true},
|
||||
// Embedded commerce plane /v1/commerce/*, /_/commerce/* — the hanzoai/commerce
|
||||
// MODULE via the adapter in commerce.go (un-forked; the in-process
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
)
|
||||
|
||||
// wire_seams.go wires cross-subsystem in-process seams that cannot be a MountSpec
|
||||
// wire_seams.go wires cross-subsystem in-process seams that cannot be a AppSpec
|
||||
// because they compose functions ACROSS packages that must not import each other.
|
||||
//
|
||||
// The coding orchestrator (clients/coding) needs git's CloneURL + VerifyRef, but
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ var frozen = []struct {
|
||||
name string
|
||||
ownsHealth bool
|
||||
hasShutdown bool
|
||||
global bool // receives the bare *zip.App — see MountSpec.Global
|
||||
global bool // receives the bare *zip.App — see AppSpec.Global
|
||||
}{
|
||||
{"pubsub", false, true, false}, // was order 5
|
||||
{"kafka", false, true, false}, // was order 6
|
||||
|
||||
@@ -963,7 +963,7 @@ func pickVaultClient(cfg *Config, log luxlog.Logger) VaultClient {
|
||||
// signature, so Wire references each one directly and the compiler checks it.
|
||||
//
|
||||
// app is a Router, not the concrete *zip.App, and that is the whole safety
|
||||
// property: middleware a subsystem installs lands on the subtrees its MountSpec
|
||||
// property: middleware a subsystem installs lands on the subtrees its AppSpec
|
||||
// declares, never over the binary. Routes register exactly as before — absolute
|
||||
// paths, same precedence. See scope.go. A subsystem that genuinely gates
|
||||
// everything says so with Global: true and gets the bare app.
|
||||
@@ -975,11 +975,11 @@ type MountFunc func(app Router, deps Deps) error
|
||||
// deadline so a slow teardown is cut off rather than hanging SIGTERM.
|
||||
type ShutdownFunc func(ctx context.Context) error
|
||||
|
||||
// MountSpec describes one subsystem to mount. There is NO Order field: the slice
|
||||
// AppSpec describes one subsystem to mount. There is NO Order field: the slice
|
||||
// position in apps.Wire() IS the mount order — the composition root lists
|
||||
// subsystems in the exact sequence they mount (and, reversed, tear down), so order
|
||||
// is data read top-to-bottom in one file, not ints scattered across the tree.
|
||||
type MountSpec struct {
|
||||
type AppSpec struct {
|
||||
Name string
|
||||
Mount MountFunc
|
||||
Shutdown ShutdownFunc // optional; nil means the subsystem has nothing to tear down.
|
||||
@@ -1020,7 +1020,7 @@ type MountSpec struct {
|
||||
// its dependents is torn down after them) with no subsystem torn down while a
|
||||
// request still uses it. Only ENABLED specs mount, so only they register a hook;
|
||||
// teardown needs no separate enablement gate.
|
||||
func MountAll(app *zip.App, specs []MountSpec, cfg *Config, deps Deps) error {
|
||||
func MountAll(app *zip.App, specs []AppSpec, cfg *Config, deps Deps) error {
|
||||
logger := deps.Logger
|
||||
for _, spec := range specs {
|
||||
if !cfg.Enabled(spec.Name) {
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestMountAll_ShutdownHooksLIFOAfterDrain(t *testing.T) {
|
||||
}
|
||||
|
||||
// Mount order a, b, c ⇒ LIFO teardown must be c, b, a.
|
||||
specs := []cloud.MountSpec{
|
||||
specs := []cloud.AppSpec{
|
||||
{Name: "a", Mount: noopMount, Shutdown: record("a")},
|
||||
{Name: "b", Mount: noopMount, Shutdown: record("b")},
|
||||
{Name: "c", Mount: noopMount, Shutdown: record("c")},
|
||||
@@ -183,7 +183,7 @@ func TestMountAll_ShutdownRegistration_EnablementAndNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
specs := []cloud.MountSpec{
|
||||
specs := []cloud.AppSpec{
|
||||
{Name: "enabled", Mount: noopMount, Shutdown: record("enabled")},
|
||||
{Name: "disabled", Mount: noopMount, Shutdown: record("disabled")},
|
||||
{Name: "nilsd", Mount: noopMount}, // enabled, but no Shutdown
|
||||
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
// drive the SAME single implementation. The subsystem is a stateless orchestrator over
|
||||
// framework (which holds the state) + the AI/social edges — it opens no store of its own.
|
||||
//
|
||||
// Registration is a one-line cloud.MountSpec in apps.Wire() (after framework +
|
||||
// Registration is a one-line cloud.AppSpec in apps.Wire() (after framework +
|
||||
// knowledge, before the AI /v1/* catch-all); the module fixtures + lifecycle hooks are
|
||||
// registered in doctypes.go's init(), process-global and mount-order-independent.
|
||||
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ import (
|
||||
|
||||
// Prefixes are the canonical absolute prefixes the IAM identity surface owns —
|
||||
// the ONE list. It registers the real routes (safeMount), serves the fail-closed 503
|
||||
// when IAM cannot boot, and is the MountSpec.Prefixes apps.Wire() hands MountAll, so
|
||||
// when IAM cannot boot, and is the AppSpec.Prefixes apps.Wire() hands MountAll, so
|
||||
// IAM's middleware can only ever land on identity's own subtrees. Everything outside
|
||||
// them belongs to cloud, so the console catch-all keeps serving the SPA.
|
||||
//
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package kms_test
|
||||
|
||||
// Integration tests for the embedded KMS subsystem, exercised through the REAL
|
||||
// orchestrator path (BuildDeps → the init()-registered MountSpec → the zip/Fiber
|
||||
// orchestrator path (BuildDeps → the init()-registered AppSpec → the zip/Fiber
|
||||
// stack), mirroring cmd/cloud/main_test.go. Requests run in-process via
|
||||
// app.Fiber().Test — no listener, no external KMS, no PostgreSQL.
|
||||
//
|
||||
@@ -49,8 +49,8 @@ func masterKeyB64(t *testing.T) string {
|
||||
// mountSpecs is the kms subsystem's composition-root entry, built locally so these
|
||||
// tests mount exactly kms (the same spec apps.Wire() carries) without linking
|
||||
// the whole bundle. cfg.Enable still gates it, exactly as in production.
|
||||
func mountSpecs() []cloud.MountSpec {
|
||||
return []cloud.MountSpec{{Name: "kms", Mount: kms.Mount, OwnsHealth: true}}
|
||||
func mountSpecs() []cloud.AppSpec {
|
||||
return []cloud.AppSpec{{Name: "kms", Mount: kms.Mount, OwnsHealth: true}}
|
||||
}
|
||||
|
||||
// newApp wires BuildDeps + the canonical middleware + MountAll for the kms
|
||||
|
||||
@@ -36,7 +36,7 @@ func newDualApp(t *testing.T, mk string) *zip.App {
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(middleware.RequestID())
|
||||
app.Use(middleware.Logger(deps.Logger))
|
||||
specs := []cloud.MountSpec{
|
||||
specs := []cloud.AppSpec{
|
||||
{Name: "kms", Mount: kms.Mount, OwnsHealth: true},
|
||||
{Name: "admin", Mount: admin.Mount},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package storage_test
|
||||
|
||||
// Integration tests for the /v1/s3 file-manager subsystem, driven through the
|
||||
// REAL orchestrator path (BuildDeps → the init()-registered MountSpec → the
|
||||
// REAL orchestrator path (BuildDeps → the init()-registered AppSpec → the
|
||||
// zip/Fiber stack), exactly like clients/kms/kms_test.go. Requests run in-process
|
||||
// via app.Fiber().Test — no listener, no live SeaweedFS.
|
||||
//
|
||||
@@ -67,7 +67,7 @@ func newApp(t *testing.T, creds bool) *zip.App {
|
||||
deps := cloud.BuildDeps(cfg)
|
||||
app := zip.New(zip.Config{Logger: deps.Logger})
|
||||
app.Use(middleware.Recover())
|
||||
specs := []cloud.MountSpec{
|
||||
specs := []cloud.AppSpec{
|
||||
{Name: "storage", Mount: storage.Mount, OwnsHealth: true},
|
||||
{Name: "provisioning", Mount: provisioning.Mount},
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func Mount(app cloud.Router, deps cloud.Deps) error {
|
||||
return searchKeyed(c)
|
||||
})
|
||||
|
||||
scrape := zip.AdaptNetHTTPFunc(scrapeHandler)
|
||||
scrape := zip.AdaptNetHTTP(http.HandlerFunc(scrapeHandler))
|
||||
// Firecrawl builds {apiUrl}/{version}/scrape; pin firecrawlVersion:v1 so the
|
||||
// client POSTs /v1/websearch/v1/scrape. Also accept the bare /scrape.
|
||||
g.Post("/v1/scrape", scrape)
|
||||
|
||||
@@ -75,7 +75,7 @@ func main() {
|
||||
}
|
||||
|
||||
// wireNames parses apps.go and returns the {Name: "..."} string literals from
|
||||
// Wire()'s returned []MountSpec composite literal — the app names, in mount
|
||||
// Wire()'s returned []AppSpec composite literal — the app names, in mount
|
||||
// order. It is a light parse (no type-check), so it stays fast and depends only
|
||||
// on the literal shape, which TestWireOrderMatchesFrozen already pins.
|
||||
func wireNames(path string) []string {
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@
|
||||
// the fused cloud control plane.
|
||||
//
|
||||
// Design — one mechanism, not many. The subsystem set is the explicit list
|
||||
// apps.Wire() returns — []cloud.MountSpec in mount order (kms first-tier,
|
||||
// apps.Wire() returns — []cloud.AppSpec in mount order (kms first-tier,
|
||||
// iam 50, commerce 100, …, ai last), no init()-registry. A subcommand is just a
|
||||
// *selection* over that slice:
|
||||
//
|
||||
@@ -142,7 +142,7 @@ func main() {
|
||||
// isServeTarget reports whether sub names something this binary serves in-process —
|
||||
// the full fused surface (cloud), standalone IAM, the datastore doc target, or any
|
||||
// registered subsystem. Everything else is delegated to the Rust CLI (passthrough).
|
||||
func isServeTarget(sub string, specs []cloud.MountSpec) bool {
|
||||
func isServeTarget(sub string, specs []cloud.AppSpec) bool {
|
||||
if _, ok := nonRegistrySubcommands[sub]; ok {
|
||||
return true
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func isServeTarget(sub string, specs []cloud.MountSpec) bool {
|
||||
}
|
||||
|
||||
// dispatch routes a subcommand to its serve entrypoint.
|
||||
func dispatch(sub string, specs []cloud.MountSpec) error {
|
||||
func dispatch(sub string, specs []cloud.AppSpec) error {
|
||||
switch sub {
|
||||
case "cloud":
|
||||
// Full fused surface: --enable governs the set (empty = all).
|
||||
@@ -193,7 +193,7 @@ func dispatch(sub string, specs []cloud.MountSpec) error {
|
||||
}
|
||||
|
||||
// registryHas reports whether name is a registered subsystem.
|
||||
func registryHas(specs []cloud.MountSpec, name string) bool {
|
||||
func registryHas(specs []cloud.AppSpec, name string) bool {
|
||||
for _, spec := range specs {
|
||||
if spec.Name == name {
|
||||
return true
|
||||
@@ -204,7 +204,7 @@ func registryHas(specs []cloud.MountSpec, name string) bool {
|
||||
|
||||
// usage prints the subcommand list: the non-registry targets (cloud, iam,
|
||||
// datastore) plus every subsystem in the composition root (Wire()), sorted.
|
||||
func usage(w *os.File, specs []cloud.MountSpec) {
|
||||
func usage(w *os.File, specs []cloud.AppSpec) {
|
||||
fmt.Fprintf(w, "hanzo %s — the unified Hanzo Go binary\n\n", version)
|
||||
fmt.Fprintf(w, "Usage:\n hanzo <command> [flags]\n\n")
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func newCloudApp(t *testing.T) (*zip.App, string, cloud.Deps) {
|
||||
app.Use(middleware.Recover())
|
||||
app.Use(middleware.RequestID())
|
||||
app.Use(middleware.Logger(deps.Logger))
|
||||
if err := cloud.MountAll(app, []cloud.MountSpec{{Name: "kms", Mount: kms.Mount, OwnsHealth: true}}, cfg, deps); err != nil {
|
||||
if err := cloud.MountAll(app, []cloud.AppSpec{{Name: "kms", Mount: kms.Mount, OwnsHealth: true}}, cfg, deps); err != nil {
|
||||
t.Fatalf("MountAll: %v", err)
|
||||
}
|
||||
return app, dir, deps
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/hanzoai/cloud
|
||||
|
||||
go 1.26.4
|
||||
go 1.26.5
|
||||
|
||||
// Dependencies will be added as subsystems are mounted per HIP-0106.
|
||||
|
||||
@@ -43,7 +43,7 @@ require (
|
||||
github.com/zap-proto/fiber/v3 v3.2.1
|
||||
github.com/zap-proto/go v1.3.0
|
||||
github.com/zap-proto/md v0.1.0
|
||||
github.com/zap-proto/zip v1.10.0
|
||||
github.com/zap-proto/zip v1.16.0
|
||||
go.opentelemetry.io/collector/component v1.54.0
|
||||
go.opentelemetry.io/collector/confmap v1.54.0
|
||||
go.opentelemetry.io/collector/confmap/provider/envprovider v1.50.0
|
||||
@@ -104,7 +104,7 @@ require (
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/vultr/govultr/v3 v3.30.0 // indirect
|
||||
github.com/zap-proto/http v0.3.0 // indirect
|
||||
github.com/zap-proto/http v0.3.1 // indirect
|
||||
github.com/zap-proto/zap2pb v0.2.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
|
||||
@@ -2103,12 +2103,16 @@ github.com/zap-proto/go v1.3.0 h1:S3rMoawwhH/BbSZ4G8zG05hJoQnMSMDPzIq75diCTqE=
|
||||
github.com/zap-proto/go v1.3.0/go.mod h1:914SNGTH6Rv3Yu1MweWJBPEN8FZlo5C39QyhaB0C7Q0=
|
||||
github.com/zap-proto/http v0.3.0 h1:l7DvlngiYqmzNY6fzyRYw2ZIAhF35FqwOe6mvAOqpMg=
|
||||
github.com/zap-proto/http v0.3.0/go.mod h1:UYfGhDDCetgxs65XSev8Lpf65COg5vKQK+cWwZGh4zQ=
|
||||
github.com/zap-proto/http v0.3.1 h1:A2rCPWYCX866eAsdiWuns0dvWnBmViZtGm4pwX7jwlY=
|
||||
github.com/zap-proto/http v0.3.1/go.mod h1:UYfGhDDCetgxs65XSev8Lpf65COg5vKQK+cWwZGh4zQ=
|
||||
github.com/zap-proto/md v0.1.0 h1:1R6w/i1FYAdGIIiOvNggKO0RjikzhWWRodQUOgzEEpc=
|
||||
github.com/zap-proto/md v0.1.0/go.mod h1:pmMx2F4Dwj1H48PIuLzRZxB2R5qVvqeg8ZScKQIntyQ=
|
||||
github.com/zap-proto/zap2pb v0.2.0 h1:sos6HnayhGMGLRO54px1InzimDzTZ2o5TSMEatYBjzs=
|
||||
github.com/zap-proto/zap2pb v0.2.0/go.mod h1:wD97Z2VTPabDq/4AMNL++PWnQ0YwEtajiuNkLGg3/18=
|
||||
github.com/zap-proto/zip v1.10.0 h1:0Swzr+SNr+4VeO8pUw+Umdkh3z/lnD0hJ4N1kObdP60=
|
||||
github.com/zap-proto/zip v1.10.0/go.mod h1:9R3FOq2ItZa7G+9QilsB/punEpVSHtdXiQji0P84LSE=
|
||||
github.com/zap-proto/zip v1.16.0 h1:Bb3StSa9xMHzWUOVDxeMUZFTrgRsBGyrm1tCJMvKRxw=
|
||||
github.com/zap-proto/zip v1.16.0/go.mod h1:BxFNqjnAVhArMJ+s7VnXdwAtfOVy47GAi62wwqyu8go=
|
||||
github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A=
|
||||
github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
|
||||
+14
-6
@@ -8,36 +8,44 @@ import (
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// PluginSpec returns a MountSpec that serves prefix from a SEPARATE binary
|
||||
// PluginSpec returns a AppSpec that serves prefix from a SEPARATE binary
|
||||
// instead of code linked into this one.
|
||||
//
|
||||
// It exists so that where a subsystem runs stops being a property of the source.
|
||||
// zip.Load returns a zip.Service — the same type a linked-in service is — so the
|
||||
// only difference between "compiled in" and "its own process" is which MountSpec
|
||||
// only difference between "compiled in" and "its own process" is which AppSpec
|
||||
// Wire() lists. Moving one out is a one-line edit at the composition root, and
|
||||
// nothing downstream (routing, health, shutdown ordering) can tell the difference.
|
||||
//
|
||||
// Pass EVERY prefix the subsystem owns. o11y owns both /v1/o11y and /v1/sentry,
|
||||
// and a prefix left out is not an error — it is a silent 404 on that subtree,
|
||||
// which is the worst way for this to fail.
|
||||
//
|
||||
// The plugin names exactly one of Addr (already listening), Bin (the binary,
|
||||
// normally go:embed'd) or Path. For Bin and Path, zip starts it as a child on a
|
||||
// normally go:embed'd), Path, or URL+Sum (a release artifact, fetched and
|
||||
// verified by digest). For Bin and Path, zip starts it as a child on a
|
||||
// private unix socket and mounts the routes onto it; the child is stopped when
|
||||
// Shutdown runs, so a plugin subsystem tears down with the rest.
|
||||
//
|
||||
// Global is set because zip.Load registers under the prefix it was given. Handing
|
||||
// it a scoped Router would nest that prefix under the subsystem name and the
|
||||
// routes would answer somewhere nobody is asking.
|
||||
func PluginSpec(name, prefix string, p zip.Plugin) MountSpec {
|
||||
func PluginSpec(name string, p zip.Plugin, prefixes ...string) AppSpec {
|
||||
if p.Name == "" {
|
||||
p.Name = name
|
||||
}
|
||||
return MountSpec{
|
||||
return AppSpec{
|
||||
Name: name,
|
||||
Global: true,
|
||||
// Deps are irrelevant to a plugin — it runs in its own process and
|
||||
// receives nothing from this one — so this is zip.Load's Service with the
|
||||
// router narrowed to the app it needs.
|
||||
Mount: func(router Router, _ Deps) error {
|
||||
app, ok := router.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("pluginspec %q: needs the root app, got %T — Global must stay set", name, router)
|
||||
}
|
||||
return zip.Load(prefix, p)(app)
|
||||
return zip.Load(p, prefixes...)(app)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
// A plugin subsystem must look like every other one at the composition root:
|
||||
// same MountSpec type, so Wire() can swap in-process for out-of-process by
|
||||
// same AppSpec type, so Wire() can swap in-process for out-of-process by
|
||||
// editing one line.
|
||||
func TestPluginSpec_IsAnOrdinaryMountSpec(t *testing.T) {
|
||||
s := PluginSpec("search", "/v1/search", zip.Plugin{Addr: "127.0.0.1:1"})
|
||||
func TestPluginSpec_IsAnOrdinaryAppSpec(t *testing.T) {
|
||||
s := PluginSpec("search", zip.Plugin{Addr: "127.0.0.1:1"}, "/v1/search")
|
||||
if s.Name != "search" {
|
||||
t.Fatalf("name = %q, want search", s.Name)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func TestPluginSpec_IsAnOrdinaryMountSpec(t *testing.T) {
|
||||
// Mounting onto a scoped Router is a wiring mistake, not something to paper
|
||||
// over: the routes would answer under a doubled prefix. Fail loudly.
|
||||
func TestPluginSpec_RefusesAScopedRouter(t *testing.T) {
|
||||
s := PluginSpec("bad", "/v1/bad", zip.Plugin{Addr: "127.0.0.1:1"})
|
||||
s := PluginSpec("bad", zip.Plugin{Addr: "127.0.0.1:1"}, "/v1/bad")
|
||||
err := s.Mount(scopedStub{}, Deps{})
|
||||
if err == nil {
|
||||
t.Fatal("mounting on a non-root Router must fail")
|
||||
|
||||
@@ -128,7 +128,7 @@ func (s *scope) err() error {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"%s installed middleware at %s, outside the prefixes it owns (%s) — declare those prefixes in its MountSpec, or Global: true if it really gates the whole binary",
|
||||
"%s installed middleware at %s, outside the prefixes it owns (%s) — declare those prefixes in its AppSpec, or Global: true if it really gates the whole binary",
|
||||
s.name, strings.Join(*s.escaped, ", "), strings.Join(s.prefixes, ", "))
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func Global(fn func(*zip.App, Deps) error) MountFunc {
|
||||
return func(r Router, deps Deps) error {
|
||||
app, ok := r.(*zip.App)
|
||||
if !ok {
|
||||
return fmt.Errorf("this subsystem takes the bare *zip.App; its MountSpec needs Global: true")
|
||||
return fmt.Errorf("this subsystem takes the bare *zip.App; its AppSpec needs Global: true")
|
||||
}
|
||||
return fn(app, deps)
|
||||
}
|
||||
|
||||
+7
-7
@@ -36,7 +36,7 @@ func get(t *testing.T, app *zip.App, path string) int {
|
||||
return resp.StatusCode
|
||||
}
|
||||
|
||||
func mountAll(t *testing.T, app *zip.App, specs []cloud.MountSpec) error {
|
||||
func mountAll(t *testing.T, app *zip.App, specs []cloud.AppSpec) error {
|
||||
t.Helper()
|
||||
enable := make([]string, 0, len(specs))
|
||||
for _, s := range specs {
|
||||
@@ -59,7 +59,7 @@ func newApp() *zip.App {
|
||||
// the /v1/<name> convention every subsystem already follows.
|
||||
func TestScopeConfinesUseToTheSubsystem(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "guard", Mount: func(r cloud.Router, _ cloud.Deps) error {
|
||||
r.Use(deny)
|
||||
r.Get("/v1/guard/whoami", pong)
|
||||
@@ -87,7 +87,7 @@ func TestScopeConfinesUseToTheSubsystem(t *testing.T) {
|
||||
// it owns two subtrees and neither of them is /v1/iam alone.
|
||||
func TestScopeHonoursDeclaredPrefixes(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "identity", Prefixes: []string{"/v1/identity", "/login/oauth"},
|
||||
Mount: func(r cloud.Router, _ cloud.Deps) error {
|
||||
r.Use(deny)
|
||||
@@ -120,7 +120,7 @@ func TestScopeHonoursDeclaredPrefixes(t *testing.T) {
|
||||
// even in the failed attempt.
|
||||
func TestScopeRefusesMiddlewareOutsideItsPrefixes(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "neighbour", Mount: func(r cloud.Router, _ cloud.Deps) error {
|
||||
r.Get("/v1/neighbour/ping", pong)
|
||||
return nil
|
||||
@@ -142,7 +142,7 @@ func TestScopeRefusesMiddlewareOutsideItsPrefixes(t *testing.T) {
|
||||
// rate-limiting a leaf of its OWN subtree is the normal case and must pass.
|
||||
func TestScopeAllowsGroupInsideItsPrefixes(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "vault", Mount: func(r cloud.Router, _ cloud.Deps) error {
|
||||
r.Group("/v1/vault/auth", deny)
|
||||
r.Get("/v1/vault/auth/login", pong)
|
||||
@@ -166,7 +166,7 @@ func TestScopeAllowsGroupInsideItsPrefixes(t *testing.T) {
|
||||
// it has always meant. That is the capability, and it is spelled out in Wire().
|
||||
func TestGlobalIsTheOnlyAppWideDoor(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "edge", Global: true, Mount: cloud.Global(func(a *zip.App, _ cloud.Deps) error {
|
||||
a.Use(deny)
|
||||
return nil
|
||||
@@ -189,7 +189,7 @@ func TestGlobalIsTheOnlyAppWideDoor(t *testing.T) {
|
||||
// fails the mount instead of silently receiving a scope it cannot use.
|
||||
func TestGlobalMountNeedsTheGlobalFlag(t *testing.T) {
|
||||
app := newApp()
|
||||
err := mountAll(t, app, []cloud.MountSpec{
|
||||
err := mountAll(t, app, []cloud.AppSpec{
|
||||
{Name: "edge", Mount: cloud.Global(func(*zip.App, cloud.Deps) error { return nil })},
|
||||
})
|
||||
if err == nil {
|
||||
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
// every enabled subsystem) before MountAll, runs the canonical middleware
|
||||
// pipeline (Recover → RequestID → Logger), and shuts down gracefully on
|
||||
// SIGINT/SIGTERM.
|
||||
func Serve(specs []MountSpec, enable []string) error {
|
||||
func Serve(specs []AppSpec, enable []string) error {
|
||||
cfg := LoadConfig()
|
||||
if enable != nil {
|
||||
cfg.Enable = enable
|
||||
|
||||
Reference in New Issue
Block a user