[Bug] Scan partiel déclenche un scan complet #9

Open
opened 2026-06-10 21:00:52 +02:00 by shuss · 3 comments
Owner

Comportement actuel : Lorsqu'un scan partiel est demandé pour un jukebox Vynil, le système effectue malgré tout un scan complet.
Impact : Ralentissement inutile des opérations et pénibilité pour l'utilisateur/admin.
Attente : Le scan partiel doit strictement se limiter aux chemins/fichiers ciblés.

**Comportement actuel :** Lorsqu'un scan partiel est demandé pour un jukebox Vynil, le système effectue malgré tout un scan complet. **Impact :** Ralentissement inutile des opérations et pénibilité pour l'utilisateur/admin. **Attente :** Le scan partiel doit strictement se limiter aux chemins/fichiers ciblés.

Analyse de la cause probable, après lecture de agent/scripts/boxes/scan.rhai :

  1. Le listing des dépôts n'est pas filtré. Pour les sources harbor/gitlab, list_repository(spec, namespace) est appelé avant l'application du filtre : tout le projet est énuméré via l'API même pour un scan partiel.
  2. Fallback silencieux vers le scan complet. Le filtre est résolu en couples registry/image via box.status.packages ; si rien ne matche (premier scan, paquet jamais scanné, status perdu), le code retombe sur la liste complète :
    if deduped.len() > 0 { deduped } else { list }
    
    Un scan partiel sur une cible inconnue du status devient donc un scan complet — c'est très probablement le comportement observé.

Proposition :

  • ne jamais retomber sur la liste complète : si le filtre ne matche rien dans le status, terminer avec un set_status_packages_merge vide + un log_warn explicite (« cible inconnue, lancez un scan complet ») ;
  • quand le filtre matche, court-circuiter le list_repository() complet (les couples registry/image sont déjà connus) — c'est là que se gagne l'essentiel du temps ;
  • les sources http/s3 filtrent déjà correctement par entrée d'index (continue avant le fetch), elles ne sont pas concernées.

Tests : scan partiel avec cible présente dans le status → seuls ses dépôts sont interrogés ; cible absente → aucun appel registre, warning, status inchangé.


Analyse et rédaction : Claude (assistant IA), publié via le compte de Xavier.

Analyse de la cause probable, après lecture de `agent/scripts/boxes/scan.rhai` : 1. **Le listing des dépôts n'est pas filtré.** Pour les sources `harbor`/`gitlab`, `list_repository(spec, namespace)` est appelé avant l'application du filtre : tout le projet est énuméré via l'API même pour un scan partiel. 2. **Fallback silencieux vers le scan complet.** Le filtre est résolu en couples registry/image via `box.status.packages` ; si rien ne matche (premier scan, paquet jamais scanné, status perdu), le code retombe sur la liste complète : ```rhai if deduped.len() > 0 { deduped } else { list } ``` Un scan partiel sur une cible inconnue du status devient donc un scan complet — c'est très probablement le comportement observé. Proposition : - ne **jamais** retomber sur la liste complète : si le filtre ne matche rien dans le status, terminer avec un `set_status_packages_merge` vide + un `log_warn` explicite (« cible inconnue, lancez un scan complet ») ; - quand le filtre matche, court-circuiter le `list_repository()` complet (les couples registry/image sont déjà connus) — c'est là que se gagne l'essentiel du temps ; - les sources `http`/`s3` filtrent déjà correctement par entrée d'index (`continue` avant le fetch), elles ne sont pas concernées. Tests : scan partiel avec cible présente dans le status → seuls ses dépôts sont interrogés ; cible absente → aucun appel registre, warning, status inchangé. --- *Analyse et rédaction : Claude (assistant IA), publié via le compte de Xavier.*
Author
Owner

Mauvaise analyse : ca arrive aussi quand le package est connu et référencé.

Le problème est plus simple : l'utilisateur demande un scan apps/monappli, mais la référence interne est {quelques-chose}/apps/monappli. Ce ce qui génère le mis-match. Idem pour un scan de catégorie, il demande apps mais en interne il y a {quelques-chose}/apps/{package_name}.

Dans les faits, je n'ai jamais vu l'agent recevoir le nom du package a scanner alors que j'ajoute l'annotations

Mauvaise analyse : ca arrive aussi quand le package est connu et référencé. Le problème est plus simple : l'utilisateur demande un scan apps/monappli, mais la référence interne est {quelques-chose}/apps/monappli. Ce ce qui génère le mis-match. Idem pour un scan de catégorie, il demande apps mais en interne il y a {quelques-chose}/apps/{package_name}. Dans les faits, je n'ai jamais vu l'agent recevoir le nom du package a scanner alors que j'ajoute l'annotations
shuss added the Kind/Bug
Priority
High
2
labels 2026-06-12 15:00:56 +02:00

Analyse corrigée — cause racine côté opérateur, liée à #17

