Je vous présente notre projet gitlab-to-github et surtout je vous explique comment je l’ai construit.
Vous trouverez le code du projet ICI.
A quoi sert le projet ?
Tout simplement pour envoyer tous ses projets gitlab sur son compte Github.
Pourquoi ?
Si comme moi tout le monde te demande ton compte Github pour voir tes projets mais tous tes repos sont sur ton gitlab 😢
Comment concevoir le plan d’action
De quoi aura-t-on besoin ?
- 1 compte Gitlab avec des projets
- 1 compte Github
- Python3
- Pip
Comment le programme fonctionne ?
Pour push tous mes projets Gitlab vers github, mon script va devoir suivre ces étapes :
- Récupérer mes repositories gitlab
- Pour chaque repos gitlab :
- via l’API de Github créer un projet avec le même nom que le repo gitlab
- cloner le repo gitlab dans un dossier temporaire puis le push vers le nouveau projet github
Bonus : Si la variable import_private_repo est True alors on import aussi les projets privés
Requête API
Le programme est basé principalement sur des requêtes API.
Pour avoir le droit de lancer des requête API sur nos comptes il faut alors qu’on crée une clé API pour chaque compte avec les scopes (périmètres) qui vont bien.
Création clé API Gitlab
Une fois connecté sur ton compte gitlab, aller sur https://gitlab.com/-/profile/personal_access_tokens
Comme sur l’impression d’écran ci-dessus :
- Donnes un nom à ton token
- Une date d’expiration (pas obligé)
- coches read_api
- Clique sur Create personnal access token
En retour Gitlab te fournit la clé de ton Token, garde le de côté on en aura besoin très bientôt.
Création clé API Github
Une fois connecté à Github aller sur https://github.com/settings/tokens.
Tu arriveras ici :
Cliques sur Generate new token.
Tu seras dirigé vers la page suivante :
- Dans Note tu peux donner une description au token
- Coche repo et project
- Generate Token
La clé est alors généré, garde la de côté.
Conception du projet
Composition
- fonctions.py : contient les différentes fonctions
- main.py : le script principal qui va exécuter les actions en appelant les fonctions.py
- config.py : contient les différentes variables nécessaires au programme.
- requirements.txt : les librairies python requises (installer via la commande : pip install -r requirements.txt)
- run_import.sh : petit script pour initialiser facilement les variables et lancer le programme python, idéal pour les débutants.
Variables
token_gitlab
: la clé API de ton compte Gitlabtoken_github
: pareil pour Githubgitlab_username
: ton login gitlabgithub_username
: ton login githublimit_repo
: le nombre limite de projets à importerimport_private_repo
: True ou False si il faut importer aussi les repositories privés.
Cacher mes petits secrets
Durant ma phase de test je stockais les tokens dans les fichiers .token_gitlab
et .token_github
.
Je les chargeais dans config.py comme ça :
token_file = open(".token_gitlab")
token_gitlab = token_file.read()
token_file = open(".token_github")
token_github = token_file.read()
Puis une fois la phase de dev terminé, j’ai modifié le fichier config.py pour récupérer mes variables sensible via des variables d’environnements.
import os
try:
token_file = open(".token_gitlab")
token_gitlab = token_file.read()
except:
if os.environ.get('token_gitlab') is not None:
token_gitlab = os.environ.get('token_gitlab')
else:
print(".Token_gitlab not found")
raise SystemExit
En gros ce bout de code vérifie si le fichier .token_gitlab est présent, sinon si la variable d’environnement token_gitlab existe il le charge dans ma variable token_gitlab.
Et si ni le fichier .token_gitlab ni la variable d’env token_gitlab ne sont trouvés alors le programme s’arrête.
Mes amis les fonctions
Le fichier fonctions.py entier :
from config import token_gitlab, token_github, gitlab_url, github_url, github_username, gitlab_username
import requests
from requests.auth import HTTPBasicAuth
import unidecode
import tempfile
from git import Repo
def get_repos_gitlab():
header = {"PRIVATE-TOKEN": token_gitlab}
r = requests.get(gitlab_url,headers=header)
return r.json()
def get_repos_public_gitlab():
projects = get_repos_gitlab()
repo_public = list()
for project in projects:
if project['visibility'] == "public":
repo_public.append(project)
return repo_public
def get_repos_private_gitlab():
projects = get_repos_gitlab()
repo_private = list()
for project in projects:
if project['visibility'] == "private":
repo_private.append(project)
return repo_private
def get_repos_github():
request = requests.get(github_url,auth = HTTPBasicAuth(github_username, token_github)).json()
return request
def create_repo_github(nom_repo):
request = requests.post(f"https://api.github.com/user/repos",auth = HTTPBasicAuth(github_username, token_github),json={"name":unidecode.unidecode(nom_repo)}).json()
return request
def create_private_repo_github(nom_repo):
request = requests.post(f"https://api.github.com/user/repos",auth = HTTPBasicAuth(github_username, token_github),json={"name":unidecode.unidecode(nom_repo),"private":"True"}).json()
return request
def delete_repo_github(nom_repo):#not used
delete_repo = requests.delete(f"https://api.github.com/repos/babidi34/{nom_repo}",auth = HTTPBasicAuth(github_username, token_github)).json()
def cloneGitlab_and_pushGithub(repo):
with tempfile.TemporaryDirectory() as tmpdirname:
print('created temporary directory', tmpdirname)
if repo['visibility'] == 'private':
cloned_repo = Repo.clone_from(f"https://{gitlab_username}:{token_gitlab}@gitlab.com/{gitlab_username}/{unidecode.unidecode(repo['path'])}.git", tmpdirname)
else:
cloned_repo = Repo.clone_from(repo['web_url'], tmpdirname)
remote = cloned_repo.create_remote("new", url=f"https://{github_username}:{token_github}@github.com/{github_username}/{unidecode.unidecode(repo['path'])}.git")
remote.push(refspec='{}:{}'.format(repo['default_branch'], repo['default_branch']))
On va déchiffrer un peu tout ça :
get_repos_gitlab()
Retourne tout les repositories de ton Gitlab.
def get_repos_gitlab():
header = {"PRIVATE-TOKEN": token_gitlab}
r = requests.get(gitlab_url,headers=header)
return r.json()
get_repos_public_gitlab()
Retourne les repos public gitlab.
def get_repos_public_gitlab():
projects = get_repos_gitlab()
repo_public = list()
for project in projects:
if project['visibility'] == "public":
repo_public.append(project)
return repo_public
get_repos_private_gitlab()
Retourne les repos privées gitlab.
def get_repos_private_gitlab():
projects = get_repos_gitlab()
repo_private = list()
for project in projects:
if project['visibility'] == "private":
repo_private.append(project)
return repo_private
get_repos_github()
Récupère les repos github.
def get_repos_github():
request = requests.get(github_url,auth = HTTPBasicAuth(github_username, token_github)).json()
return request
create_repo_github(nom_repo)
Créer un repo public sur son compte Github.
def create_repo_github(nom_repo):
request = requests.post(f"https://api.github.com/user/repos",auth = HTTPBasicAuth(github_username, token_github),json={"name":unidecode.unidecode(nom_repo)}).json()
return request
create_private_repo_github(nom_repo)
Créer un repo privé sur son Github.
def create_private_repo_github(nom_repo):
request = requests.post(f"https://api.github.com/user/repos",auth = HTTPBasicAuth(github_username, token_github),json={"name":unidecode.unidecode(nom_repo),"private":"True"}).json()
return request
delete_repo_github(nom_repo)
Supprime un repo github.
def delete_repo_github(nom_repo):#not used
delete_repo = requests.delete(f"https://api.github.com/repos/babidi34/{nom_repo}",auth = HTTPBasicAuth(github_username, token_github)).json()
cloneGitlab_and_pushGithub(repo)
Cette fonction prend en input un objet repo, un JSON et va le push sur le compte Github.
Pour ce faire la fonction suit 3 étapes :
- Création d’un dossier temporaire
- git clone du repo dans notre dossier temporaire
- On ajoute une nouvelle URL vers son github au git remote
- git push
def cloneGitlab_and_pushGithub(repo):
with tempfile.TemporaryDirectory() as tmpdirname:
print('created temporary directory', tmpdirname)
if repo['visibility'] == 'private':
cloned_repo = Repo.clone_from(f"https://{gitlab_username}:{token_gitlab}@gitlab.com/{gitlab_username}/{unidecode.unidecode(repo['path'])}.git", tmpdirname)
else:
cloned_repo = Repo.clone_from(repo['web_url'], tmpdirname)
remote = cloned_repo.create_remote("new", url=f"https://{github_username}:{token_github}@github.com/{github_username}/{unidecode.unidecode(repo['path'])}.git")
remote.push(refspec='{}:{}'.format(repo['default_branch'], repo['default_branch']))
Explication du fonctionnement de la fonction cloneGitlab_and_pushGithub(repo) :
- Création du tempdir, tout ce qui sera dans le with ci-dessous se fera durant l’existence du dossier temporaire :
with tempfile.TemporaryDirectory() as tmpdirname:
- git clone le repo dans le dossier temporaire (si le repo est privé on utilise notre token pour le cloner et si il est public on utilise un simple git clone)
cloned_repo = Repo.clone_from(f"https://{gitlab_username}:{token_gitlab}@gitlab.com/{gitlab_username}/{unidecode.unidecode(repo['path'])}.git", tmpdirname)
- on spécifie dans le git remote l’URL du repo que l’on à crée précédemment sur Github
remote = cloned_repo.create_remote("new", url=f"https://{github_username}:{token_github}@github.com/{github_username}/{unidecode.unidecode(repo['path'])}.git")
- Enfin pour faire un git push (sur le même nom de branche que notre repo Gitlab):
remote.push(refspec='{}:{}'.format(repo['default_branch'], repo['default_branch']))
Toutes les fonctions ne sont pas forcément utilisées.
Certaines ont été crées pour faciliter les tests durant la phase de test comme la fonction delete_repo_github(nom_repo)
.
Décollage
C’est le fichier main.py qui pilote tout ça.
main.py :
import fonctions, config
public_repo_gitlab = fonctions.get_repos_public_gitlab()
private_repo_gitlab = fonctions.get_repos_private_gitlab()
for repo in public_repo_gitlab:
fonctions.create_repo_github(repo['name'])
fonctions.cloneGitlab_and_pushGithub(repo)
if config.import_private_repo:
for repo in private_repo_gitlab:
fonctions.create_private_repo_github(repo['name'])
fonctions.cloneGitlab_and_pushGithub(repo)
- Il récupère dans des variables les repos gitlab public et privés
- Pour chaque repositories public il crée un repo du même nom sur Github et lance l’importation du repo de gitlab vers github
- Si la variable
import_private_repo
est True alors il fait pareil pour les projets privées
Pipeline CI CD
Voici mon fichier .gitlab-ci pour créer une pipeline CI CD :
image: python:latest
stages:
- push_to_github
before_script:
- pip install -r requirements.txt
push_to_github:
stage: push_to_github
script: python main.py
rules:
- if: '$token_gitlab'
- if: '$token_github'
- if: '$gitlab_username'
On utilise ici une image docker Python3.
Avant de lancer le stage principal pip installe toutes les librairies requises.
Et j’ai fait en sorte que la pipeline ne soit joué que si les variables $token_gitlab, $token_github et $gitlab_username existe :
rules:
- if: '$token_gitlab'
- if: '$token_github'
- if: '$gitlab_username'
Pipeline planifié
De plus pour que mes comtes Gitlab et Github soient assez souvent synchronisé j’ai créer un scheduler pour jouer la pipeline tous les vendredis à 20h.
Pour ce faire :
- Dans le projet gitlab, dans la partie CI/CD / Schedules
- New schedule
- J’ai donné un nom à la tâche planifié
- une fréquence
- j’ai ajouter les variables necessaire à la pipeline :
- gitlab_username
- import_private_repo
- token_github
- token_gitlab
Une fois le scheduler sauvegardé, la pipeline se déclenchera automatiquement tous les vendredi à 20h.
Suggestion ?
Si tu as des idées pour améliorer le code ou pour arriver au même objectif différemment n’hésite pas à donner des tips en commentaires plus bas pour en faire profiter à la communauté. ♥️
Besoin d’aide ?
Tu galères sur un script, une pipeline ou autres ? Contactes nous ICI ou poses ta question en commentaire.
Le partage nous aide à nous faire connaître, merci pour ton partage 🫀