feat(tenants): provision the product tenant with the registry UUID on create (#21)
This commit was merged in pull request #21.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/authn"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/config"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/keycloak"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/product"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/store"
|
||||
)
|
||||
|
||||
@@ -20,8 +21,19 @@ type Server struct {
|
||||
Cfg *config.Config
|
||||
Log *slog.Logger
|
||||
Store store.Store
|
||||
Keycloak keycloak.Adapter // never nil — main wires Mock when KC env is unset
|
||||
Auth *authn.Verifier // nil ⇒ AUTH_ENABLED=false, API is open (dev only)
|
||||
Keycloak keycloak.Adapter // never nil — main wires Mock when KC env is unset
|
||||
Auth *authn.Verifier // nil ⇒ AUTH_ENABLED=false, API is open (dev only)
|
||||
Product product.Provisioner // never nil — main wires Noop when PRODUCT_API_URL is unset
|
||||
}
|
||||
|
||||
// productProvisioner guarantees the "never nil" invariant the struct documents.
|
||||
// Tests construct Server directly and would otherwise panic; an unset provisioner
|
||||
// simply means no downstream provisioning, never a crash mid-tenant-creation.
|
||||
func (s *Server) productProvisioner() product.Provisioner {
|
||||
if s.Product == nil {
|
||||
return product.NoopProvisioner{}
|
||||
}
|
||||
return s.Product
|
||||
}
|
||||
|
||||
// NewRouter builds the http.Handler with logging middleware applied.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/product"
|
||||
"gitea.meghsakha.com/platform/tenant-registry/internal/store"
|
||||
)
|
||||
|
||||
@@ -93,6 +94,27 @@ func (s *Server) createTenant(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// Best-effort product provisioning, same contract as Keycloak above: the
|
||||
// registry is the authority for tenant identity, so the product gets a row
|
||||
// keyed by OUR uuid. A failure must not roll the tenant back — it is
|
||||
// recorded as an audit event so the divergence is traceable and fixable.
|
||||
// Without this the anchors drift and the product's gate rejects every
|
||||
// request from the tenant because it cannot resolve the org slug.
|
||||
if perr := s.productProvisioner().ProvisionTenant(ctx, product.Tenant{
|
||||
ID: t.ID, Name: t.Name, Slug: t.Slug,
|
||||
}); perr != nil {
|
||||
s.emitAudit(ctx, r, store.AuditEvent{
|
||||
TenantID: t.ID, Action: "product.provision_failed",
|
||||
TargetID: t.ID, TargetType: "tenant",
|
||||
Metadata: map[string]interface{}{"err": perr.Error()},
|
||||
})
|
||||
} else {
|
||||
s.emitAudit(ctx, r, store.AuditEvent{
|
||||
TenantID: t.ID, Action: "product.tenant_provisioned",
|
||||
TargetID: t.ID, TargetType: "tenant",
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, createTenantResp{Tenant: t, InviteURL: inviteURL})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user