Hello ici, je n’ai pas trouvé de titre rigolo cette fois-ci, mais on part sur un article efficace. Je te balance en vrac les trucs que j’ai pu expérimenter avec gitlab, y’aura à boire et à manger comme d’hab. Des trucs que j’ai faits chez des clients (mais j’ai réécrit tout le code, ce n’est pas du vol, ne pas poursuivre) et des idées qui ont germé de mon cerveau malade.

On va s’enjailler en tabarnak, Tequila, Heineken, pas le temps de niaser !

meme, tequila Heineken, pas le temps de niaser

backup sécurisé des variables gitlab-ci

Un truc que j’ai toujours trouvé un peu frustrant sur Gitlab, c’est le manque de traçabilité des actions manuels. C’est quand même super con de faire des efforts pour versionner du code de l’infra, le code, les pipelines, mais qu’on risque de tout casser en faisant un changement manuel. Par exemple un secret gitlab que tu modifies ou que tu supprimes par erreur et c’est toute ta chaine de build qui peut être cassé.

Pourquoi ne pas profiter des API de Gitlab et du système de versionning de repo pour faire un backup versionné des variables Gitlab ? Mais bien entendu on va chiffrer les secrets avec une clef GPG pour que les variables ne soient pas versionnées en clair dans le repo.

Plus d’info sur GPG

l’idée de la clef GPG c’est d’utiliser un chiffrage fort et standard sur les secrets versionnés dans git pour que si le repo est intercepté par un malandrin, il ne puisse pas te voler tes secrets.

l’idée du chiffrement asymétrique c’est qu’on peux aussi révoquer et ajouter plusieurs clefs si besoin. Je te met ici la littérature sur GPG si tu veux en savoir plus:

https://fr.wikipedia.org/wiki/GNU_Privacy_Guard

Boucle bien ta ceinture, tu n’es pas près pour la prochaine connerie :)

vidéo Air France élégante, boucler vos ceintures et rendez les visibles

Parce que c’est la France, le pays de l’élégance ! et parfois de l’arrogance (dédicace à Bobby)

On va utiliser git-crypt pour chiffrer les secrets, mais pour éviter les confusions franco linguistique des plus français d’entres nous, nous allons renommer cette commande en utilisant des alias.

1
2
alias git-chiffre="git-crypt lock"
alias git-dechiffre="git-crypt unlock"

Pour les plus septiques d’entre vous, je vous renvoie vers ce lien d’une importance capitale pour la francophonie. https://chiffrer.info/

Pour la visibilité des changements, on va générer un fichier en clair avec un md5 des valeurs des clefs, afin de facilement détecter les changements sans avoir besoin de tout le temps déchiffrer le fichier de backup.

On va profiter du python SDK de Gitlab en utilisant un utilisateur robot admin avec un token API. C’est très important d’utiliser un user technique pour des taches admin afin que les trucs que tu as mis en place marche toujours, parce que le jour où tu pars et qu’on désactive ton compte, les collègues seront bien emmerdés à chercher quel token correspond à quel compte.

On va faire un truc un peu comme ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
lister les projets
pour chaque projet 
	lire les variables
	transformer les variables en **base64**
	faire un **hash md5** de la valeur de la variable
	écrire un fichier variable + hash
	déchiffrer le fichier de variable
	écrire un fichier variable + value
	chiffrer le fichier de variable
 	autocommit le changement sur le repo avec un flag no ci pour éviter de faire une boucle de ci/cd

Truc un peu idiot, on va utiliser une clef GPG qui sera dans les secrets Gitlab. (tu devrais en garder une copie dans un vault ailleurs au cas où) On part du principe que si tu peux lire les secrets Gitlab de ce projet, tu as les droits admin pour pouvoir lire toutes les variables. Donc la protection des secrets du repo tient à trois choses:

  • ne pas laisser trainer la clef de déchiffrement sur ton poste
  • restreindre au maximum les accès à ce repo de backup
  • chiffrer le disque dur de ton poste pour éviter de te faire voler tes secrets si on te vole ton poste et que tu n’as pas respecté la condition #1 :)

