From aff9af832df426bdf1316715a0189b15b08829ad Mon Sep 17 00:00:00 2001 From: "steven.liatti" <steven@liatti.ch> Date: Wed, 11 Nov 2020 19:55:31 +0100 Subject: [PATCH] Add list functionality --- README.md | 21 ++++++++- pwm | 128 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 15 deletions(-) mode change 100755 => 100644 pwm diff --git a/README.md b/README.md index 54f9fd7..2a508c5 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,18 @@ Programme python pour gérer les travaux pratiques des étudiants avec la contra ## TL;DR -- Créer un groupe et les repositories en une seule commande (avec un repository "image" optionnel) : `python 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 : `python pwm -t TOKEN clone ID DIRECTORY` +- Créer un groupe et les repositories en une seule commande, avec un repository "image" optionnel et un fichier d'emails (voir [Syntaxe du fichier YAML (REPOS_FILE)](#syntaxe-du-fichier-yaml-repos_file) : + ```bash + python pwm -t TOKEN group_repos GROUP_NAME REPOS_FILE [-i IMPORT_URL] + ``` +- Clone tous les projets des étudiants d'un groupe gitlab (`ID`) dans un répertoire créé à la volée : + ```bash + python pwm -t TOKEN clone ID DIRECTORY + ``` +- Liste les noms des projets dans un groupe : + ```bash + python pwm list ID + ``` - [Practical Work Manager (pwm)](#practical-work-manager-pwm) - [TL;DR](#tldr) @@ -15,6 +25,7 @@ Programme python pour gérer les travaux pratiques des étudiants avec la contra - [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) + - [Liste des projets d'un groupe ou des membres d'un projet](#liste-des-projets-dun-groupe-ou-des-membres-dun-projet) - [Syntaxe du fichier YAML (REPOS_FILE)](#syntaxe-du-fichier-yaml-repos_file) - [Noms et emails](#noms-et-emails) - [Emails seulement](#emails-seulement) @@ -89,6 +100,12 @@ python pwm clone [-h] [-g | -f] [-u UNTIL_DATE] ID DIRECTORY ``` 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. +### Liste des projets d'un groupe ou des membres d'un projet +```bash +python pwm list [-h] [-p | -m] [-s SHOW] ID +``` +Liste les projets d'un groupe ou les membres d'un projet selon l'id `ID`. L'argument `--show` (ou `-s`) permet de choisir quelles informations à afficher (par défaut le nom) : `[all | name | url | ssh]`. + ## Syntaxe du fichier YAML (REPOS_FILE) Le fichier YAML doit respecter une des deux syntaxes suivantes. diff --git a/pwm b/pwm old mode 100755 new mode 100644 index 9f85c32..523c97c --- a/pwm +++ b/pwm @@ -8,11 +8,13 @@ Steven Liatti from argparse import Namespace import os -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional import requests import subprocess import argparse +from requests.models import Response import yaml +import json BASE_URL: str = 'https://gitedu.hesge.ch/api/v4' @@ -112,6 +114,59 @@ def create_repository(token: str, group_id: str, emails: List[str], name: str, i print(out) +def paginate_responses(url: str, headers: Dict[str, str], params: Dict[str, Any]) -> List[Response]: + """ + Manage gitlab pagination, max 100 results by request + """ + responses = [requests.get(url, params=params, headers=headers)] + last_response = responses[len(responses) - 1] + + while last_response.status_code == 200 and len(last_response.headers['X-Next-Page']) != 0: + next_page = last_response.headers['X-Next-Page'] + params['page'] = next_page + responses.append(requests.get(url, params=params, headers=headers)) + last_response = responses[len(responses) - 1] + + return responses + + +def get_members(token: str, id: str) -> List: + """ + Return members list from given id + """ + url = BASE_URL + '/projects/' + id + '/members' + + headers = {'PRIVATE-TOKEN': token} + params = {'simple': 'true', 'order_by': 'name', + 'sort': 'asc', 'per_page': 100} + responses = paginate_responses(url, headers, params) + + members = [] + for r in responses: + members += r.json() + return members + + +def get_projects(token: str, id: str, source: str = 'group') -> List: + """ + Return projects list from given id and source ('group' or 'forks') + """ + if source == 'forks': + url = BASE_URL + '/projects/' + id + '/forks' + else: + url = BASE_URL + '/groups/' + id + '/projects' + + headers = {'PRIVATE-TOKEN': token} + params = {'simple': 'true', 'order_by': 'name', + 'sort': 'asc', 'per_page': 100} + responses = paginate_responses(url, headers, params) + + projects = [] + for r in responses: + projects += r.json() + return projects + + def clone_all(token: str, id: str, directory: str, until_date: Optional[str], source: str = 'group'): """ Clone all repositories (from a group or "forks of") from id (group or @@ -123,18 +178,8 @@ def clone_all(token: str, id: str, directory: str, until_date: Optional[str], so print("Creation of the directory '%s' failed, exit\n" % directory) exit(1) - params = {'simple': 'true', 'per_page': 100} headers = {'PRIVATE-TOKEN': token} - - if source == 'forks': - url = BASE_URL + '/projects/' + id + '/forks' - else: - url = BASE_URL + '/groups/' + id + '/projects' - - repositories = requests.get(url, params=params, headers=headers).json() - if 'message' in repositories: - print('Error retrieving repositories: ' + repositories['message']) - exit(1) + repositories = get_projects(token, id, source) for repo in repositories: repo_url = BASE_URL + '/projects/' + str(repo['id']) + '/members' @@ -234,6 +279,50 @@ def command_clone_all(args): clone_all(args.token, args.id, args.directory, args.until_date) +def command_list(args): + """ + Call get_projects or get_members + """ + if args.members: + members = get_members(args.token, args.id) + if args.show: + if args.show == 'all': + print(json.dumps(members, indent=2)) + elif args.show == 'url': + results = list(map(lambda p: p['web_url'], members)) + for r in results: + print(r) + else: + names = list(map(lambda p: p['username'], members)) + for name in names: + print(name) + else: + names = list(map(lambda p: p['username'], members)) + for name in names: + print(name) + else: + projects = get_projects(args.token, args.id) + if args.show: + if args.show == 'all': + print(json.dumps(projects, indent=2)) + elif args.show == 'url': + results = list(map(lambda p: p['http_url_to_repo'], projects)) + for r in results: + print(r) + elif args.show == 'ssh': + results = list(map(lambda p: p['ssh_url_to_repo'], projects)) + for r in results: + print(r) + else: + names = list(map(lambda p: p['name'], projects)) + for name in names: + print(name) + else: + names = list(map(lambda p: p['name'], projects)) + for name in names: + print(name) + + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Practical Work Manager - \ Manage students PW - Create group, projects or clone repositories') @@ -241,7 +330,7 @@ if __name__ == '__main__': parser.add_argument("-t", "--token", metavar="TOKEN", help="Create a token here: https://gitedu.hesge.ch/profile/personal_access_tokens") subparsers = parser.add_subparsers( - metavar='(group_repos | group | repo | clone)') + metavar='(group_repos | group | repo | clone | list)') parser_group_repos = subparsers.add_parser( 'group_repos', help='Create group and repos associated') @@ -292,6 +381,19 @@ if __name__ == '__main__': "-u", "--until_date", help="Do a git checkout for all repositories at given date, format \"YYYY-MM-DD hh:mm\" (optional).") parser_clone.set_defaults(func=command_clone_all) + parser_list = subparsers.add_parser( + 'list', help="List group's projects or project's members") + group_list = parser_list.add_mutually_exclusive_group() + group_list.add_argument( + "-p", "--projects", action="store_true", help="List group's projects (default") + group_list.add_argument( + "-m", "--members", action="store_true", help="List project's members") + parser_list.add_argument( + "id", metavar="ID", help="The group_id or the project_id (int).") + parser_list.add_argument( + "-s", "--show", help="Amount of informations (default name) : [all | name | url | ssh]") + parser_list.set_defaults(func=command_list) + args = parser.parse_args() if not args.token: -- GitLab