Repository navigation
[NestJS] Migrer le plugin observability depuis Fastify vers server-nestjs #2418
Description
Activity
- changed the title
[-][refactor] Migrer le plugin observability vers le module NestJS[/-][+][NestJS] Migrer le plugin observability depuis Fastify vers server-nestjs[/+]on Aug 5, 2026 - addedtechnical debtRésoud de la dette techniqueRésoud de la dette technique
on Aug 5, 2026 - added a parent issue
on Aug 5, 2026 Investigation: users lost Grafana access after the server-nestjs observability migration
Root cause (proven):
getListPermsinapps/server-nestjs/src/modules/observability/observability.utils.tsassigns every project user to exactly one Grafana bucket pair — prod if any prod environment exists, hors-prod otherwise. The legacy plugin (console-plugin-observabilitysrc/function.ts) bucketed per-stage: the Fastify hook wrapper (apps/server/src/utils/hook-wrapper.tstransformToHookProject) attached the same permission list to every environment, so users landed in both the prod and hors-prod Keycloak group pairs.Because
ObservabilityService.reconcileGroupMembershipis a diff-and-remove sync, on the firstproject.upsertunder server-nestjs every member of the group pair opposite to the chosen bucket is removed from Keycloak — and Grafana maps org roles from those groups. Loss of access follows deterministically.Evidence (repro):
apps/server-nestjs/src/modules/observability/zz-investigate-repro.spec.tspnpm vitest run src/modules/observability/zz-investigate-repro.spec.ts ✓ legacy contract: owner/member expected in BOTH buckets when both stages exist × 2 failed — perms['hors-prod'] is [] when a prod env exists (and vice versa)The existing spec (
observability.utils.spec.ts) asserts the new semantics as intended behavior (assigns to prod bucket when a prod environment exists), so this is a behavioral regression against the legacy contract, not an accidental typo — the spec codified the wrong contract.Secondary deltas vs legacy worth noting:
- Legacy
ensureKeycloakGroupsresolved the root group viakeycloakApi.getProjectGroupPath(); NestJS usesgenerateKeycloakRootGroupPath(project) = /${slug}— equivalent today, but the legacy path came from the Keycloak plugin's canonical source. - NestJS adds 404-tolerant add/remove (users without a Keycloak account are skipped with a warning) — an improvement; keep it.
Proposed solution (single change at the source): in
getListPerms(observability.utils.ts), restore per-stage bucketing: for each user compute(ro, rw)once, then add toprodif any prod-stage environment exists and add tohors-prodif any non-prod environment exists — i.e. mirror the legacy semantics where both pairs receive the full permission roster. Update the three assertions inobservability.utils.spec.tsthat codified the single-bucket contract.Sketch:
for (const userId of projectUserIds) { const { ro, rw } = resolveUserPerms(project, rolesById, userId) const hasProd = project.environments.some(e => isProdStage(e.stage)) const hasHprod = project.environments.some(e => !isProdStage(e.stage)) if (hasProd) { if (rw && !perms.prod.edit.includes(userId)) perms.prod.edit.push(userId) if (ro && !perms.prod.view.includes(userId)) perms.prod.view.push(userId) } if (hasHprod) { if (rw && !perms['hors-prod'].edit.includes(userId)) perms['hors-prod'].edit.push(userId) if (ro && !perms['hors-prod'].view.includes(userId)) perms['hors-prod'].view.push(userId) } }
Regression test: keep
zz-investigate-repro.spec.tsassertions (both buckets populated when both stages exist), promoted intoobservability.utils.spec.ts; plus an end-to-end guard inobservability.service.spec.ts: given existing Keycloak members in both pairs and a project with both stages,reconcileGroupMembershipremoves nobody.Remediation note for affected instances: removing users was destructive — after deploying the fix, a
project.upsertreplay per affected project re-adds the removed memberships (re-add is idempotent; the sync restores desired state).Handing off to
sks-dev-workflow/sks-prfor the actual change.- Legacy
- linked a pull request that will close this issuefeat(server-nestjs): migrate observability plugin to NestJS module #2419
on Oct 8, 2026
Description
Issue de suivi globale : #1889 — [NestJS] Modularisation de server
Ce ticket couvre spécifiquement le portage du plugin observability (
console-plugin-observability, dossier externeplugins/) vers un module NestJS interne dansapps/server-nestjs/src/modules/observability/.Portage complet du plugin observability :
project.upsert— génération des values Helm (Grafana), création des groupes Keycloak RBAC, commit dans le repo GitLab globalproject.delete— suppression des values + nettoyage des groupes KeycloakServiceInfos(titre, description, config schema, URLs Grafana)ConditionalModule.registerWhen(ObservabilityModule, 'USE_OBSERVABILITY')Le plugin externe
console-plugin-observability:preCreate/preUpdate: récupère les données projet (membres, rôles, environnements avec stage)rw(read-write) /ro(read-only)/grafana/prod-RW,/grafana/prod-RO,/grafana/hprod-RW,/grafana/hprod-ROvalues.yamldans le repo GitLab global observability (un tenant par env, ID compressé viacompressProjectId)Utiliser
@OnEvent('project.upsert')et@OnEvent('project.delete')via@nestjs/event-emitter, aveccapturePluginResult('observability', ...)— même patron que les modules déjà migrés (gitlab, sonarqube).GitlabClientServicepour les opérations repo/file/commit (méthodecreateGroupRepoajoutée)KeycloakClientServicepour la gestion des groupes/membresPas de duplication des clients API
GitlabModule : déjà migré —
GitlabClientServiceétendu aveccreateGroupRepo()KeycloakModule : déjà migré —
KeycloakClientServiceréutiliséEventEmitterModule (Couche 0d) : doit être fonctionnel
Config Zod :
observabilityConfigFactory(GRAFANA_URL,DSO_OBSERVABILITY_CHART_VERSION).env-example/.env.docker-example:USE_OBSERVABILITY=falseajouté.env.integ-example: à mettre à jour~1,5 jour (module déjà compilé et type-checké, reste tests + validation intégration).
Issues liées
plugins/observability/(console-plugin-observability)Définition du fini
observabilitycréé dansapps/server-nestjs/src/modules/observability/project.upsert/project.deleteimplémentés avec événementsUSE_OBSERVABILITY.env.integ-examplemis à jour avecUSE_OBSERVABILITY