Laisse quand même un peu de challenge au braqueur pour piquer tes secrets, sinon tu ne fais même pas une saison et tu te fais shooter devant la banque à l’épisode 1 :)

La casa del papel, Denvers, Tokyo, Manille en attente de l’assaut

implémentation

En langue de serpent ça nous donne un truc comme ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env pipenv-shebang
import os, sys
from hashlib import md5
import gitlab
import yaml
def get_env(env):
    getenv = os.getenv(env)
    if getenv:
        print (f"get env {env}")
        return getenv
    else:
        print (f"error, missing env {env}")
        sys.exit(-1)

def write_yaml_file(filename, data_dict):
    print (f"dumping file {filename}")
    with open (filename, "w") as f:
        f.write(yaml.dump(data_dict))

gl = gitlab.Gitlab("https://gitlab.com", private_token=get_env("GITLAB_ADMIN_TOKEN"))
group = gl.groups.get(get_env("GITLAB_ROOT_GROUP_ID"))

print ("filter project names and ids")
projects = group.projects.list(all=True)
projects_filtered = [
    {"id": project.id, "name": project.name} for project in projects if project.id
]
variables = {}
md5_dict = {}

print ("filters variables by projects")
for project in projects_filtered:
    for variable in gl.projects.get(project["id"]).variables.list(all=True):
        if not variables.get(project["name"]):
            variables[project["name"]] = []
            md5_dict[project["name"]] = []
        enc_value = variable.value.encode("utf8")
        variables[project["name"]].append(
            {variable.key: b64encode(enc_value).decode("utf8")}
        )
        md5_dict[project["name"]].append({variable.key: md5(enc_value).hexdigest()})

write_yaml_file("env_dump.yml", variables)
write_yaml_file("md5_dump.yml", md5_dict)

et je vous mets le Pipfile ici

1
2
3
4
5
6
 [[source]]
 url = "https://pypi.org/simple"
 verify_ssl = true
 
 [requires]
 python_version = "3.10"

il ne t’aura pas échappé que le script utilise 2 variables d’environnement:

  • GITLAB_ADMIN_TOKEN l’api token de mon utilisateur admin
  • GITLAB_ROOT_GROUP_ID l’id de ton group dans lequel ce trouve les projets que tu veux backup

/!\ pense bien à initialiser correctement git-crypt avant de commit le fichier sensible.

Un petit article de Korben ici qui explique sans ambiguïté comment faire.

https://korben.info/git-crypt-du-chiffrement-transparent-git.html

Et avec le fichier .gitattributes qui va bien

1
env_dump.yml filter=git-crypt diff=git-crypt

/!\ si tu as le moindre doute sur le fait que tu as commité au moins une fois le fichier en clair supprime complètement ton repo et repart d’un historique clean :)

cross over sncf serge le lapin et lazy town you are a pirate, ne commit pas tes secrets en clair, tu risque de te faire pirater très fort

Une fois chiffrée on devrait avoir une belle bouillie comme celle-ci

