mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Failing after 11m25s
Hanzo CI/CD / test-amd64 (push) Failing after 1h6m41s
Hanzo CI/CD / cicd (push) Skipped
CI/CD / cicd (push) Failing after 1h6m44s
hanzo.yml stops publishing the o11y-site Sites project: o11y.hanzo.ai, obs.hanzo.ai and dashboards.hanzo.ai redirect to platform.hanzo.ai/o11y. Sign-out goes to IAM's end-session endpoint instead of a forward-auth's /__guard/logout, and the "local" authz comment states the boundary as it is: only cloud reaches a standalone runtime, with headers it minted from a validated IAM bearer. Co-authored-by: Hanzo Dev <[email protected]>
199 lines
9.9 KiB
Go
199 lines
9.9 KiB
Go
// Package community constructs the ONE Hanzo o11y (O11y-community) runtime and
|
|
// HTTP server used by BOTH the standalone `cmd/community` binary AND the unified
|
|
// hanzoai/cloud binary's in-process embed (via app.Server.PublicHandler).
|
|
//
|
|
// Keeping the whole construction — config resolution, the O11y provider set,
|
|
// and the app server — behind a single exported builder guarantees the two
|
|
// deployments run byte-identical middleware, identity (pkg/identn/iamidentn,
|
|
// i.e. Hanzo IAM gateway-header auth), authz (iamauthz), telemetry stores, rule
|
|
// manager, dashboards and alerts. One construction, one way: the embed cannot
|
|
// drift from the standalone pod's auth or wiring, because they are the same code.
|
|
package community
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"github.com/hanzoai/o11y/pkg/alertmanager"
|
|
"github.com/hanzoai/o11y/pkg/analytics"
|
|
"github.com/hanzoai/o11y/pkg/auditor"
|
|
"github.com/hanzoai/o11y/pkg/authz"
|
|
"github.com/hanzoai/o11y/pkg/authz/iamauthz"
|
|
"github.com/hanzoai/o11y/pkg/authz/localauthz"
|
|
"github.com/hanzoai/o11y/pkg/cache"
|
|
"github.com/hanzoai/o11y/pkg/config"
|
|
"github.com/hanzoai/o11y/pkg/config/envprovider"
|
|
"github.com/hanzoai/o11y/pkg/config/fileprovider"
|
|
"github.com/hanzoai/o11y/pkg/factory"
|
|
"github.com/hanzoai/o11y/pkg/flagger"
|
|
"github.com/hanzoai/o11y/pkg/gateway"
|
|
"github.com/hanzoai/o11y/pkg/gateway/noopgateway"
|
|
"github.com/hanzoai/o11y/pkg/global"
|
|
"github.com/hanzoai/o11y/pkg/licensing"
|
|
"github.com/hanzoai/o11y/pkg/licensing/nooplicensing"
|
|
"github.com/hanzoai/o11y/pkg/meterreporter"
|
|
"github.com/hanzoai/o11y/pkg/modules/cloudintegration"
|
|
"github.com/hanzoai/o11y/pkg/modules/cloudintegration/implcloudintegration"
|
|
"github.com/hanzoai/o11y/pkg/modules/dashboard"
|
|
"github.com/hanzoai/o11y/pkg/modules/dashboard/impldashboard"
|
|
"github.com/hanzoai/o11y/pkg/modules/metricreductionrule"
|
|
"github.com/hanzoai/o11y/pkg/modules/metricreductionrule/implmetricreductionrule"
|
|
"github.com/hanzoai/o11y/pkg/modules/organization"
|
|
"github.com/hanzoai/o11y/pkg/modules/retention"
|
|
"github.com/hanzoai/o11y/pkg/modules/rulestatehistory"
|
|
"github.com/hanzoai/o11y/pkg/modules/serviceaccount"
|
|
"github.com/hanzoai/o11y/pkg/modules/tag"
|
|
"github.com/hanzoai/o11y/pkg/o11y"
|
|
"github.com/hanzoai/o11y/pkg/prometheus"
|
|
"github.com/hanzoai/o11y/pkg/querier"
|
|
"github.com/hanzoai/o11y/pkg/query-service/app"
|
|
"github.com/hanzoai/o11y/pkg/queryparser"
|
|
"github.com/hanzoai/o11y/pkg/ruler"
|
|
"github.com/hanzoai/o11y/pkg/ruler/o11yruler"
|
|
"github.com/hanzoai/o11y/pkg/sqlschema"
|
|
"github.com/hanzoai/o11y/pkg/sqlstore"
|
|
"github.com/hanzoai/o11y/pkg/telemetrystore"
|
|
"github.com/hanzoai/o11y/pkg/types/telemetrytypes"
|
|
"github.com/hanzoai/o11y/pkg/zeus"
|
|
"github.com/hanzoai/o11y/pkg/zeus/noopzeus"
|
|
)
|
|
|
|
// NewConfig resolves the O11y config from the given YAML files (if any) plus
|
|
// the process environment (env:), applying the Hanzo operator-facing aliases
|
|
// (e.g. the flat O11Y_DATASTORE_DSN → telemetrystore.datastore.dsn) inside
|
|
// o11y.NewConfig. This is THE config path; cmd.NewO11yConfig delegates here
|
|
// so standalone and embed read configuration identically.
|
|
func NewConfig(ctx context.Context, logger *slog.Logger, configFiles []string) (o11y.Config, error) {
|
|
uris := make([]string, 0, len(configFiles)+1)
|
|
for _, f := range configFiles {
|
|
uris = append(uris, "file:"+f)
|
|
}
|
|
uris = append(uris, "env:")
|
|
|
|
return o11y.NewConfig(
|
|
ctx,
|
|
logger,
|
|
config.ResolverConfig{
|
|
Uris: uris,
|
|
ProviderFactories: []config.ProviderFactory{
|
|
envprovider.NewFactory(),
|
|
fileprovider.NewFactory(),
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
// SQLStoreProviderFactories is the community SQL store provider set (the
|
|
// control-plane metadata store — sqlite by default).
|
|
func SQLStoreProviderFactories() factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]] {
|
|
return o11y.NewSQLStoreProviderFactories()
|
|
}
|
|
|
|
// SQLSchemaProviderFactories is the community SQL schema provider set.
|
|
func SQLSchemaProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
|
|
return o11y.NewSQLSchemaProviderFactories(sqlstore)
|
|
}
|
|
|
|
// NewO11y constructs the O11y runtime with the community provider set: noop
|
|
// zeus/licensing/gateway, Hanzo IAM authz (iamauthz — the sole authorizer), the
|
|
// Datastore (Hanzo Datastore) telemetry store, sqlite control-plane store, the
|
|
// full dashboard/cloudintegration/metricreductionrule/ruler modules, and (wired
|
|
// internally by o11y.New) the identN provider set including iamidentn — the
|
|
// gateway-header human identity the running pod trusts. The provider list is the
|
|
// single source of truth for how o11y boots.
|
|
func NewO11y(ctx context.Context, config o11y.Config) (*o11y.O11y, error) {
|
|
return o11y.New(
|
|
ctx,
|
|
config,
|
|
zeus.Config{},
|
|
noopzeus.NewProviderFactory(),
|
|
licensing.Config{},
|
|
func(_ sqlstore.SQLStore, _ zeus.Zeus, _ organization.Getter, _ analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
|
return nooplicensing.NewFactory()
|
|
},
|
|
o11y.NewEmailingProviderFactories(),
|
|
o11y.NewCacheProviderFactories(),
|
|
o11y.NewWebProviderFactories(config.Global),
|
|
SQLSchemaProviderFactories,
|
|
SQLStoreProviderFactories(),
|
|
o11y.NewTelemetryStoreProviderFactories(),
|
|
func(_ context.Context, sqlstore sqlstore.SQLStore, config authz.Config, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
|
// Authorization is selected by the TRUST BOUNDARY, not by pluggable policy —
|
|
// the enforced policy is identical either way, only where the relationship
|
|
// tuples live differs:
|
|
// - "local" (O11Y_AUTHZ_PROVIDER=local): the identity headers were minted
|
|
// by cloud from a Hanzo IAM bearer it validated (cloud embeds this
|
|
// module, and its /v1/o11y proxies to a standalone runtime with the
|
|
// same headers), and the sharder gates cross-org — so authorizing that
|
|
// user for their own org is a local decision. See pkg/authz/localauthz.
|
|
// - "iam" (default): every decision AND every tuple WRITE is delegated to
|
|
// Hanzo IAM's Casbin enforcer.
|
|
//
|
|
// The boundary is a property of the DEPLOYMENT, not of standalone-vs-embed:
|
|
// a standalone runtime that only cloud reaches has the embed's boundary and
|
|
// runs "local". No public route may reach one.
|
|
//
|
|
// A WARNING the default cannot express: "iam" requires IAM to serve
|
|
// add-policy / remove-policy / get-policies / batch-enforce, and IAM v1.34 —
|
|
// the clean-room zip v2 — does not. Selecting it today makes the founding
|
|
// grant fail with authz_unavailable on every sign-in. Choose "local" for a
|
|
// runtime only cloud reaches, or restore those routes first.
|
|
if config.Provider == "local" {
|
|
return localauthz.NewProviderFactory(sqlstore), nil
|
|
}
|
|
return iamauthz.NewProviderFactory(sqlstore), nil
|
|
},
|
|
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
|
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, tagModule)
|
|
},
|
|
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
|
return noopgateway.NewProviderFactory()
|
|
},
|
|
func(_ licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
|
|
return o11y.NewAuditorProviderFactories()
|
|
},
|
|
func(_ context.Context, _ factory.ProviderSettings, _ flagger.Flagger, _ licensing.Licensing, _ telemetrystore.TelemetryStore, _ retention.Getter, _ organization.Getter, _ zeus.Zeus) (factory.NamedMap[factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config]], string) {
|
|
return o11y.NewMeterReporterProviderFactories(), "noop"
|
|
},
|
|
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
|
return querier.NewHandler(ps, q, a)
|
|
},
|
|
func(_ sqlstore.SQLStore, _ dashboard.Module, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
|
return implcloudintegration.NewModule(), nil
|
|
},
|
|
func(_ sqlstore.SQLStore, _ telemetrystore.TelemetryStore, _ dashboard.Module, _ queryparser.QueryParser, _ licensing.Licensing, _ flagger.Flagger, _ telemetrytypes.MetadataStore, _ factory.ProviderSettings, _ int) metricreductionrule.Module {
|
|
return implmetricreductionrule.NewModule()
|
|
},
|
|
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
|
|
return factory.MustNewNamedMap(o11yruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, nil, nil))
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewServer constructs the O11y runtime and its HTTP server together. Callers
|
|
// choose the serving mode:
|
|
//
|
|
// - standalone (cmd/community): server.Start binds the listeners; O11y.Start
|
|
// runs background evaluation; O11y.Wait blocks.
|
|
// - embedded (hanzoai/cloud): O11y.Start runs background evaluation, and the
|
|
// server's route table is installed via o11y.SetRuntime — cloud's own HTTP
|
|
// stack serves /v1/o11y/*, resolving each address against that table; the
|
|
// listeners are never bound. A host that runs the runtime as a separate
|
|
// Deployment installs o11y.Whole over its proxy instead, because for a
|
|
// remote every address really is the same endpoint.
|
|
//
|
|
// Both paths share this ONE construction, so identity and authz are identical.
|
|
func NewServer(ctx context.Context, config o11y.Config) (*app.Server, *o11y.O11y, error) {
|
|
sn, err := NewO11y(ctx, config)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
server, err := app.NewServer(config, sn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return server, sn, nil
|
|
}
|