Gitlab vers Github – Importer ses projets Gitlab dans Github

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.

gitlab github api
api gitlab

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 :

  1. Récupérer mes repositories gitlab
  2. Pour chaque repos gitlab :
    1. via l’API de Github créer un projet avec le même nom que le repo gitlab
    2. 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

gitlab_ci api token git

Comme sur l’impression d’écran ci-dessus :

  1. Donnes un nom à ton token
  2. Une date d’expiration (pas obligé)
  3. coches read_api
  4. 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 :

github api git personal access tokens

Cliques sur Generate new token.

Tu seras dirigé vers la page suivante :

github create token api
  1. Dans Note tu peux donner une description au token
  2. Coche repo et project
  3. 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 Gitlab
  • token_github : pareil pour Github
  • gitlab_username : ton login gitlab
  • github_username : ton login github
  • limit_repo : le nombre limite de projets à importer
  • import_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 :

  1. Création d’un dossier temporaire
  2. git clone du repo dans notre dossier temporaire
  3. On ajoute une nouvelle URL vers son github au git remote
  4. 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)
  1. Il récupère dans des variables les repos gitlab public et privés
  2. 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
  3. 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 :

  1. Dans le projet gitlab, dans la partie CI/CD / Schedules
  2. New schedule
gitlab ci cd pipeline 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 🫀

Laisser un commentaire