1
2
3
4
5
^@GITCRYPT^@GAtöªîÊÛ·£@x»àMgI¥á86Î+Æ#Ìõ °Ó_TwVüRèØ/"Ç@®ýM®âD#!Hû['m^VÂgÇ^[µBO2'd"¸ØH<99
Îñâ^Ô^KÃ^FUû@`<9c>ÍvW<88>¦¬ÿµT"<8a>δ^Ka<83>̧Læ5lV^^Çí7    
  ·^V²<80>ul%J><90><8f><90>1<92>á*;Ò<8d>^H^U<94>+7<92>
<8b>Þ<8f>wÑ ù^T±7H¯<97>RGx»Z<92>¨^W^Z<9c><94>»OñÂÛ¬v÷¸dôd½^ZÐÓ^[^S<8b>á<9c>^Yö8V<86>±<81>^W¶<9a>Ö_W !aâËwþn<9d>^_
[...]

le fichier de clef md5 ressemblera à ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
backup-variables-gitlabs:
- CLEF_GIT_CHIFFRE: bc0bc06b6b898b6adc7622d22c522ede
- GITLAB_ROOT_GROUP_ID: d704dc815619e38d70d592d8c5147591
- GITLAB_ADMIN_TOKEN: a390581175b688900005055fbb089b80
- GITLAB_AUTH_STRING: a088d6834b7349cadc62e8f635562776
gitlab-runner-training:
- CACA: ff8e6123d1b6655283c51c0f8ce07110
- DOCKER_IMAGE: aa01c5a51ad55ff0684f2bb63279b4e9
kubernetes-runner:
- USELESS_VAR_BUT_NOBODIES_KNOW: 6f077d5c5d0a25feff082d54cd761d81

Un dict par projet, un md5 de la valeur de chacune des clefs S’il y a une nouvelle clef ou un changement de valeur, il vous suffira de comparer les versions du fichier md5 pour savoir tout de suite ce qui a bougé.

Et si vraiment une valeur est corrompue ou perdue, il te faudra déchiffrer le fichier et décoder la valeur base64 de la clef. Oui c’est un peu relou, mais au moins tu sauves ton cul et ta prod !

C’est pas mortel ça ?

Yvain et Gauvain de kaamelott tape dans la main

maintenant on va faire le fichier de ci/cd pour automatiser les backups :)

Voici le yaml des familles avec quelques astuces de sioux.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
image: python:3

gitlab_env_vars_backup:
  script:
    - pip3 install pipenv pipenv-shebang
    - pipenv install
    - apt update && apt install -y git-crypt git
    - echo $CLEF_GIT_CHIFFRE|base64 -d > ./gpg_key
    - shopt -s expand_aliases # expand alias on non interactive
    - alias git-dechiffre="git-crypt unlock"
    - git-dechiffre ./gpg_key
    - ./backup-gitlab-envs.py
    - git config --global user.name "Gitlab bot"
    - git config --global user.email "[email protected]"
    - git add --all
    - git commit -m "[SKIP CI] autocommit backup file"||true
    - export authenticated_url=$(echo $CI_REPOSITORY_URL|sed "s,https://.*@,https://${GITLAB_AUTH_STRING},g")
    - git push "$authenticated_url" "HEAD:${CI_COMMIT_REF_NAME}" -o skip-ci||true 

Rien de magique:

  • j’installe les libs python et j’utilise python-shebang dans mon script pour utiliser le virtualenv de pipenv
  • on déchiffre notre fichier d’envs
  • on lance notre script python
  • on set un nom et un email pour notre robot
  • on add all et on en fait un commit
    • on remarque le [SKIP CI] dans le message de commit
  • on remplace l’utilisateur par défaut de la ci/cd pour utiliser notre token privilégié
    • GITLAB_AUTH_STRING contient en fait une fstring du type “nom de mon token api” + “:” + “nom token gitlab” +"@"
  • et on fait un git push qui gagne à tous les coups pour éviter de prendre une erreur Gitlab si n’y’a rien à commit

Bon maintenant on va mettre un cron Gitlab en place pour scheduler le bouzin, je fais ça manuellement. Si vous avez compris ma démarche, vous connaissez déjà la prochaine étape.

/!\ si tu utilises les environnements Gitlab et que vous avez plusieurs fois la même clef sur plusieurs envs, il faudra adapter le code pour en tenir compte. Mais je pense que si tu es ici, tu parles un peu de fourchelang, donc rien d’insurmontable !

bear grill , improvise, adapt, overcome

backup des crons

On vient de mettre en place un cron Gitlab, mais ça serait vachement bien d’avoir en 1 seul coup d’œil tous les crons actifs de tous les projets et versionner tout ça un peu de la même façon.

Je fais fourcher ma langue pour vous faire ça !

2022 on fait du recyclage, on prend la même en adaptant un peu :)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env pipenv-shebang
import os,sys
from base64 import b64encode
from hashlib import md5
import gitlab
import yaml


def get_env(env):
    getenv = os.getenv(env)
    if getenv:
        print(f"get env {env}")
        return getenv
    else:
        print (f"error, missing env {env}")
        sys.exit(-1)


def write_yaml_file(filename, data_dict):
    print(f"dumping file {filename}")
    with open(filename, "w") as f:
        f.write(yaml.dump(data_dict))


gl = gitlab.Gitlab("https://gitlab.com", private_token=get_env("GITLAB_ADMIN_TOKEN"))
group = gl.groups.get(get_env("GITLAB_ROOT_GROUP_ID"))

print("filter project names and ids")
projects = group.projects.list(all=True)
projects_filtered = [
    {"id": project.id, "name": project.name} for project in projects if project.id
]

cron_dict = {}

print("filters schedule by projects")
for project in projects_filtered:
    for cron in gl.projects.get(project["id"]).pipelineschedules.list(all=True):
        if not cron_dict.get(project["name"]):
            cron_dict[project["name"]] = []

        filtered_dict = {
            "description": cron.description,
            "branch": cron.ref,
            "schedule": cron.cron,
            "active": cron.active,
            "created_at": cron.created_at,
            "updated_at": cron.updated_at,
            "owner": cron.owner,
        }
        filtered_dict["md5"] = md5(yaml.dump(filtered_dict).encode('utf8')).hexdigest()
        cron_dict[project["name"]].append(filtered_dict)

write_yaml_file("crons_dump.yml", cron_dict)

Bon je vais pas faire l’affront de recopier la CI/CD, c’est pratiquement la même chose, mais sans git-crypt et avec un nom de script différent. Ça nous donne un fichier yaml de résultat comme celui-ci.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
backup-crons:
- active: true
  branch: main
  created_at: '2022-09-24T12:57:30.614Z'
  description: backups cron
  md5: 2c95b6b7a08b348d6eec0f73d6d164ca
  owner:
    avatar_url: https://secure.gravatar.com/avatar/7a7b63b4e7c0c2e45714dfbc41dd6fca?s=80&d=identicon
    id: 10734904
    name: helldrum
    state: active
    username: jonathanchardon91
    web_url: https://gitlab.com/jonathanchardon91
  schedule: 0 5 * * *
  updated_at: '2022-09-24T12:57:30.614Z'
backup-variables-gitlabs:
- active: true
  branch: main
  created_at: '2022-09-24T12:15:12.575Z'
  description: schedule envs backups
  md5: f4b3a6afa7cbe761104b780a8ca4fd00
  owner:
    avatar_url: https://secure.gravatar.com/avatar/7a7b63b4e7c0c2e45714dfbc41dd6fca?s=80&d=identicon
    id: 10734904
    name: helldrum
    state: active
    username: jonathanchardon91
    web_url: https://gitlab.com/jonathanchardon91
  schedule: 0 * * * *
  updated_at: '2022-09-24T12:15:12.575Z'

J’ai changé une lettre dans la description de mon cron et en relançant le script on voit tout de suite que quelque chose a changé.

gitk diff entre 2 versions du fichier de cron

J’ai mis un schedule toutes les heures parce que c’est bien moins critique que les variables d’env et le rythme de changement devrait être bien plus lent. On est maintenant couvert sur ces deux modifs manuelles qui sont plus rapides et bien trop facile à faire pour ne pas se couvrir :) On pourrait faire ça en terraform, mais cela nécessite de la discipline et y’aura toujours quelqu’un pour faire sauter ça.

Parce que:

  • ça m’a pris 10 secondes de le faire
  • je n’ai pas besoin de connaitre terraform
  • j’ai les accès au projet, pourquoi j’irais demander des accès en plus ou modifier un code d’infra compliqué ?
  • je n’ai juste pas le temps, mon projet est à la bourre (l’excuse de merde que tout le monde sort)
  • j’ai besoin d’autonomie, la personne qui le fait habituellement n’est pas là (promis je lui dirais si je n’ai pas oublié)

Bob Ross, la joie de la peinture

Il te faut un moyen serein de gérer les joyeux petits incidents de prod et les esprits un peu trop créatifs :)

versionner la cicd en dehors des repo de code

Le yaml à un défaut majeur, les merges sont compliqués à relire. Quand on mélange du code métier et de la ci/cd, traquer les modifs sans erreurs et sans casser du code est un peu stressant et compliqué. Je conseille de plutôt versionner la CI/CD et les scripts associés dans un repo dédié et de faire des tags de version propre. C’est d’autant plus important quand plusieurs repos utilisent le même fichier yaml.

Il suffira ensuite d’utiliser les includes pour forcer une version spécifique de la CI/CD. https://docs.gitlab.com/ee/ci/yaml/includes.html

Par exemple dans mon projet de code, mon fichier gitlab-ci contiendra ce yaml (J’ai mis la branche main en référence, mais un tag c’est encore plus précis) Ce yaml fait référence au fichier .gitlab-ci.yml dans mon repo cicd_common.

1
2
3
4
include:
  - project: 'cyberpunkachien/cicd_common'
    ref: main
    file: '.gitlab-ci.yml'

Plusieurs effets cool de cette méthode:

  • versionner et figer une version précise et stable de la ci/cd
  • réduire la complexité des merges
  • ne pas polluer les repos de code avec des tentatives de modification de yaml
  • inclure des tests des scripts utilisés dans la CI/CD
  • rollback et changement de la CI/CD maitrisé sur un ou plusieurs repo
  • créer des branches de CI/CD totalement divergentes de la version main pour tester et casser des trucs sans impacter directement les devs
  • versionner et mutualiser des scripts utilisés dans la CI/CD

Alors quand c’est possible, soit cool, et laisse tes devs en dehors des dramas du yaml game.

dessin de lama avec des lunettes, no drama lama

Utilisez une image custom gitlab et automatiser sa mise à jour

Dans certain cas, il peut être intéressant de faire un runner dédié avec une image docker custom qui regroupe tous les outils nécessaires dans une seule image. Là ou ça devient vraiment intéressant, c’est de mettre le nom de cette image en variable de groupe et de permettre le changement de cette variable pour toutes les CI/CD du groupe :)

J’aime bien avoir python3 + gcloud + docker dind + tfenv dans la même image. pour builder des containers, lancer des commandes gcloud depuis la CI/CD pusher vers le registry google, lancer du terraform

Concrètement on fait comme ça:

le dockerFile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
FROM docker:dind 
ENV LANG C.UTF-8
RUN apk update && apk upgrade
RUN apk add jq curl git bash python3 py3-pip gcc g++ make unzip
RUN pip3 install pipenv
RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-388.0.0-linux-x86_64.tar.gz
RUN tar -xvzf google-cloud-cli-388.0.0-linux-x86_64.tar.gz
RUN ./google-cloud-sdk/install.sh
RUN rm google-cloud-cli-388.0.0-linux-x86_64.tar.gz
ENV PATH $PATH:/google-cloud-sdk/bin
RUN git clone https://github.com/tfutils/tfenv.git /.tfenv
ENV PATH="/.tfenv/bin:$PATH"
ENV TF_VERSION="1.0.11"
RUN tfenv install $TF_VERSION
RUN tfenv use $TF_VERSION

On construit notre image sur docker dind pour nous permettre de faire des actions docker dans le container. Ton runner doit avoir le droit de puller et pusher sur ton repo, le mode privilégier et le binding du socket docker dans le toml du runner pour fonctionner.

1
2
3
4
 [runners.docker]
    [...]
    privileged = true
    volumes = ["/var/run/docker.sock:/var/run/docker.sock","/certs/client", "/cache"]

On peut tout à fait builder cette image avec cette même image, mais pour éviter d’être bloqué, ça peut être une bonne idée d’utiliser gcloud builder pour cette image uniquement. Ensuite un petit coup de script python et tu peux mettre à jour ton image dans la variable de group.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env pipenv-shebang
import os, sys
import gitlab


def get_env(env):
    getenv = os.getenv(env)
    if getenv:
        print(f"get env {env}")
        return getenv
    else:
        print(f"error, missing env {env}")
        sys.exit(-1)


gl = gitlab.Gitlab("https://gitlab.com", private_token=get_env("GITLAB_ADMIN_TOKEN"))
group = gl.groups.get(get_env("GITLAB_ROOT_GROUP_ID"))
group_var_name = "DOCKER_IMAGE"
new_gitlab_image=get_env("NEW_GITLAB_IMAGE")

try:
    docker_image_value = group.variables.get("DOCKER_IMAGE")
    print (f"key exist with value : {docker_image_value}")
    print (f"change it with new value : {new_gitlab_image}")
    docker_image_value.value = new_gitlab_image
    docker_image_value.save()
    
except gitlab.exceptions.GitlabCreateError:
    print (f"key {group_var_name} not found, create it with value : {docker_image_value}")
    group.variables.create({'key': 'DOCKER_IMAGE', 'value': new_gitlab_image})

Premier run

1
2
3
4
get env GITLAB_ADMIN_TOKEN
get env GITLAB_ROOT_GROUP_ID
get env NEW_GITLAB_IMAGE
key DOCKER_IMAGE not found, create it with value : utils:v1.0.2

Deuxième run

1
2
3
4
5
6
7
get env GITLAB_ADMIN_TOKEN
get env GITLAB_ROOT_GROUP_ID
get env NEW_GITLAB_IMAGE
key exist with value : <class 'gitlab.v4.objects.variables.GroupVariable'> => \
{'variable_type': 'env_var', 'key': 'DOCKER_IMAGE', 'value': 'utils:v1.0.2', \
'protected': False, 'masked': False, 'environment_scope': '*'}
change it with new value : utils:v1.0.3

Lance ce script en CI/CD dans ton repo avec le dockerfile pour avoir une trace du changement. Ça peut être malin de mettre des tags propres sur les noms des images (hash de commit ou version sémantique v1.1.1 par exemple). Bon je suis d’accord pour dire que les noms de clefs hardcodés ce n’est pas fou, si tu veux un truc un peu plus modulaire, utilise ce bon vieux arg parse. D’ailleurs un truc qu’on faisait dans une ancienne mission (coucou Antoine), c’est qu’on persistait de l’info dans les variables de ci et aussi dans le wiki et le snippet Gitlab.

à toi de voir si tu préfères un runner unique qui tabasse, les runners de gitlab SASS ou les runners en kube.

Un petit kube, un gros kube, c’est l’heure de payer la facture ! ah non ça doit pas être ça la pub :)

photo apéricube dans une assiette

backup de la configuration des repos

Dernière astuce, si vous travaillez en terrain miné, il peut être de bon ton de faire des backups de la conf des repos. Surtout si vous avez mis en place des règles restrictives sur le repo et que des admins désactivent ces options de temps en temps. Pour faire du sale ou pour faire une action legit, parfois on n’a pas le choix de passer hors process, mais c’est important de vérifier que les règles soient remises en place une fois la malversation terminée.

On reprend une même base de script et on remix un peu tout ça ! recyclage je vous dis :)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env pipenv-shebang
import os, sys
from base64 import b64encode
from hashlib import md5
import gitlab
import yaml


def get_env(env):
    getenv = os.getenv(env)
    if getenv:
        print(f"get env {env}")
        return getenv
    else:
        print(f"missing env var {env}")
        sys.exit(-1)


def write_yaml_file(filename, data_dict):
    print(f"dumping file {filename}")
    with open(filename, "w") as f:
        f.write(yaml.dump(data_dict))


gl = gitlab.Gitlab("https://gitlab.com", private_token=get_env("GITLAB_ADMIN_TOKEN"))
group = gl.groups.get(get_env("GITLAB_ROOT_GROUP_ID"))

projects = group.projects.list(all=True)

import pprint

configurations = {}
for project_conf in projects:
    project_name = project_conf.__dict__["_attrs"]["name"]
    full_current_project = gl.projects.get(project_conf.id)

    if not configurations.get(project_name):
        configurations[project_name] = []

        configurations[project_name] = {
            "configuration": full_current_project.__dict__["_attrs"],
            "protected_tags": [
                tags.__dict__["_attrs"]
                for tags in full_current_project.protectedtags.list(all=True)
            ],
            "protected_branches": [
                p_branche.__dict__["_attrs"]
                for p_branche in full_current_project.protectedbranches.list(all=True)
            ],
        }

write_yaml_file("repo_dump.yml", configurations)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
parcequecestnotreprojet:
  configuration:
    _links:
    [...]
    allow_merge_on_skipped_pipeline: null
    analytics_access_level: enabled
    archived: false
    auto_cancel_pending_pipelines: enabled
    auto_devops_deploy_strategy: continuous
    auto_devops_enabled: false
    autoclose_referenced_issues: true
    avatar_url: null
    build_git_strategy: fetch
    build_timeout: 3600
    builds_access_level: enabled
    can_create_merge_request_in: true
    ci_allow_fork_pipelines_to_run_in_parent_project: true
    ci_config_path: ''
    ci_default_git_depth: 20
    ci_forward_deployment_enabled: true
    ci_job_token_scope_enabled: false
    ci_opt_in_jwt: false
    ci_separated_caches: true
    compliance_frameworks: []
    created_at: '2022-09-27T13:19:04.101Z'
    creator_id: 10734904
    default_branch: main
    last_activity_at: '2022-09-28T08:41:17.884Z'
    namespace:
      avatar_url: null
      full_path: cyberpunkachien
      id: 15874964
      kind: group
      name: cyberpunkachien
      parent_id: null
      path: cyberpunkachien
    only_allow_merge_if_all_discussions_are_resolved: false
    only_allow_merge_if_pipeline_succeeds: false
    permissions:
      group_access:
        access_level: 50
        notification_level: 3
      project_access: null
    printing_merge_request_link_enabled: true
    wiki_access_level: enabled
    wiki_enabled: true
  protected_branches:
  - allow_force_push: false
    code_owner_approval_required: false
    id: 57384225
    merge_access_levels:
    - access_level: 40
      access_level_description: Maintainers
      group_id: null
      user_id: null
    name: main
    push_access_levels:
    - access_level: 40
      access_level_description: Maintainers
      group_id: null
      user_id: null
    unprotect_access_levels: []
  - allow_force_push: false
    code_owner_approval_required: false
    id: 57424365
    merge_access_levels:
    - access_level: 40
      access_level_description: Maintainers
      group_id: null
      user_id: null
    name: feat/macron_demission
    push_access_levels:
    - access_level: 40
      access_level_description: Maintainers
      group_id: null
      user_id: null
    unprotect_access_levels: []
  protected_tags: []

On reprend la même base de script, mais cette fois-ci on backup la conf des repo + la conf des protected branche + la conf des protected tags. Comme ça on sait tout de suite si quelqu’un a touché à l’allow push force d’un repo par exemple.

Ça peut faire gagner un temps considérable si on travail en environnement un peu trouble. par exemple des gens qui font des réécritures d’historique git, qui font push force sur master sans trop faire attention au travail des autres. Ou qui joue avec ce genre d’outils :)

https://lesjoiesducode.fr/git-blame-someone-else-blamez-les-autres-pour-votre-mauvais-code-github

https://rtyley.github.io/bfg-repo-cleaner/

On vous voit bandes de margoulins, je n’en ai pas la preuve, mais au moins j’aurai des indices :)

détective pikachu, regarde à travers une loupe

Conclusion

Le python SDK de Gitlab est extrêmement puissant à partir du moment où on prend le temps de jouer un peu avec, ça prend du temps, mais ça vaut le coup. C’est important de versionner et d’automatiser les zones grises de l’infra et de la conf pour éviter de se retrouver au dépourvu devant l’imprévu. Garde le Bash pour les trucs simples et prends Python dès que tu commences à manipuler des API ou des formats de données compliquées.

Je te conseille vivement de potasser la doc du python SDK pour voir tous les trucs super que tu peux faire avec https://python-gitlab.readthedocs.io/en/stable/api-objects.html. Bien sûr si tu commences à avoir masse code qui se ressemble, ça vaut le coup de factoriser tout ça dans une lib pour éviter de trainer une code base avec beaucoup de code dupliqué.

Team serpentard, team des flemmards. (je n’ai pas trouvé une meilleure rime et j’ai la flemme)

équipe Serpentard avec malfoy au premier plan