Tu as raison, ma première analyse était à côté. En retraçant le cycle complet de réconciliation (operator/src/jukebox.rs), le mécanisme exact est le suivant :

  1. Quand tu poses l'annotation vynil.solidite.fr/force-scan: apps/monappli, l'opérateur crée bien le job scan-<name> avec SCAN_PACKAGE=apps/monappli (le job précédent est supprimé, le filtre est injecté, l'annotation est retirée après création). Pendant que ce job partiel tourne, la garde le protège (requeue 60 s).
  2. Dès que le job partiel est terminal, la réconciliation suivante (≤ 60 s après) retombe dans le fall-through et ré-applique le template scan.yamlsans SCAN_PACKAGE puisque l'annotation a été consommée. Le spec diffère du job existant, or spec.template d'un Job Kubernetes est immuable → l'apply échoue → la branche de secours supprime le job partiel et recrée un job de scan complet (jukebox.rs:167-187).

Cela explique exactement tes deux observations :

  • « le scan partiel déclenche un scan complet » : oui, systématiquement, ~0-60 s après la fin du scan partiel ;
  • « je n'ai jamais vu l'agent recevoir SCAN_PACKAGE » : le job partiel existe avec la variable, mais il est éphémère — supprimé et remplacé par un job complet qui porte le même nom (scan-<name>). Quand tu inspectes le job, tu vois le remplaçant.

Sur l'hypothèse du mismatch de chemin ({quelque-chose}/apps/monappli) : le matching dans scan.rhai se fait sur metadata.category / metadata.name des packages du status (scan.rhai:203-208), pas sur les chemins de dépôt — apps/monappli devrait donc matcher. Le scan complet observé n'est pas un mauvais matching : c'est le job de remplacement décrit ci-dessus.

Correction proposée (opérateur)

Ne plus ré-appliquer le spec du Job à chaque réconciliation. Le Job direct ne doit être créé/recréé que dans deux cas :

  1. il n'existe pas (scan initial à la création du JukeBox) ;
  2. l'annotation force-scan est présente (suppression + recréation avec filtre éventuel).

Un job terminal sans annotation ne doit jamais être touché — les rescans périodiques sont la responsabilité du CronJob.

En durcissement côté agent (ma proposition initiale reste valable en complément) : supprimer le fallback if deduped.len() > 0 { deduped } else { list } qui transforme silencieusement un filtre sans correspondance en scan complet — préférer un log_warn + merge vide.

La correction est commune avec #17 (même zone de code, même refonte du cycle job/cache) ; le plan de test complet de remédiation et de non-régression couvrant les deux issues est posté sur #17.


Analyse rédigée par Claude (assistant IA), pour le compte de @reivaxm.

## Analyse corrigée — cause racine côté opérateur, liée à #17 Tu as raison, ma première analyse était à côté. En retraçant le cycle complet de réconciliation (`operator/src/jukebox.rs`), le mécanisme exact est le suivant : 1. Quand tu poses l'annotation `vynil.solidite.fr/force-scan: apps/monappli`, l'opérateur **crée bien** le job `scan-<name>` avec `SCAN_PACKAGE=apps/monappli` (le job précédent est supprimé, le filtre est injecté, l'annotation est retirée après création). Pendant que ce job partiel tourne, la garde le protège (requeue 60 s). 2. **Dès que le job partiel est terminal**, la réconciliation suivante (≤ 60 s après) retombe dans le fall-through et ré-applique le template `scan.yaml` — **sans** `SCAN_PACKAGE` puisque l'annotation a été consommée. Le spec diffère du job existant, or `spec.template` d'un Job Kubernetes est **immuable** → l'apply échoue → la branche de secours **supprime le job partiel et recrée un job de scan complet** (`jukebox.rs:167-187`). Cela explique exactement tes deux observations : - « le scan partiel déclenche un scan complet » : oui, systématiquement, ~0-60 s après la fin du scan partiel ; - « je n'ai jamais vu l'agent recevoir SCAN_PACKAGE » : le job partiel **existe** avec la variable, mais il est éphémère — supprimé et remplacé par un job complet **qui porte le même nom** (`scan-<name>`). Quand tu inspectes le job, tu vois le remplaçant. Sur l'hypothèse du mismatch de chemin (`{quelque-chose}/apps/monappli`) : le matching dans `scan.rhai` se fait sur `metadata.category` / `metadata.name` des packages du status (`scan.rhai:203-208`), pas sur les chemins de dépôt — `apps/monappli` devrait donc matcher. Le scan complet observé n'est pas un mauvais matching : c'est le job de remplacement décrit ci-dessus. ### Correction proposée (opérateur) Ne plus ré-appliquer le spec du Job à chaque réconciliation. Le Job direct ne doit être créé/recréé que dans deux cas : 1. il n'existe pas (scan initial à la création du JukeBox) ; 2. l'annotation `force-scan` est présente (suppression + recréation avec filtre éventuel). Un job terminal sans annotation ne doit **jamais** être touché — les rescans périodiques sont la responsabilité du CronJob. En durcissement côté agent (ma proposition initiale reste valable en complément) : supprimer le fallback `if deduped.len() > 0 { deduped } else { list }` qui transforme silencieusement un filtre sans correspondance en scan complet — préférer un `log_warn` + merge vide. La correction est commune avec #17 (même zone de code, même refonte du cycle job/cache) ; le plan de test complet de remédiation et de non-régression couvrant les deux issues est posté sur #17. --- *Analyse rédigée par Claude (assistant IA), pour le compte de @reivaxm.*
Sign in to join this conversation.