Pulse/internal/api/router_helpers.go
rcourtman 0622169be8 Harden multi-tenant GA readiness
Revalidate and harden the Pulse multi-tenant GA surface across org ownership transfer, MSP invite lifecycle, tenant state fallback, registry workspace limits, and the embedded Pulse Account portal bundle.
2026-04-23 23:04:03 +01:00

125 lines
3.7 KiB
Go

package api
import (
"errors"
"net/http"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
"github.com/rs/zerolog/log"
)
var errTenantMonitorUnavailable = errors.New("tenant monitor unavailable")
// getMonitor returns the tenant-specific monitor instance for the request.
// It uses the OrgID from the context (injected by TenantMiddleware).
// Non-default orgs fail closed when tenant monitor resolution is unavailable.
func (r *Router) getMonitor(req *http.Request) (*monitoring.Monitor, error) {
orgID := GetOrgID(req.Context())
if orgID == "" || orgID == "default" {
return r.monitor, nil
}
if r.mtMonitor == nil {
return nil, errTenantMonitorUnavailable
}
return r.mtMonitor.GetMonitor(orgID)
}
// MultiTenantStateProvider wraps a MultiTenantMonitor to provide state for specific tenants.
type MultiTenantStateProvider struct {
mtMonitor *monitoring.MultiTenantMonitor
defaultMonitor *monitoring.Monitor
}
// NewMultiTenantStateProvider creates a new tenant state provider.
func NewMultiTenantStateProvider(mtm *monitoring.MultiTenantMonitor, defaultM *monitoring.Monitor) *MultiTenantStateProvider {
return &MultiTenantStateProvider{
mtMonitor: mtm,
defaultMonitor: defaultM,
}
}
func (p *MultiTenantStateProvider) monitorForTenant(orgID string) *monitoring.Monitor {
if orgID == "" || orgID == "default" {
return p.defaultMonitor
}
if p.mtMonitor != nil {
monitor, err := p.mtMonitor.GetMonitor(orgID)
if err != nil {
log.Warn().Err(err).Str("org_id", orgID).Msg("Failed to get tenant monitor")
return nil
}
return monitor
}
return nil
}
// UnifiedReadStateForTenant returns the canonical typed unified read-state for a
// specific tenant, falling back to a snapshot-backed adapter only when the
// monitor has not been wired with a resource store yet.
func (p *MultiTenantStateProvider) UnifiedReadStateForTenant(orgID string) unifiedresources.ReadState {
monitor := p.monitorForTenant(orgID)
if monitor == nil {
return nil
}
return monitor.GetUnifiedReadStateOrSnapshot()
}
// UnifiedResourceSnapshotForTenant returns the canonical unified-resource seed
// for a specific tenant, along with its freshness marker.
func (p *MultiTenantStateProvider) UnifiedResourceSnapshotForTenant(orgID string) ([]unifiedresources.Resource, time.Time) {
monitor := p.monitorForTenant(orgID)
if monitor == nil {
return nil, time.Time{}
}
return monitor.UnifiedResourceSnapshot()
}
// SetMultiTenantMonitor updates the multi-tenant monitor manager.
// Used during reload.
func (r *Router) SetMultiTenantMonitor(mtm *monitoring.MultiTenantMonitor) {
r.mtMonitor = mtm
if r.configHandlers != nil {
r.configHandlers.SetMultiTenantMonitor(mtm)
}
if r.alertHandlers != nil {
r.alertHandlers.SetMultiTenantMonitor(mtm)
}
if r.notificationHandlers != nil {
r.notificationHandlers.SetMultiTenantMonitor(mtm)
}
if r.dockerAgentHandlers != nil {
r.dockerAgentHandlers.SetMultiTenantMonitor(mtm)
}
if r.unifiedAgentHandlers != nil {
r.unifiedAgentHandlers.SetMultiTenantMonitor(mtm)
}
if r.kubernetesAgentHandlers != nil {
r.kubernetesAgentHandlers.SetMultiTenantMonitor(mtm)
}
if r.systemSettingsHandler != nil {
r.systemSettingsHandler.SetMultiTenantMonitor(mtm)
}
if r.aiSettingsHandler != nil {
r.aiSettingsHandler.SetMultiTenantMonitor(mtm)
}
if r.aiHandler != nil {
r.aiHandler.SetMultiTenantMonitor(mtm)
}
if mtm != nil {
if m, err := mtm.GetMonitor("default"); err == nil {
r.monitor = m
}
mtm.SetMonitorInitializer(r.configureMonitorDependencies)
}
// Wire tenant state provider to resource handlers
if r.resourceHandlers != nil {
r.resourceHandlers.SetTenantStateProvider(NewMultiTenantStateProvider(mtm, r.monitor))
}
}