Intégration Moodle¶
Cette intégration relie CloudPoolManager au Moodle de l'établissement pour trois usages :
- Importer la liste des élèves d'un cours Moodle et les inscrire automatiquement à un pool de VMs.
- Permettre aux élèves de se connecter avec leur compte Moodle (en plus de GitHub / SSO).
- Remonter les notes nbgrader vers le carnet de notes Moodle.
Tout repose sur les Web Services REST de Moodle (authentifiés par un token) et sur le point
d'entrée login/token.php pour valider les identifiants. L'intégration est pensée pour passer du
Moodle local de développement au Moodle de l'école en changeant uniquement deux variables
(MOODLE_URL et MOODLE_TOKEN).
⚠️ Au déploiement : accès en LECTURE SEULE¶
En production, l'établissement n'accordera très probablement qu'un token en lecture seule. Conséquences : - ✅ Fonctionne : import des élèves, lecture des cours/inscriptions, connexion via Moodle (
login/token.phpne nécessite pas d'écriture). - ❌ Ne fonctionnera pas sans accès en écriture : la remontée des notes (mod_assign_save_gradeest une opération d'écriture). Le bouton « Envoyer les notes vers Moodle » renverra simplement une erreur côté serveur, sans rien casser d'autre.Autrement dit : conçu pour fonctionner en lecture seule. Le push de notes est un bonus qui ne s'activera que si l'école fournit un token avec les droits d'écriture (
mod_assign_save_grade). En lecture seule, l'enseignant continue d'exporter le CSV des notes nbgrader et de le téléverser manuellement dans Moodle.
Architecture¶
flowchart LR
subgraph CPM["CloudPoolManager — control center"]
EP["Endpoints REST\n/api/moodle/*"]
MC["Client Web Services\ninternal/moodle/client.go"]
end
subgraph M["Moodle"]
WS["Web Services REST\n(server.php)"]
TK["login/token.php"]
end
PG[("PostgreSQL\nstudents.moodle_email / moodle_user_id\nserverpools.moodle_course_id\nmoodle_sessions")]
FE["Frontend SvelteKit\n(login, portail étudiant, notation)"]
FE -->|fetch JSON| EP
EP --> MC --> WS
EP -->|valide identifiants| TK
EP --> PG
- Client Go :
control_center/internal/moodle/client.go(un wrapper minimal des Web Services). - Endpoints REST :
control_center/grpc/moodle.go(routés dansgrpc/server.go). - Données : 3 ajouts au schéma (voir plus bas).
L'email est la clé d'identité universelle. C'est lui qui relie : compte Moodle ↔ ligne
students↔ identifiant nbgrader ↔ utilisateur Moodle pour la remontée des notes. Lors de l'import, on posestudents.name = emailpour que cette chaîne soit cohérente de bout en bout.
Configuration¶
Deux variables d'environnement (dans .env racine et control_center/.env, gitignorés) :
MOODLE_URL=https://moodle.exemple.fr # racine du Moodle (sans slash final)
MOODLE_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxx # token Web Services (secret)
Si ces variables sont absentes, toute la fonctionnalité Moodle est désactivée proprement
(moodle.Configured() renvoie false) : les onglets/boutons Moodle n'apparaissent pas dans l'UI.
Fonctions Web Services utilisées¶
| Fonction | Usage | Lecture/écriture |
|---|---|---|
core_course_get_courses_by_field |
lister les cours¹ | lecture |
core_enrol_get_enrolled_users |
élèves inscrits à un cours | lecture |
core_user_get_users_by_field |
retrouver l'email d'un utilisateur (par login) | lecture |
core_webservice_get_site_info |
info de session (sanity check) | lecture |
login/token.php |
valider les identifiants (connexion) | lecture |
mod_assign_get_assignments |
lister les devoirs d'un cours | lecture |
mod_assign_save_grade |
écrire une note | écriture ⚠️ |
¹ core_course_get_courses plante sur un verrou de cache avec certaines installations ; on utilise
donc ..._by_field.
Modèle de données (ajouts)¶
| Table / champ | Rôle |
|---|---|
students.moodle_email |
email Moodle de l'élève (clé de jointure VM↔élève, notes↔élève) |
students.moodle_user_id |
id numérique Moodle (pour mod_assign_save_grade) |
serverpools.moodle_course_id |
cours Moodle lié au pool (0 = aucun) |
table moodle_sessions |
session légère après login Moodle (id, email, fullname, role) |
Tout est créé par l'AutoMigrate GORM (config/database.go).
1) Import des élèves d'un cours¶
Côté prof : un pool → bouton « Étudiants » → onglet « Moodle » → choisir un cours → cocher les élèves → Importer.
GET /api/moodle/courses— liste des cours (pour le sélecteur).GET /api/moodle/enrolments?course_id=X— élèves inscrits (aperçu).POST /api/moodle/import{pool_id, user_id, course_id, emails?}— crée une lignestudentspar élève (name = email,moodle_email,moodle_user_id), sans clé SSH, et mémoriseserverpools.moodle_course_id. Idempotent (ne duplique pas).
On peut aussi lier un pool à un cours sans importer (pour la remontée de notes) :
POST /api/moodle/link-pool {pool_id, user_id, course_id}.
2) Connexion via Moodle (élèves)¶
Côté élève : page de connexion / (ou portail /student) → « Se connecter avec Moodle »
→ identifiant + mot de passe Moodle.
sequenceDiagram
participant E as Élève
participant CC as Control Center
participant M as Moodle
E->>CC: POST /api/moodle/login (login, mot de passe)
CC->>M: login/token.php (valide les identifiants)
M-->>CC: token (ou erreur)
CC->>M: core_user_get_users_by_field (email via token de service)
M-->>CC: email, nom
CC->>CC: crée une session (moodle_sessions)
CC-->>E: {email, fullname} → portail étudiant
POST /api/moodle/login— valide vialogin/token.php, récupère l'email via le token de service (le token utilisateur n'a pas accès à la lecture des emails), crée une session. C'est un flux étudiant : les enseignants/admins utilisent le SSO Polytechnique (OIDC).GET /api/moodle/my-pools?email=— les pools où cet email est inscrit.POST /api/moodle/attrib-vm{pool_id, user_id, email}— attribue une VM sans clé SSH (attribvm.AttribVMByEmail). L'accès se fait par JupyterLab (navigateur) et le terminal Guacamole (clé gérée côté plateforme) ; aucune clé personnelle n'est requise. Même exclusion de la VM enseignant que le flux par clé SSH.- Clé SSH optionnelle :
POST /api/moodle/ssh-key{email, ssh_key}(page « Mon compte ») pour ceux qui veulent du SSH direct.
3) Remontée des notes ⚠️ (écriture — voir l'encart en tête)¶
Côté prof : page Notation → noter (collecter + notation auto) → choisir le devoir Moodle cible → « Envoyer les notes vers Moodle ». Option : envoi automatique après chaque notation.
GET /api/moodle/assignments?pool_id=&user_id=— devoirs (mod_assign) du cours lié au pool.POST /api/moodle/push-grades{pool_id, user_id, assignment, moodle_assign_id}:- lit les notes nbgrader (
fetchNbgraderGrades, qui réutilisenbgrader export), - ignore les non-rendus (
status == missing), - mappe
email → moodle_user_id, - met à l'échelle
note = score / max_score × barème du devoir Moodle, - appelle
mod_assign_save_gradepour chaque élève. - Lien « Carnet de notes Moodle ↗ » (deep-link) + « Moodle » dans le menu.
En lecture seule, cette étape échoue (write refusé) → utiliser Exporter CSV + import manuel dans Moodle.
Endpoints REST (récap)¶
| Méthode + route | Rôle | Écriture Moodle ? |
|---|---|---|
GET /api/moodle/status |
Moodle configuré ? (+ URL) | — |
GET /api/moodle/courses |
liste des cours | non |
GET /api/moodle/enrolments?course_id= |
élèves d'un cours | non |
POST /api/moodle/import |
importer des élèves dans un pool | non |
POST /api/moodle/link-pool |
lier un pool ↔ un cours | non |
POST /api/moodle/login |
connexion élève | non |
GET /api/moodle/session?id= |
identité d'une session | non |
GET /api/moodle/my-pools?email= |
pools de l'élève | non |
POST /api/moodle/attrib-vm |
attribuer une VM (sans clé) | non |
POST /api/moodle/ssh-key |
ajouter une clé SSH (optionnel) | non |
GET /api/moodle/assignments |
devoirs d'un cours | non |
POST /api/moodle/push-grades |
remonter les notes | OUI ⚠️ |
→ Tout est en lecture sauf push-grades. Sans droit d'écriture, seul ce dernier est indisponible.
Moodle local de développement¶
moodle/ contient un Moodle complet (Docker) pour développer sans dépendre du Moodle de l'école.
Voir moodle/README.md.
cd moodle && cp .env.example .env && docker compose up -d # ~quelques min au 1er boot
cd .. && scripts/moodle-bootstrap.sh
Le bootstrap (idempotent) : active les Web Services + REST, crée un service dédié + un token,
crée 2 cours de démo, 4 élèves + 1 prof + inscriptions, 1 devoir par cours, et écrit
MOODLE_URL/MOODLE_TOKEN dans .env (racine + control_center/.env).
UI : http://localhost:8081 (admin / MOODLE_ADMIN_PASSWORD). Comptes de démo : alice, bob,
charlie, diana, prof prof1 — mot de passe Student_2026!.
Le Moodle local sert UNIQUEMENT au développement : il a un token admin (lecture + écriture), ce qui permet de tester aussi la remontée des notes. Le déploiement réel, lui, sera lecture seule.
Pièges techniques (mémo)¶
- Images
bitnamilegacy/: Bitnami a déplacé ses tags versionnés vers ce namespace en 2025. core_course_get_courses→ verrou de cache ; utilisercore_course_get_courses_by_field.login/token.phpexige le service mobile activé côté Moodle.- L'email se lit via le token de service (le token utilisateur n'y a pas accès).
mod_assign_get_assignmentsfiltre par inscription → en dev, l'admin est inscrit enseignant dans chaque cours par le bootstrap.- Perms
moodledatarendues inscriptibles après le bootstrap (les WS créent des dossiers temporaires) — dev uniquement.
Sécurité¶
MOODLE_TOKENet les mots de passe Moodle sont gitignorés (*.env) ; le dépôt ne contient que des.env.example.- En production, demander à la DSI un token de service dédié, en lecture seule, limité aux
fonctions listées (sans
mod_assign_save_gradesauf si la remontée de notes est souhaitée).