Skip to content
Snippets Groups Projects
Verified Commit 93bb33df authored by steven.liatti's avatar steven.liatti
Browse files

Update doc, remove old unnecessary scripts

parent 561a9b22
No related branches found
No related tags found
No related merge requests found
# Practical Work Manager (pwm)
Ce repository contient différents scripts python pour gérer la réalisation de travaux pratiques par les étudiants avec la contrainte d'utiliser le gitlab d'HEPIA.
Programme python pour gérer les travaux pratiques des étudiants avec la contrainte d'utiliser le gitlab d'HEPIA.
## TL;DR
- Créer un groupe et les repositories en une seule commande (avec un repository "image" optionnel) : `./pwm -t TOKEN group_repos GROUP_NAME REPOS_FILE [-i IMPORT_URL]`, voir [Syntaxe du fichier YAML (REPOS_FILE)](#syntaxe-du-fichier-yaml-repos_file)
- Clone tous les projets des étudiants d'un groupe gitlab (`ID`) dans un répertoire créé à la volée : `./pwm -t TOKEN clone ID DIRECTORY`
- [Practical Work Manager (pwm)](#practical-work-manager-pwm)
- [TL;DR](#tldr)
- [Workflow d'utilisation](#workflow-dutilisation)
- [Utilisation de pwm](#utilisation-de-pwm)
- [Création d'un groupe et des projets](#création-dun-groupe-et-des-projets)
- [Création d'un groupe seulement](#création-dun-groupe-seulement)
- [Création d'un sous-projet dans le groupe](#création-dun-sous-projet-dans-le-groupe)
- [Clone de tous les repositories](#clone-de-tous-les-repositories)
- [Syntaxe du fichier YAML (REPOS_FILE)](#syntaxe-du-fichier-yaml-repos_file)
- [Noms et emails](#noms-et-emails)
- [Emails seulement](#emails-seulement)
## Workflow d'utilisation
Pour expliquer la démarche d'utilisation, prenons le scénario suivant :
- L'enseignant Michel Albuquerque prépare un nouveau travail pratique (TP). Il souhaite forcer les étudiants à utiliser git et githepia pour qu'ils versionnent leur code et pour qu'il puisse visualiser et recevoir leur rendus.
- L'enseignant Michel Albuquerque prépare un nouveau travail pratique (TP). Il souhaite forcer les étudiants à utiliser git et gitedu pour qu'ils versionnent leur code et pour qu'il puisse visualiser et recevoir leur rendus.
- Les TPs sont à faire par groupe ou de manière individuelle.
- Pour transmettre son énoncé et des fichiers (exemples, squelette de code, librairies, binaires, etc.) aux étudiants, Michel Albuquerque crée un repository git accessible publiquement, nommé "super-tp".
- Grâce à ce repository "super-tp", il peut mettre à jour le contenu distribué aux étudiants, en leur offrant la possibilité de visualiser les changements incrémentaux survenus.
Le moment est venu de créer les dépôts git pour chaque groupe/étudiant suivant le cours et devant réaliser le TP. Sur la base d'une liste de groupes ou d'étudiants, Michel Albuquerque pourra utiliser les scripts suivants pour :
Le moment est venu de créer les dépôts git pour chaque groupe/étudiant suivant le cours et devant réaliser le TP. Sur la base d'une liste de groupes ou d'étudiants, Michel Albuquerque pourra utiliser le programme pour :
1. Créer le groupe (namespace) dédié au cours/TP, contenant tous les repositories des étudiants.
1. Créer chaque repository pour chaque groupe/étudiant avec les contraintes nécessaires (privé, accessible à (aux) l'étudiant(s) concerné(s), aux enseignants, etc.)
1. Récupérer (clone) sur sa machine tous les repositories d'un seul coup, dans des répertoires séparés, au moment du rendu par exemple.
La section suivante décrit l'utilisation des scripts.
## Utilisation de pwm
## Utilisation des scripts
Ce programme est écrit en python et testé avec la version 3.9.0, avec les dépendances suivantes (voir `requirements.txt`) :
Tous les scripts sont (actuellement) écrits en python et testés avec la version 3.6.8, sans dépendances à des librairies externes. Ils se présentent sous la forme de programmes à lancer dans un shell en ligne de commande. Ils nécessitent tous un `token` gitlab, pouvant être généré [sur cette page](https://gitedu.hesge.ch/profile/personal_access_tokens), en cochant la case "api". Certains attendent également un `project_id` correspond à celui affiché sur la page de repo :
```
requests
pyyaml
```
![image](doc/project_id.png)
Pour rappel, pour ne pas à avoir à installer ces deux dépendances au niveau système, les commandes suivantes génèrent un environnement virtuel :
### create_group.py
```bash
python3 create_group.py <token> <group_name> <visibility>
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Crée un groupe au sens gitlab du terme, nommé `group_name`, avec la visibilité optionnelle `visibility` (`private`, `internal` ou `public`), par défaut privée. Si le groupe existe déjà, ne fait rien. Retourne le `group_id` du groupe créé, nécessaire pour `create_repo_for_students.py` par exemple.
### create_repo_for_students.py
pwm se présente sous la forme d'une CLI. Il nécessite un `token` gitlab, pouvant être généré [sur cette page](https://gitedu.hesge.ch/profile/personal_access_tokens), en cochant la case "api". Ce `token` peut ensuite être utilisé de trois manières :
1. Écrit dans le fichier `~/.gitedu_token`
2. Placé dans la variable d'environnement `GITEDU_TOKEN`
3. Donné en argument de `pwm` avec l'option `-t` ou `--token`
Selon les commandes, un `project_id` ou `group_id` est également nécessaire, il correspond à celui affiché sur la page du groupe / projet :
![image](project_id.png)
L'exécution du programme sans arguments affiche l'aide et le détail pour chaque sous-commande.
### Création d'un groupe et des projets
```bash
python3 create_repo_for_students.py <token> <import_url> <group_id> <project_name> <student-mail1,student-mail2,...,student-mailN> <expires_at>
python pwm group_repos GROUP_NAME REPOS_FILE [-h] [--visibility VISIBILITY] [-i IMPORT_URL] [-x EXPIRES_AT]
```
Crée un dépôt git (projet) au sein d'un groupe à partir de l'URL d'un projet existant pour une liste d'emails d'étudiants. Détail des arguments :
- `token` : le token gitlab.
- `import_url` : l'URL (http) du projet (repository) existant. Ce projet doit être public.
- `group_id` : l'identifiant du groupe dédié au cours/TP, créé précédemment (avec `create_group.py` par exemple).
- `project_name` : le nom du nouveau repository à créer pour le ou les étudiants concernés.
- `student-mail1,student-mail2,...,student-mailN` : une liste d'emails des étudiants. Les emails sont séparés par une virgule. Peut contenir un seul email.
- `expires_at`: optionnel, au format `AAAA-MM-DD`, supprime les étudiants ajoutés à la date donnée.
### clone_all_repos_in_group.py
Exécute les opérations de création de groupe et de repositories à partir d'un fichier YAML (voir [Syntaxe du fichier YAML (REPOS_FILE)](#syntaxe-du-fichier-yaml-repos_file)). Voir les sous-sections suivantes pour les détails des sous-commandes.
### Création d'un groupe seulement
```bash
python3 clone_all_repos_in_group.py <token> <group_id> <directory> <until_date>
python pwm group [-h] [--visibility VISIBILITY] GROUP_NAME
```
Clone tous les repositories d'un groupe `group_id` donné dans un répertoire nommé `directory`. Si une date `until_date` (au format `AAAA-MM-DD hh:mm`) est donnée, exécute un `git checkout` sur le premier commit précédant cette date. Affiche sur la sortie standard les membres du groupe, l'url web du repo et dans quel sous-répertoire se trouvent les fichiers.
Crée un groupe au sens gitlab du terme, nommé `GROUP_NAME`, avec la visibilité optionnelle `VISIBILITY` (`private`, `internal` ou `public`), par défaut privée. Si le groupe existe déjà, ne fait rien. Retourne le `group_id` du groupe créé, nécessaire pour la création des sous-projets par exemple.
### clone_all_forks.py
### Création d'un sous-projet dans le groupe
```bash
python3 clone_all_forks.py <token> <project_id> <directory> <until_date>
python pwm repo [-h] [-n NAME] [-i IMPORT_URL] [-x EXPIRES_AT] GROUP_ID EMAILS
```
Clone tous les forks d'un projet `project_id` donné dans un répertoire nommé `directory`. Si une date `until_date` (au format `AAAA-MM-DD hh:mm`) est donnée, exécute un `git checkout` sur le premier commit précédant cette date. Affiche sur la sortie standard les membres du groupe (avec un droit d'accès supérieur à *Reporter*), l'url web du repo et dans quel sous-répertoire se trouvent les fichiers.
Crée un dépôt git (projet) au sein d'un groupe à partir de l'URL d'un projet existant pour une liste d'emails d'étudiants. Détail des arguments :
- `NAME` : optionnel, le nom du nouveau repository à créer pour le ou les étudiants concernés. Si non renseigné, prend la première partie du premier email dans `EMAILS`.
- `IMPORT_URL` : optionnel, l'URL (http) du projet (repository) existant. Ce projet doit être public.
- `EXPIRES_AT`: optionnel, au format `YYYY-MM-DD`, supprime les étudiants ajoutés à la date donnée (ils ne peuvent plus `push`).
- `GROUP_ID` : l'identifiant du groupe dédié au cours/TP, créé précédemment.
- `EMAILS` : une liste d'emails des étudiants. Les emails sont séparés par une virgule. Peut contenir un seul email.
## "Convenient" script
### Clone de tous les repositories
```bash
./create_group_and_repos.sh <token> <group_name> <import_url> <repos_students>
python pwm clone [-h] [-g | -f] [-u UNTIL_DATE] ID DIRECTORY
```
Un script bash est également disponible, `create_group_and_repos.sh` qui permet de "batcher" les opérations de création de groupe et de repositories à partir d'un fichier texte `repos_students` formaté ainsi :
Clone tous les repositories d'un groupe (`-g`) ou tous les forks d'un projet (`-f`) selon l'id (`ID`) donné dans un répertoire nommé `DIRECTORY`. Si une date `UNTIL_DATE` (au format `YYYY-MM-DD hh:mm`) est donnée, exécute un `git checkout` sur le premier commit précédant cette date. Affiche sur la sortie standard les membres du groupe (avec un droit d'accès supérieur à *Reporter*), l'url web du repo et dans quel sous-répertoire se trouvent les fichiers.
## Syntaxe du fichier YAML (REPOS_FILE)
Le fichier YAML doit respecter une des deux syntaxes suivantes.
### Noms et emails
Pour chaque projet créé, un nom et une liste d'emails doivent être renseignés :
```yaml
- name: group1
emails:
- prenom.nom11@hesge.ch
- prenom.nom12@hesge.ch
- name: group2
emails:
- prenom.nom21@hesge.ch
- prenom.nom22@hesge.ch
- name: group3
emails:
- prenom.nom31@hesge.ch
- prenom.nom32@hesge.ch
```
repository1;email1,email2
repository2;email3,email4
### Emails seulement
Si uniquement les emails sont fournis, prend le premier nom de chaque email pour nom de projet :
```yaml
- emails:
- prenom.nom11@hesge.ch # project_name = prenom.nom11
- prenom.nom12@hesge.ch
- emails:
- prenom.nom21@hesge.ch # project_name = prenom.nom21
- prenom.nom22@hesge.ch
- emails:
- prenom.nom31@hesge.ch # project_name = prenom.nom31
- prenom.nom32@hesge.ch
```
#!/usr/bin/env bash
if [[ $# != 4 ]]; then
echo "Usage: $0 <token> <group_name> <import_url> <repos_students>"
exit 1
fi
token=$1
group_name=$2
import_url=$3
repos_students=$4
group=$(scripts/create_group.py $token $group_name)
group_id=$(echo $group | cut -d';' -f2)
printf "$group\n\n"
for line in $(cat $repos_students); do
project_name=$(echo $line | cut -d';' -f1)
students=$(echo $line | cut -d';' -f2)
new_repo=$(scripts/create_repo_for_students.py $token $import_url $group_id "$project_name" $students)
printf "$new_repo\n\n"
done
doc/project_id.png

8.18 KiB

project_id.png

10.6 KiB

......@@ -248,7 +248,7 @@ if __name__ == '__main__':
parser_group_repos.add_argument(
"group_name", metavar="GROUP_NAME", help="The group name.")
parser_group_repos.add_argument(
"repos_file", metavar="REPOS_FILE", help="A file with projects names and students emails.")
"repos_file", metavar="REPOS_FILE", help="YAML file with projects names and/or students emails.")
parser_group_repos.add_argument(
"--visibility", help="Group visibility. By default private.")
parser_group_repos.add_argument("-i", "--import_url",
......
#!/usr/bin/env python3
import os
import requests
import subprocess
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-g", "--group", action="store_true", help="Clone repositories from a group (with group_id) or forks of a project (with project_id) (default behavior).")
group.add_argument("-f", "--forks", action="store_true", help="Clone forks of a project (with project_id).")
parser.add_argument(
"token", metavar="TOKEN", help="Create a token here: https://gitedu.hesge.ch/profile/personal_access_tokens")
parser.add_argument(
"id", metavar="ID", help="The group_id (int) of the projects or the project_id (int) of the forks.")
parser.add_argument(
"directory", metavar="DIRECTORY", help="Local directory where clone all repositories.")
parser.add_argument(
"-u", "--until_date", help="Do a git checkout for all repositories at given date, format \"YYYY-MM-DD hh:mm\" (optional).")
args = parser.parse_args()
try:
os.mkdir(args.directory)
except OSError:
print("Creation of the directory '%s' failed, exit\n" % args.directory)
exit(1)
base_url = 'https://gitedu.hesge.ch/api/v4/'
params = {'simple': 'true', 'per_page': 100}
headers = {'PRIVATE-TOKEN': args.token}
if args.forks:
url = base_url + 'projects/' + args.id + '/forks'
else:
url = base_url + 'groups/' + args.id + '/projects'
repositories = requests.get(url, params=params, headers=headers).json()
if 'message' in repositories:
print('Error retrieving repositories: ' + repositories['message'])
exit(1)
for repo in repositories:
repo_url = base_url + '/projects/' + str(repo['id']) + '/members'
members = requests.get(repo_url, headers=headers).json()
if 'message' in members:
print('Error retrieving members: ' + members['message'])
exit(1)
ssh_url_to_repo = repo['ssh_url_to_repo']
web_url = repo['web_url']
members_names = ''
for member in members:
if member['access_level'] > 20: # Access level greater than "Reporter"
members_names += member['username'] + ', '
if args.forks:
repo_local_name = repo['namespace']['path']
else:
repo_local_name = repo['path']
print('Members: ' + members_names)
print('Web url: ' + web_url)
print('Cloning in "' + args.directory + '/' + repo_local_name + '"')
subprocess.run(["git", "clone", "-q", ssh_url_to_repo,
args.directory + '/' + repo_local_name])
if args.until_date:
commit_id = subprocess.check_output([
"git", "rev-list", "-n", "1", "--before=\"" + args.until_date + "\"",
"master"], cwd=args.directory + '/' + repo_local_name).decode('utf-8').rstrip()
subprocess.run(
["git", "checkout", "-q", str(commit_id)],
cwd=args.directory + '/' + repo_local_name)
print("Checkout at " + str(commit_id) + "\n")
else:
print()
#!/usr/bin/env python3
import argparse
import requests
parser = argparse.ArgumentParser()
parser.add_argument(
"token", metavar="TOKEN", help="Create a token here: https://gitedu.hesge.ch/profile/personal_access_tokens")
parser.add_argument(
"group_name", metavar="GROUP_NAME", help="The group name.")
parser.add_argument(
"--visibility", help="Group visibility. By default private.")
args = parser.parse_args()
if args.visibility:
visibility = args.visibility
else:
visibility = 'private'
base_url = 'https://gitedu.hesge.ch/api/v4/'
params = {'path': args.group_name,
'name': args.group_name, 'visibility': visibility}
headers = {'PRIVATE-TOKEN': args.token}
group = requests.post(base_url + '/groups',
params=params, headers=headers).json()
if 'message' in group:
print('Error in creating group: %s' % group)
exit(1)
print("Group '" + group['name'] + "' with id '" + str(group['id']) + "' and visibility '" +
group['visibility'] + "' available at '" + group['web_url'] + "' ;" + str(group['id']))
#!/usr/bin/env python3
import argparse
import requests
parser = argparse.ArgumentParser()
parser.add_argument(
"token", metavar="TOKEN", help="Create a token here: https://gitedu.hesge.ch/profile/personal_access_tokens")
parser.add_argument(
"group_id", metavar="GROUP_ID", help="The group id (int) where to store the created new project.")
parser.add_argument(
"emails", metavar="EMAILS", help="Emails list of students working in this project, separated by commas (email1,email2).")
parser.add_argument(
"-n", "--name", help="The project name. If blank, take the first student name (from email) as name.")
parser.add_argument("-i", "--import_url",
help="Import the publicly accessible project by URL given here (optional).")
parser.add_argument("-x", "--expires_at",
help="Expiration date to kick off students from this project, at 00:00:00. YYYY-MM-DD format (optional).")
args = parser.parse_args()
base_url = 'https://gitedu.hesge.ch/api/v4'
headers = {'PRIVATE-TOKEN': args.token}
# split '@' in the case when project name = student's email
if args.name:
name = args.name
else:
name = args.emails.split('@')[0]
# Get students ids from their emails
users_emails = args.emails.split(',')
user_ids = []
for email in users_emails:
user_requested = requests.get(
base_url + '/users', params={'search': email}, headers=headers).json()
if len(user_requested) == 0:
print('No user %s found, operation aborted' % email)
exit(1)
user_id = user_requested[0]['id']
user_ids.append(user_id)
# Create project from name, import_url (if given) and group_id
params = {'name': name, 'namespace_id': args.group_id, 'visibility': 'private'}
if args.import_url:
params['import_url'] = args.import_url
project = requests.post(base_url + '/projects',
params=params, headers=headers).json()
if 'message' in project:
print('Error in creating project: %s' % project)
exit(1)
print("Project '" + project['name'] + "' at '" +
project['web_url'] + "' created")
# Allow users with developer access level to push and merge on master
access_level = 30
params = {'name': 'master', 'push_access_level': str(
access_level), 'merge_access_level': str(access_level)}
requests.post(base_url + '/projects/' +
str(project['id']) + '/protected_branches', params=params, headers=headers).json()
# Add each student as project's developer (level 30)
for user_id in user_ids:
params = {'user_id': user_id, 'access_level': access_level}
if args.expires_at:
params['expires_at'] = args.expires_at
new_user = requests.post(base_url + '/projects/' + str(
project['id']) + '/members', params=params, headers=headers).json()
if 'message' in new_user:
print('Error in adding user: %s' % new_user)
else:
out = ("Adding '" + new_user['name'] + "' (" + new_user['username'] + ") in '"
+ project['name'] + "' with access level: " + str(new_user['access_level']))
if args.expires_at:
out += ", expires at: " + new_user['expires_at']
print(out)
# Do not forget : students have to add second remote in their local repositories for pulling last changes.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment