[{"data":1,"prerenderedAt":708},["ShallowReactive",2],{"/fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/":3,"navigation-fr-fr":37,"banner-fr-fr":456,"footer-fr-fr":468,"Michael Friedrich":680,"next-steps-fr-fr":693},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":27,"_id":30,"_type":31,"title":32,"_source":33,"_file":34,"_stem":35,"_extension":36},"/fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Utiliser l'API python-gitlab pour améliorer vos workflows DevSecOps","Vous souhaitez améliorer vos workflows DevSecOps ? Découvrez dans ce tutoriel des exemples et bonnes pratiques d’utilisation de l’API python-gitlab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659883/Blog/Hero%20Images/post-cover-image.jpg","https://about.gitlab.com/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Utiliser l'API python-gitlab pour améliorer vos workflows DevSecOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2023-02-01\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Michael Friedrich","2023-02-01","Un jour, un ami m’a dit : « Le travail manuel est un bug ». Depuis, face à des tâches répétitives, j’ai pris l’habitude de les automatiser autant que possible. \n\nPar exemple, en interrogeant une [API REST](https://about.gitlab.com/fr-fr/blog/what-is-rest-api/ \"API REST\") pour faire un inventaire des paramètres, ou en effectuant des appels d’API pour créer de nouveaux commentaires dans les tickets ou les merge requests de GitLab. L'interaction avec l'API REST de GitLab peut se faire de différentes manières, en utilisant des requêtes HTTP avec curl (ou [hurl](https://about.gitlab.com/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd/ \"hurl\")) en ligne de commande, ou en écrivant un script dans un langage de programmation. \n\nDans ce dernier cas, il faut effectuer des tâches fastidieuses avec le code brut des requêtes HTTP et l'analyse des réponses JSON. Grâce à la communauté GitLab, de nombreux langages sont pris en charge par les bibliothèques d'abstraction d'API. Elles prennent en charge tous les attributs de l’API, ajoutent des fonctions d'aide pour obtenir, créer et supprimer des objets, et facilitent ainsi la tâche des équipes de développement. La [bibliothèque python-gitlab](https://python-gitlab.readthedocs.io/en/stable/ \"Bibliothèque python-gitlab\") est une bibliothèque écrite en Python, riche en fonctionnalités et facile à utiliser.\n\nDans cet article, vous apprendrez les bases de l’utilisation de la bibliothèque python-gitlab en vous familiarisant avec les objets de l’API, les attributs, la pagination et les ensembles de résultats. Vous découvrirez également des cas d'utilisation concrets tels que la collecte de données, la génération de synthèses et l’écriture de données dans l'API pour créer des commentaires et des validations. \n\nIl y a encore beaucoup à apprendre, avec de nombreux cas d'utilisation inspirés des questions posées par la communauté sur le forum, Hacker News, des tickets, et bien plus encore.\n\n## Premiers pas\n\nLa [documentation python-gitlab](https://python-gitlab.readthedocs.io/en/stable/api-usage.html \"Documentation python-gitlab\") est une excellente ressource pour débuter. Elle offre un aperçu des types d'objets et de leurs méthodes, ainsi que des exemples de workflows combinés. Cette ressource est idéale pour faire vos premiers pas, en plus de la [documentation sur les ressources de l'API GitLab](https://docs.gitlab.com/ee/api/api_resources.html \"Documentation sur les ressources de l'API GitLab\") qui fournit les attributs d'objet associés.\n\nLes exemples de code présentés dans cet article nécessitent Python 3.8+ et la bibliothèque `python-gitlab`. Des exigences supplémentaires sont spécifiées dans le fichier `requirements.txt`. Un exemple nécessite `pyyaml` pour l'analyse de la configuration YAML. Pour suivre et mettre en pratique le code des cas d'utilisation, il est recommandé de cloner le projet, d'installer les prérequis et d'exécuter les scripts. \n\nExemple avec Homebrew sur macOS :\n\n```shell\ngit clone https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python.git\n\ncd gitlab-api-python\n\nbrew install python\n\npip3 install -r requirements.txt\n\npython3 \u003Cscriptname>.py\n```\n\nLes scripts n'utilisent pas de bibliothèque partagée commune fournissant des fonctions génériques pour la lecture des paramètres ou d’autres fonctionnalités d'aide supplémentaires. L’objectif est de montrer des exemples faciles à suivre, qui peuvent être utilisés de manière autonome pour des tests, et qui nécessitent uniquement l'installation de la bibliothèque python-gitlab.\n\nNous recommandons d'améliorer le code pour une utilisation en production. Cela vous aidera à créer un projet d’API tooling maintenu, incluant par exemple des images de conteneurs et des modèles CI/CD que les équipes de développement peuvent utiliser au sein d'une plateforme [DevSecOps](https://about.gitlab.com/fr-fr/topics/devsecops/ \"Qu'est-ce que DevSecOps ?\").\n\n## Configuration\n\nSans configuration, python-gitlab exécutera des requêtes non authentifiées sur le serveur par défaut : `https://gitlab.com`. Les paramètres de configuration les plus courants concernent l'instance GitLab à laquelle se connecter, et la méthode d'authentification en spécifiant les jetons d'accès. Python-gitlab prend en charge différents types de configuration : un fichier de configuration ou des variables d'environnement.\n\nLe [fichier de configuration](https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#cli-configuration \"Fichier de configuration CLI\") est disponible pour les liaisons de bibliothèque d’API et pour l'interface de ligne de commande (que nous n’aborderons pas dans cet article). Le fichier de configuration prend en charge les [credential helpers](https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#credential-helpers \"Credential helpers\") pour accéder directement aux jetons.\n\nLes variables d'environnement, en tant que méthode de configuration alternative, vous offrent un moyen simple d'exécuter le script dans un terminal, de l'intégrer dans des images de conteneurs et de le préparer à être exécuté dans des [pipelines CI/CD](https://about.gitlab.com/fr-fr/topics/ci-cd/cicd-pipeline/ \"Pipeline CI/CD\").\n\nVous devez lancer la configuration dans le contexte du script Python. Importez la bibliothèque `os` pour récupérer les variables d'environnement à l'aide de la méthode `os.environ.get()`. Le premier paramètre spécifie la clé, le second paramètre définit la valeur par défaut lorsque la variable n'est pas disponible dans l'environnement.\n\n```python\nimport os\n\ngl_server = os.environ.get('GL_SERVER', 'https://gitlab.com')\n\nprint(gl_server)\n```\n\nLe paramétrage dans le terminal peut se faire directement pour la commande uniquement, ou être exporté dans l'environnement shell.\n\n```shell\n$ GL_SERVER=’https://gitlab.company.com’ python3 script.py\n\n$ export GL_SERVER=’https://gitlab.company.com’\n$ python3 script.py\n```\n\nNous recommandons d'ajouter des contrôles de sécurité pour s’assurer que toutes les variables sont définies avant de continuer l'exécution du programme. L'extrait de code suivant importe les bibliothèques requises, lit la variable d'environnement `GL_SERVER`, et attend de l'utilisateur qu'il définisse la variable `GL_TOKEN`. Si ce n'est pas le cas, le script affiche et génère des erreurs, puis appelle `sys.exit(1)`, pour indiquer un statut d’erreur. \n\n```python\nimport gitlab\nimport os\nimport sys\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\nGITLAB_TOKEN = os.environ.get('GL_TOKEN')\n\nif not GITLAB_TOKEN:\n    print(\"Please set the GL_TOKEN env variable.\")\n    sys.exit(1)\n```\n\nExaminons maintenant un exemple plus détaillé, qui crée une connexion à l'API et effectue une requête de données.\n\n## Gestion des objets : l'objet GitLab\n\nToute interaction avec l'API nécessite une instanciation de l'objet GitLab. C'est le point d'entrée pour configurer le serveur GitLab auquel se connecter et s'authentifier à l'aide de jetons d'accès, et définir d’autres paramètres globaux pour la pagination, le chargement d’objets, et plus encore. \n\nL'exemple suivant exécute une requête non authentifiée sur GitLab.com. Il est possible d'accéder aux points de terminaison d’API publique, et d'obtenir par exemple un [modèle .gitignore pour Python](https://python-gitlab.readthedocs.io/en/stable/gl_objects/templates.html#gitignore-templates \"Modèle .gitignore pour Python\").\n\n[python_gitlab_object_unauthenticated.py](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_object_unauthenticated.py)\n\n```python\nimport gitlab\n\ngl = gitlab.Gitlab()\n\n# Get .gitignore templates without authentication\ngitignore_templates = gl.gitignores.get('Python')\n\nprint(gitignore_templates.content)\n```\n\nDans les sections suivantes, nous vous partageons des informations détaillées sur : \n\n- La gestion et le chargement des objets,\n- La pagination des résultats,\n- Le travail avec les relations entre objets,\n- Le travail avec différents scopes de collection d'objets.\n\n### La gestion et le chargement des objets\n\nLa bibliothèque python-gitlab donne accès aux ressources GitLab en utilisant ce que l’on appelle des « [Gestionnaires](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#managers) ». Chaque type de gestionnaire implémente des méthodes pour travailler avec les ensembles de données (list, get, etc.).\n\nLe script ci-dessous montre comment accéder aux sous-groupes, aux projets directs et à tous les projets, y compris les sous-groupes, aux tickets, aux epics et aux tâches. Une authentification est nécessaire pour accéder à tous les attributs. L'extrait de code utilise donc des variables pour obtenir le jeton d'authentification, et utilise également la variable `GROUP_ID` pour spécifier un groupe principal à partir duquel il faut commencer la recherche.\n\n```python\n#!/usr/bin/env python\n\nimport gitlab\nimport os\nimport sys\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\n# https://gitlab.com/gitlab-de/use-cases/\nGROUP_ID = os.environ.get('GL_GROUP_ID', 16058698)\nGITLAB_TOKEN = os.environ.get('GL_TOKEN')\n\nif not GITLAB_TOKEN:\n    print(\"Please set the GL_TOKEN env variable.\")\n    sys.exit(1)\n\ngl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)\n\n# Main\nmain_group = gl.groups.get(GROUP_ID)\n\nprint(\"Sub groups\")\nfor sg in main_group.subgroups.list():\n    print(\"Subgroup name: {sg}\".format(sg=sg.name))\n\nprint(\"Projects (direct)\")\nfor p in main_group.projects.list():\n    print(\"Project name: {p}\".format(p=p.name))\n\nprint(\"Projects (including subgroups)\")\nfor p in main_group.projects.list(include_subgroups=True, all=True):\n     print(\"Project name: {p}\".format(p=p.name))\n\nprint(\"Issues\")\nfor i in main_group.issues.list(state='opened'):\n    print(\"Issue title: {t}\".format(t=i.title))\n\nprint(\"Epics\")\nfor e in main_group.issues.list():\n    print(\"Epic title: {t}\".format(t=e.title))\n\nprint(\"Todos\")\nfor t in gl.todos.list(state='pending'):\n    print(\"Todo: {t} url: {u}\".format(t=t.body, u=t.target_url\n```\n\nVous pouvez exécuter le script `python_gitlab_object_manager_methods.py` en remplaçant la variable `GROUP_ID` sur GitLab.com SaaS pour analyser votre propre groupe. Vous devez spécifier la variable `GL_SERVER` pour les instances auto-gérées. `GL_TOKEN` doit fournir le jeton d'accès personnel.\n\n```shell\nexport GL_TOKEN=xxx\n\nexport GL_SERVER=”https://gitlab.company.com”\n\nexport GL_SERVER=”https://gitlab.com”\n\nexport GL_GROUP_ID=1234\n\npython3 python_gitlab_object_manager_methods.py\n```\n\nÀ partir de maintenant, les exemples n'affichent plus les en-têtes Python et l'analyse des variables d'environnement, afin de se concentrer sur l'algorithme et les fonctionnalités. Tous les scripts sont open source sous licence MIT, et sont disponibles dans [ce projet](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python).\n\n### La pagination des résultats\n\nPar défaut, l’API GitLab ne renvoie pas tous les ensembles de résultats et exige que les clients utilisent la [pagination](https://docs.gitlab.com/ee/api/rest/index.html#pagination \"Pagination GitLab\") pour parcourir toutes les pages de résultats. La bibliothèque python-gitlab permet aux utilisateurs de [définir les paramètres](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination) globalement dans l'objet GitLab, ou sur chaque appel `list()`. Cela permet d'éviter que tous les ensembles de résultats déclenchent des requêtes API, ce qui peut ralentir l'exécution du script. Utilisez `iterator=True`, et les appels d'API sont déclenchés à la demande lors de l'accès à l'objet.\n\nL'exemple suivant recherche le nom de groupe `everyonecancontribute` et utilise la pagination du jeu de clés pour afficher 100 résultats sur chaque page. L'itérateur est défini sur true dans `gl.groups.list(iterator=True)` pour récupérer de nouveaux ensembles de résultats à la demande. Si le nom du groupe recherché est trouvé, la boucle s'interrompt et affiche un résumé, incluant la mesure de la durée totale de la requête de recherche.\n\n```python\nSEARCH_GROUP_NAME=\"everyonecancontribute\"\n\n# Use keyset pagination\n# https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination\ngl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN,\n    pagination=\"keyset\", order_by=\"id\", per_page=100)\n\n# Iterate over the list, and fire new API calls in case the result set does not match yet\ngroups = gl.groups.list(iterator=True)\n\nfound_page = 0\nstart = timer()\n\nfor group in groups:\n    if SEARCH_GROUP_NAME == group.name:\n        # print(group) # debug\n        found_page = groups.current_page\n        break\n\nend = timer()\n\nduration = f'{end-start:.2f}'\n\nif found_page > 0:\n    print(\"Pagination API example for Python with GitLab{desc} - found group {g} on page {p}, duration {d}s\".format(\n        desc=\", the DevSecOps platform\", g=SEARCH_GROUP_NAME, p=found_page, d=duration))\nelse:\n    print(\"Could not find group name '{g}', duration {d}\".format(g=SEARCH_GROUP_NAME, d=duration))\n```\nL'exécution de `python_gitlab_pagination.py` a permis de trouver le groupe [everyonecancontribute](https://gitlab.com/everyonecancontribute) à la page 5.\n\n```shell\n$ python3 python_gitlab_pagination.py\nPagination API example for Python with GitLab, the DevSecOps platform - found group everyonecancontribute on page 5, duration 8.51s\n```\n\n### Le travail avec les relations entre objets\n\nLorsque vous travaillez avec des relations entre objets, par exemple pour collecter tous les projets dans un groupe donné, vous devez envisager des étapes supplémentaires. Par défaut, les objets de projet renvoyés présentent des attributs limités. Les objets gérables nécessitent un appel supplémentaire `get()` pour obtenir l'objet de projet complet de l'API en arrière-plan. Ce workflow permet de réduire les temps d’attente et le trafic en limitant les attributs immédiatement renvoyés.\n\nL'exemple suivant illustre le problème en parcourant tous les projets d'un groupe et en essayant d'appeler la fonction `project.branches.list()`. Cela génère une exception dans le flux try/except. Le deuxième exemple obtient un objet de projet gérable et tente à nouveau d'appeler la fonction.\n\n```python\n# Main\ngroup = gl.groups.get(GROUP_ID)\n\n# Collect all projects in group and subgroups\nprojects = group.projects.list(include_subgroups=True, all=True)\n\nfor project in projects:\n    # Try running a method on a weak object\n    try:\n       print(\"🤔 Project: {pn} 💡 Branches: {b}\\n\".format(\n        pn=project.name,\n        b=\", \".join([x.name for x in project.branches.list()])))\n    except Exception as e:\n        print(\"Got exception: {e} \\n ===================================== \\n\".format(e=e))\n\n    # Retrieve a full manageable project object\n    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n    manageable_project = gl.projects.get(project.id)\n\n    # Print a method available on a manageable object\n    print(\"🤔 Project: {pn} 💡 Branches: {b}\\n\".format(\n        pn=manageable_project.name,\n        b=\", \".join([x.name for x in manageable_project.branches.list()])))\n```\n\nLe gestionnaire d'exceptions dans la bibliothèque python-gitlab affiche le message d'erreur et renvoie à la documentation. Pour le débogage, notez que les objets peuvent ne pas être disponibles pour la gestion lorsque vous ne pouvez pas accéder aux attributs de l'objet ou aux appels de fonction.\n\n```shell\n$ python3 python_gitlab_manageable_objects.py\n\n🤔 Project: GitLab API Playground 💡 Branches: cicd-demo-automated-comments, docs-mr-approval-settings, main\n\nGot exception: 'GroupProject' object has no attribute 'branches'\n\n\u003Cclass 'gitlab.v4.objects.projects.GroupProject'> was created via a\nlist() call and only a subset of the data may be present. To ensure\nall data is present get the object using a get(object.id) call. For\nmore details, see:\n\nhttps://python-gitlab.readthedocs.io/en/v3.8.1/faq.html#attribute-error-list\n =====================================\n```\n\n[Consultez le script complet](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_manageable_objects.py).\n\n### Le travail avec différents scopes de collection d'objets\n\nParfois, le script doit collecter tous les projets d'une instance auto-gérée, d'un groupe avec des sous-groupes, ou d'un projet unique. Ce dernier cas est utile pour accélérer les tests sur les attributs requis, et la récupération du groupe facilite les tests à grande échelle par la suite. L'extrait de code suivant collecte tous les objets de projet dans la liste `projects` et y ajoute les objets provenant des différentes configurations entrantes. Vous retrouverez également à nouveau le modèle d'objet gérable pour le projet dans les groupes.\n\n```python\n    # Collect all projects, or prefer projects from a group id, or a project id\n    projects = []\n\n    # Direct project ID\n    if PROJECT_ID:\n        projects.append(gl.projects.get(PROJECT_ID))\n\n    # Groups and projects inside\n    elif GROUP_ID:\n        group = gl.groups.get(GROUP_ID)\n\n        for project in group.projects.list(include_subgroups=True, all=True):\n            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n            manageable_project = gl.projects.get(project.id)\n            projects.append(manageable_project)\n\n    # All projects on the instance (may take a while to process)\n    else:\n        projects = gl.projects.list(get_all=True)\n```\n\nL'exemple complet se trouve dans [ce script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py) pour lister les paramètres des règles d'approbation des merge requests pour les cibles de projet spécifiées.\n\n## Utilisation de l’approche DevSecOps pour les actions de lecture API\n\nLe jeton d'accès authentifié nécessite un scope `read_api`.\n\nLes cas d’utilisation suivants seront abordés :\n- Lister les branches par état de fusion,\n- Afficher les paramètres du projet pour révision : règles d'approbation des merge requests,\n- Inventaire : obtenir toutes les variables CI/CD protégées ou masquées,\n- Télécharger un fichier depuis le dépôt,\n- Aide à la migration : lister tous les clusters Kubernetes basés sur des certificats,\n- Productivité des équipes : vérifier si les merge requests existantes nécessitent un rebase après avoir fusionné une merge request de refactorisation majeure.  \n\n### Lister les branches par état de fusion \n\nPour nettoyer un projet [Git](https://about.gitlab.com/fr-fr/blog/what-is-git/ \"Qu'est-ce que Git ?\"), il est courant d'évaluer le nombre de branches fusionnées et non fusionnées. En réponse à une [question sur le forum de la communauté GitLab](https://forum.gitlab.com/t/python-gitlab-project-branch-list-filter/80257) concernant le filtrage des listes de branches, j'ai écrit un [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/tree/main) aidant à cela. La méthode `branches.list()` renvoie tous les objets de branche stockés dans une liste temporaire, pour un traitement ultérieur en deux boucles : la collecte des noms de branches fusionnées, et celle des noms de branches non fusionnées. L'attribut `merged` sur l'objet `branch` est une valeur booléenne qui indique si la branche a été fusionnée ou non.\n\n```python\nproject = gl.projects.get(PROJECT_ID, lazy=False, pagination=\"keyset\", order_by=\"updated_at\", per_page=100)\n\n# Get all branches\nreal_branches = []\nfor branch in project.branches.list():\n    real_branches.append(branch)\n\nprint(\"All branches\")\nfor rb in real_branches:\n    print(\"Branch: {b}\".format(b=rb.name))\n\n# Get all merged branches\nmerged_branches_names = []\nfor branch in real_branches:\n    if branch.default:\n        continue # ignore the default branch for merge status\n\n    if branch.merged:\n        merged_branches_names.append(branch.name)\n\nprint(\"Branches merged: {b}\".format(b=\", \".join(merged_branches_names)))\n\n# Get un-merged branches\nnot_merged_branches_names = []\nfor branch in real_branches:\n    if branch.default:\n        continue # ignore the default branch for merge status\n\n    if not branch.merged:\n        not_merged_branches_names.append(branch.name)\n\nprint(\"Branches not merged: {b}\".format(b=\", \".join(not_merged_branches_names)))\n```\n\nLe workflow est destiné à être lu étape par étape. Vous pouvez vous entraîner à optimiser le code Python pour la collecte conditionnelle des noms de branches.\n\n### Afficher les paramètres du projet pour examen : règles d'approbation des merge requests\n\nLe [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py) suivant parcourt tous les objets de projet collectés et vérifie si des règles d'approbation sont spécifiées. Si la longueur de la liste est supérieure à zéro, il parcourt la liste en boucle et affiche les paramètres avec la méthode JSON pretty print.\n\n```python\n    # Loop over projects and print the settings\n    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_request_approvals.html\n    for project in projects:\n        if len(project.approvalrules.list()) > 0:\n            #print(project) #debug\n            print(\"# Project: {name}, ID: {id}\\n\\n\".format(name=project.name_with_namespace, id=project.id))\n            print(\"[MR Approval settings]({url}/-/settings/merge_requests)\\n\\n\".format(url=project.web_url))\n\n            for ar in project.approvalrules.list():\n                print(\"## Approval rule: {name}, ID: {id}\".format(name=ar.name, id=ar.id))\n                print(\"\\n```json\\n\")\n                print(json.dumps(ar.attributes, indent=2)) # TODO: can be more beautiful, but serves its purpose with pretty print JSON\n                print(\"\\n```\\n\")\n\n```\n\n### Inventaire : obtenir toutes les variables CI/CD protégées ou masquées\n\nLes [variables CI/CD](https://docs.gitlab.com/ee/ci/variables/ \"Variables CI/CD\") sont utiles au paramétrage des pipelines et peuvent être configurées globalement sur l'instance, dans les groupes et dans les projets. Nous pouvons aussi y stocker des informations confidentielles, des mots de passe ou encore des secrets. Il peut parfois être nécessaire d’avoir une vue d'ensemble de toutes les variables CI/CD protégées ou masquées pour estimer le nombre de variables à actualiser, lors de la rotation des jetons par exemple.\n\nLe [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_variables_masked_or_protected.py) suivant récupère tous les groupes et projets, puis tente de collecter les variables CI/CD de l'instance globale (cela nécessite des autorisations d'administrateur), des groupes et des projets (cela nécessite des autorisations de chargé de maintenance/propriétaire). Il affiche toutes les variables CI/CD qui sont soit protégées, soit masquées, en précisant qu'une valeur potentiellement secrète y est stockée.\n\n```python\n#!/usr/bin/env python\n\nimport gitlab\nimport os\nimport sys\n\n# Helper function to evaluate secrets and print the variables\ndef eval_print_var(var):\n    if var.protected or var.masked:\n        print(\"🛡️🛡️🛡️ Potential secret: Variable '{name}', protected {p}, masked: {m}\".format(name=var.key,p=var.protected,m=var.masked))\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\nGITLAB_TOKEN = os.environ.get('GL_TOKEN') # token requires maintainer+ permissions. Instance variables require admin access.\nPROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional\nGROUP_ID = os.environ.get('GL_GROUP_ID', 8034603) # https://gitlab.com/everyonecancontribute\n\nif not GITLAB_TOKEN:\n    print(\"🤔 Please set the GL_TOKEN env variable.\")\n    sys.exit(1)\n\ngl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)\n\n# Collect all projects, or prefer projects from a group id, or a project id\nprojects = []\n# Collect all groups, or prefer group from a group id\ngroups = []\n\n# Direct project ID\nif PROJECT_ID:\n    projects.append(gl.projects.get(PROJECT_ID))\n\n# Groups and projects inside\nelif GROUP_ID:\n    group = gl.groups.get(GROUP_ID)\n\n    for project in group.projects.list(include_subgroups=True, all=True):\n        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n        manageable_project = gl.projects.get(project.id)\n        projects.append(manageable_project)\n\n    groups.append(group)\n\n# All projects/groups on the instance (may take a while to process, use iterators to fetch on-demand).\nelse:\n    projects = gl.projects.list(iterator=True)\n    groups = gl.groups.list(iterator=True)\n\nprint(\"# List of all CI/CD variables marked as secret (instance, groups, projects)\")\n\n# https://python-gitlab.readthedocs.io/en/stable/gl_objects/variables.html\n\n# Instance variables (if the token has permissions)\nprint(\"Instance variables, if accessible\")\ntry:\n    for i_var in gl.variables.list(iterator=True):\n        eval_print_var(i_var)\nexcept:\n    print(\"No permission to fetch global instance variables, continueing without.\")\n    print(\"\\n\")\n\n# group variables (maintainer permissions for groups required)\nfor group in groups:\n    print(\"Group {n}, URL: {u}\".format(n=group.full_path, u=group.web_url))\n    for g_var in group.variables.list(iterator=True):\n        eval_print_var(g_var)\n\n    print(\"\\n\")\n\n# Loop over projects and print the settings\nfor project in projects:\n    # skip archived projects, they throw 403 errors\n    if project.archived:\n        continue\n\n    print(\"Project {n}, URL: {u}\".format(n=project.path_with_namespace, u=project.web_url))\n    for p_var in project.variables.list(iterator=True):\n        eval_print_var(p_var)\n\n    print(\"\\n\")\n```\n\nLe script n’affiche pas les valeurs des variables, cela étant réservé comme exercice pour les environnements sécurisés. Pour stocker des secrets, faites plutôt appel à des [fournisseurs externes](https://docs.gitlab.com/ee/ci/secrets/).\n\n### Télécharger un fichier depuis le dépôt\n\nL'objectif de ce [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_raw_file_content.py) est de télécharger un fichier à partir d’un chemin spécifié dans une branche donnée, et de stocker son contenu dans un nouveau fichier.\n\n```python\n# Goal: Try to download README.md from https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/-/blob/main/README.md\nFILE_NAME = 'README.md'\nBRANCH_NAME = 'main'\n\n# Search the file in the repository tree and get the raw blob\nfor f in project.repository_tree():\n    print(\"File path '{name}' with id '{id}'\".format(name=f['name'], id=f['id']))\n\n    if f['name'] == FILE_NAME:\n        f_content = project.repository_raw_blob(f['id'])\n        print(f_content)\n\n# Alternative approach: Get the raw file from the main branch\nraw_content = project.files.raw(file_path=FILE_NAME, ref=BRANCH_NAME)\nprint(raw_content)\n\n# Store the file on disk\nwith open('raw_README.md', 'wb') as f:\n    project.files.raw(file_path=FILE_NAME, ref=BRANCH_NAME, streamed=True, action=f.write)\n```\n\n### Aide à la migration : lister tous les clusters Kubernetes basés sur des certificats\n\nL'intégration des clusters [Kubernetes](https://about.gitlab.com/fr-fr/blog/kubernetes-the-container-orchestration-solution/ \"Qu'est-ce que Kubernetes ?\") basée sur des certificats dans GitLab [a été dépréciée](https://docs.gitlab.com/ee/update/deprecations.html#self-managed-certificate-based-integration-with-kubernetes). Pour faciliter les plans de migration, l'inventaire des groupes et projets existants peut être automatisé à l'aide de l'API GitLab.\n\n```python\ngroups = [ ]\n\n# get GROUP_ID group\ngroups.append(gl.groups.get(GROUP_ID))\n\nfor group in groups:\n    for sg in group.subgroups.list(include_subgroups=True, all=True):\n        real_group = gl.groups.get(sg.id)\n        groups.append(real_group)\n\ngroup_clusters = {}\nproject_clusters = {}\n\nfor group in groups:\n    #Collect group clusters\n    g_clusters = group.clusters.list()\n\n    if len(g_clusters) > 0:\n        group_clusters[group.id] = g_clusters\n\n    # Collect all projects in group and subgroups and their clusters\n    projects = group.projects.list(include_subgroups=True, all=True)\n\n    for project in projects:\n        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n        manageable_project = gl.projects.get(project.id)\n\n        # skip archived projects\n        if project.archived:\n            continue\n\n        p_clusters = manageable_project.clusters.list()\n\n        if len(p_clusters) > 0:\n            project_clusters[project.id] = p_clusters\n\n# Print summary\nprint(\"## Group clusters\\n\\n\")\nfor g_id, g_clusters in group_clusters.items():\n    url = gl.groups.get(g_id).web_url\n    print(\"Group ID {g_id}: {u}\\n\\n\".format(g_id=g_id, u=url))\n    print_clusters(g_clusters)\n\nprint(\"## Project clusters\\n\\n\")\nfor p_id, p_clusters in project_clusters.items():\n    url = gl.projects.get(p_id).web_url\n    print(\"Project ID {p_id}: {u}\\n\\n\".format(p_id=p_id, u=url))\n    print_clusters(p_clusters)\n```\n\n[Consultez le script complet](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/list_cert_based_kubernetes_clusters.py).\n\n### Productivité des équipes : vérifier si les merge requests existantes nécessitent un rebase après avoir fusionné une merge request de refactorisation majeure\n\nLe dépôt du [manuel GitLab](https://about.gitlab.com/handbook/ \"Manuel de GitLab\") est un large monorepo qui contient de nombreuses merge requests créées, examinées, approuvées et fusionnées. Certaines revues prennent plus de temps que d'autres, et certaines merge requests impactent plusieurs pages, lorsqu'il s'agit par exemple de renommer une chaîne de caractères ou [toutes les pages du manuel](https://about.gitlab.com/handbook/about/#count-handbook-pages). Le manuel Marketing avait besoin d’une restructuration (pensez à une refactorisation du code), et de nombreux répertoires et chemins d'accès ont été déplacés ou renommés. \n\nLes [tâches liées aux tickets](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#tasks) ont augmenté au fil du temps, et nous craignons que des conflits sur d'autres merge requests apparaissent après avoir fusionné des changements importants. Avec python-gitlab vous pouvez récupérer toutes les merge requests dans un projet donné, y compris les détails sur la branche Git, sur les chemins sources modifiés, et bien plus encore.\n\nLe script résultant configure une liste des sources touchées par toutes les merge requests, vérifie si la merge request diffère avec `mr.diffs.list()`, et si un modèle correspond à la valeur dans `old_path`. Si une correspondance est trouvée, le script l'enregistre et sauvegarde la merge request dans le dictionnaire `seen_mr`, pour un résumé ultérieur. Des attributs supplémentaires sont collectés pour afficher une liste de tâches en Markdown contenant des URL, afin de faciliter le copier-coller dans les [descriptions des tickets](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#additional-tasks). [Consultez le script complet](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/search_mr_contains_updated_path.py).\n\n```python\nPATH_PATTERNS = [\n    'path/to/handbook/source/page.md',\n]\n\n# Only list opened MRs\n# https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_requests.html#project-merge-requests\nmrs = project.mergerequests.list(state='opened', iterator=True)\n\nseen_mr = {}\n\nfor mr in mrs:\n    # https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-request-diffs\n    real_mr = project.mergerequests.get(mr.get_id())\n    real_mr_id = real_mr.attributes['iid']\n    real_mr_url = real_mr.attributes['web_url']\n\n    for diff in real_mr.diffs.list(iterator=True):\n        real_diff = real_mr.diffs.get(diff.id)\n\n        for d in real_diff.attributes['diffs']:\n            for p in PATH_PATTERNS:\n                if p in d['old_path']:\n                    print(\"MATCH: {p} in MR {mr_id}, status '{s}', title '{t}' - URL: {mr_url}\".format(\n                        p=p,\n                        mr_id=real_mr_id,\n                        s=mr_status,\n                        t=real_mr.attributes['title'],\n                        mr_url=real_mr_url))\n\n                    if not real_mr_id in seen_mr:\n                        seen_mr[real_mr_id] = real_mr\n\nprint(\"\\n# MRs to update\\n\")\n\nfor id, real_mr in seen_mr.items():\n    print(\"- [ ] !{mr_id} - {mr_url}+ Status: {s}, Title: {t}\".format(\n        mr_id=id,\n        mr_url=real_mr.attributes['web_url'],\n        s=real_mr.attributes['detailed_merge_status'],\n        t=real_mr.attributes['title']))\n```\n\n## Cas d’utilisation DevSecOps pour les actions d'écriture de l’API\n\nLe jeton d'accès authentifié nécessite une [portée d’autorisation complète de l’API](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes). \n\nLes cas d’utilisation suivants sont abordés :\n- Déplacer des epics d’un groupe à l’autre,\n- Conformité : vérifier que les paramètres du projet ne sont pas remplacés,\n- Prendre des notes, générer un aperçu de la date d'échéance,\n\n### Déplacer des epics d’un groupe à l’autre\n\nVous devez parfois déplacer des epics dans un autre groupe. Une question posée dans le Slack de GitLab nous a incité à examiner une [proposition de fonctionnalité pour l'interface utilisateur](https://gitlab.com/gitlab-org/gitlab/-/issues/12689), pour plus tard écrire un script API permettant d'automatiser ces étapes. L'idée consiste à déplacer une epic d'un groupe source vers un groupe cible, et de copier son titre, sa description et ses labels. Puisque les epics permettent de regrouper les tickets, elles doivent également être réaffectées à l'epic cible. Il faut aussi prendre en compte les relations parent-enfant des epics, toutes les epics enfants des epics sources devant être réaffectées à l'epic cible.\n\nLe script suivant recherche d'abord tous les attributs de l'epic source, puis crée une nouvelle epic cible avec des attributs minimaux : titre et description. La liste des labels est copiée et les modifications sont conservées grâce à l'appel `save()`. Les tickets attribués à l'epic doivent être recréés dans l'epic cible. L'appel `create()` crée l'élément de relation, et non un nouvel objet de ticket en tant que tel. Le déplacement des epics enfants nécessite une approche différente, car la relation est inversée : le `parent_id` de l'epic enfant doit être comparé à l'identifiant de l'epic source et, s'il correspond, mis à jour vers l'identifiant de l'epic cible. Après avoir tout copié avec succès, l'epic source doit être passée à l'état `closed`.\n\n```python\n#!/usr/bin/env python\n\n# Description: Show how epics can be moved between groups, including title, description, labels, child epics and issues.\n# Requirements: python-gitlab Python libraries. GitLab API write access, and maintainer access to all configured groups/projects.\n# Author: Michael Friedrich \u003Cmfriedrich@gitlab.com>\n# License: MIT, (c) 2023-present GitLab B.V.\n\nimport gitlab\nimport os\nimport sys\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\n# https://gitlab.com/gitlab-da/use-cases/gitlab-api\nSOURCE_GROUP_ID = os.environ.get('GL_SOURCE_GROUP_ID', 62378643)\n# https://gitlab.com/gitlab-da/use-cases/gitlab-api/epic-move-target\nTARGET_GROUP_ID = os.environ.get('GL_TARGET_GROUP_ID', 62742177)\n# https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1\nEPIC_ID = os.environ.get('GL_EPIC_ID', 1)\nGITLAB_TOKEN = os.environ.get('GL_TOKEN')\n\nif not GITLAB_TOKEN:\n    print(\"Please set the GL_TOKEN env variable.\")\n    sys.exit(1)\n\ngl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)\n\n# Main\n# Goal: Move epic to target group, including title, body, labels, and child epics and issues.\nsource_group = gl.groups.get(SOURCE_GROUP_ID)\ntarget_group = gl.groups.get(TARGET_GROUP_ID)\n\n# Create a new target epic and copy all its items, then close the source epic.\nsource_epic = source_group.epics.get(EPIC_ID)\n# print(source_epic) #debug\n\nepic_title = source_epic.title\nepic_description = source_epic.description\nepic_labels = source_epic.labels\nepic_issues = source_epic.issues.list()\n\n# Create the epic with minimal attributes\ntarget_epic = target_group.epics.create({\n    'title': epic_title,\n    'description': epic_description,\n})\n\n# Assign the list\ntarget_epic.labels = epic_labels\n\n# Persist the changes in the new epic\ntarget_epic.save()\n\n# Epic issues need to be re-assigned in a loop\nfor epic_issue in epic_issues:\n    ei = target_epic.issues.create({'issue_id': epic_issue.id})\n\n# Child epics need to update their parent_id to the new epic\n# Need to search in all epics, use lazy object loading\nfor sge in source_group.epics.list(lazy=True):\n    # this epic has the source epic as parent epic?\n    if sge.parent_id == source_epic.id:\n        # Update the parent id\n        sge.parent_id = target_epic.id\n        sge.save()\n\nprint(\"Copied source epic {source_id} ({source_url}) to target epic {target_id} ({target_url})\".format(\n    source_id=source_epic.id, source_url=source_epic.web_url,\n    target_id=target_epic.id, target_url=target_epic.web_url))\n\n# Close the old epic\nsource_epic.state_event = 'close'\nsource_epic.save()\nprint(\"Closed source epic {source_id} ({source_url})\".format(\n    source_id=source_epic.id, source_url=source_epic.web_url))\n```\n\n```shell\n$  python3 move_epic_between_groups.py\nCopied source epic 725341 (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1) to target epic 725358 (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/6)\nClosed source epic 725341 (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1)\n```\n\nL'[epic cible](https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/5 \"Epic cible\") a été créée et affiche le résultat attendu : même titre, description, labels, epic enfant et tickets. \n\n![Tutoriel sur l'API GitLab : déplacer des epics](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_moved_epic_with_all_attributes.png){: .shadow}\n\n__Exercice :__ le script ne copie pas encore les [commentaires](https://python-gitlab.readthedocs.io/en/stable/gl_objects/notes.html) et les [fils de discussion](https://python-gitlab.readthedocs.io/en/stable/gl_objects/discussions.html). Faites des recherches et aidez-nous à mettre à jour le script. Les merge requests sont les bienvenues !\n\n### Conformité : vérifier que les paramètres du projet ne sont pas remplacés\n\nLes paramètres des projets et des groupes peuvent être modifiés accidentellement par des membres de l'équipe. Les exigences de conformité doivent être respectées. Autre cas d’utilisation : gérer la configuration avec des outils d’[Infrastructure as Code](https://about.gitlab.com/fr-fr/topics/gitops/infrastructure-as-code/ \"Infrastructure as Code\") et s'assurer que la configuration de GitLab reste la même au niveau du groupe, du projet et autres. Des outils comme Ansible ou Terraform peuvent invoquer un script API ou utiliser la bibliothèque python-gitlab pour effectuer des tâches de gestion des paramètres.\n\nDans l'exemple suivant, seule la branche `main` est protégée.\n\n![API python-gitlab : protection des branches](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main.png){: .shadow}\n\nSupposons qu'une nouvelle branche `production` a été ajoutée et qu’elle doit également être protégée. Le script suivant définit le dictionnaire des branches protégées et leurs niveaux d'accès pour les autorisations de push et de fusion au niveau du chargé de maintenance. Il établit la logique de comparaison de la [documentation python-gitlab sur les branches protégées](https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html).\n\n```python\n#!/usr/bin/env python\n\nimport gitlab\nimport os\nimport sys\n\nGITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')\n# https://gitlab.com/gitlab-da/use-cases/\nGROUP_ID = os.environ.get('GL_GROUP_ID', 16058698)\nGITLAB_TOKEN = os.environ.get('GL_TOKEN')\n\nPROTECTED_BRANCHES = {\n    'main': {\n        'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,\n        'push_access_level': gitlab.const.AccessLevel.MAINTAINER\n    },\n    'production': {\n        'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,\n        'push_access_level': gitlab.const.AccessLevel.MAINTAINER\n    },\n}\n\nif not GITLAB_TOKEN:\n    print(\"Please set the GL_TOKEN env variable.\")\n    sys.exit(1)\n\ngl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)\n\n# Main\ngroup = gl.groups.get(GROUP_ID)\n\n# Collect all projects in group and subgroups\nprojects = group.projects.list(include_subgroups=True, all=True)\n\nfor project in projects:\n    # Retrieve a full manageable project object\n    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n    manageable_project = gl.projects.get(project.id)\n\n    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html\n    protected_branch_names = []\n\n    for pb in manageable_project.protectedbranches.list():\n        manageable_protected_branch = manageable_project.protectedbranches.get(pb.name)\n        print(\"Protected branch name: {n}, merge_access_level: {mal}, push_access_level: {pal}\".format(\n            n=manageable_protected_branch.name,\n            mal=manageable_protected_branch.merge_access_levels,\n            pal=manageable_protected_branch.push_access_levels\n        ))\n\n        protected_branch_names.append(manageable_protected_branch.name)\n\n    for branch_to_protect, levels in PROTECTED_BRANCHES.items():\n        # Fix missing protected branches\n        if branch_to_protect not in protected_branch_names:\n            print(\"Adding branch {n} to protected branches settings\".format(n=branch_to_protect))\n            p_branch = manageable_project.protectedbranches.create({\n                'name': branch_to_protect,\n                'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,\n                'push_access_level': gitlab.const.AccessLevel.MAINTAINER\n            })\n```\n\nL'exécution du script affiche la branche `main` existante, ainsi qu’une note indiquant que la branche `production` sera mise à jour. La capture d'écran des paramètres du dépôt démontre cette action.\n\n```\n$ python3 enforce_protected_branches.py                                                ─╯\nProtected branch name: main, merge_access_level: [{'id': 67294702, 'access_level': 40, 'access_level_description': 'Maintainers', 'user_id': None, 'group_id': None}], push_access_level: [{'id': 68546039, 'access_level': 40, 'access_level_description': 'Maintainers', 'user_id': None, 'group_id': None}]\nAdding branch production to protected branches settings\n```\n\n![Capture d'écran de code en Python avec GitLab](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main_production.png){: .shadow}\n\n### Prise de notes : générer un aperçu de la date d'échéance\n\nUne [discussion de Hacker News sur les outils de prise de notes](https://news.ycombinator.com/item?id=32155848) nous a inspiré la création d'un tableau Markdown, extrait de fichiers de prise de notes, et trié par date d'échéance. Le script est plus complexe à comprendre.\n","engineering",[23,24,25,26],"integrations","tutorial","DevSecOps","DevSecOps platform",{"slug":28,"featured":6,"template":29},"efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","BlogPost","content:fr-fr:blog:efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml","yaml","Efficient Devsecops Workflows Hands On Python Gitlab Api Automation","content","fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml","fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","yml",{"_path":38,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":40,"_id":452,"_type":31,"title":453,"_source":33,"_file":454,"_stem":455,"_extension":36},"/shared/fr-fr/main-navigation","fr-fr",{"logo":41,"freeTrial":46,"sales":51,"login":56,"items":61,"search":393,"minimal":429,"duo":443},{"config":42},{"href":43,"dataGaName":44,"dataGaLocation":45},"/fr-fr/","gitlab logo","header",{"text":47,"config":48},"Commencer un essai gratuit",{"href":49,"dataGaName":50,"dataGaLocation":45},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":52,"config":53},"Contacter l'équipe commerciale",{"href":54,"dataGaName":55,"dataGaLocation":45},"/fr-fr/sales/","sales",{"text":57,"config":58},"Connexion",{"href":59,"dataGaName":60,"dataGaLocation":45},"https://gitlab.com/users/sign_in/","sign in",[62,106,205,210,314,374],{"text":63,"config":64,"cards":66,"footer":89},"Plateforme",{"dataNavLevelOne":65},"platform",[67,73,81],{"title":63,"description":68,"link":69},"La plateforme DevSecOps alimentée par l'IA la plus complète",{"text":70,"config":71},"Découvrir notre plateforme",{"href":72,"dataGaName":65,"dataGaLocation":45},"/fr-fr/platform/",{"title":74,"description":75,"link":76},"GitLab Duo (IA)","Créez des logiciels plus rapidement en tirant parti de l'IA à chaque étape du développement",{"text":77,"config":78},"Découvrez GitLab Duo",{"href":79,"dataGaName":80,"dataGaLocation":45},"/fr-fr/gitlab-duo/","gitlab duo ai",{"title":82,"description":83,"link":84},"Choisir GitLab","10 raisons pour lesquelles les entreprises choisissent GitLab",{"text":85,"config":86},"En savoir plus",{"href":87,"dataGaName":88,"dataGaLocation":45},"/fr-fr/why-gitlab/","why gitlab",{"title":90,"items":91},"Démarrer avec",[92,97,102],{"text":93,"config":94},"Ingénierie de plateforme",{"href":95,"dataGaName":96,"dataGaLocation":45},"/fr-fr/solutions/platform-engineering/","platform engineering",{"text":98,"config":99},"Expérience développeur",{"href":100,"dataGaName":101,"dataGaLocation":45},"/fr-fr/developer-experience/","Developer experience",{"text":103,"config":104},"MLOps",{"href":105,"dataGaName":103,"dataGaLocation":45},"/fr-fr/topics/devops/the-role-of-ai-in-devops/",{"text":107,"left":108,"config":109,"link":111,"lists":115,"footer":187},"Produit",true,{"dataNavLevelOne":110},"solutions",{"text":112,"config":113},"Voir toutes les solutions",{"href":114,"dataGaName":110,"dataGaLocation":45},"/fr-fr/solutions/",[116,142,165],{"title":117,"description":118,"link":119,"items":124},"Automatisation","CI/CD et automatisation pour accélérer le déploiement",{"config":120},{"icon":121,"href":122,"dataGaName":123,"dataGaLocation":45},"AutomatedCodeAlt","/fr-fr/solutions/delivery-automation/","automated software delivery",[125,129,133,138],{"text":126,"config":127},"CI/CD",{"href":128,"dataGaLocation":45,"dataGaName":126},"/fr-fr/solutions/continuous-integration/",{"text":130,"config":131},"Développement assisté par l'IA",{"href":79,"dataGaLocation":45,"dataGaName":132},"AI assisted development",{"text":134,"config":135},"Gestion du code source",{"href":136,"dataGaLocation":45,"dataGaName":137},"/fr-fr/solutions/source-code-management/","Source Code Management",{"text":139,"config":140},"Livraison de logiciels automatisée",{"href":122,"dataGaLocation":45,"dataGaName":141},"Automated software delivery",{"title":143,"description":144,"link":145,"items":150},"Securité","Livrez du code plus rapidement sans compromettre la sécurité",{"config":146},{"href":147,"dataGaName":148,"dataGaLocation":45,"icon":149},"/fr-fr/solutions/security-compliance/","security and compliance","ShieldCheckLight",[151,155,160],{"text":152,"config":153},"Sécurité et conformité",{"href":147,"dataGaLocation":45,"dataGaName":154},"Security & Compliance",{"text":156,"config":157},"Sécurité de la chaîne d'approvisionnement logicielle",{"href":158,"dataGaLocation":45,"dataGaName":159},"/fr-fr/solutions/supply-chain/","Software supply chain security",{"text":161,"config":162},"Conformité et gouvernance",{"href":163,"dataGaLocation":45,"dataGaName":164},"/fr-fr/solutions/continuous-software-compliance/","Compliance and governance",{"title":166,"link":167,"items":172},"Mesures",{"config":168},{"icon":169,"href":170,"dataGaName":171,"dataGaLocation":45},"DigitalTransformation","/fr-fr/solutions/visibility-measurement/","visibility and measurement",[173,177,182],{"text":174,"config":175},"Visibilité et mesures",{"href":170,"dataGaLocation":45,"dataGaName":176},"Visibility and Measurement",{"text":178,"config":179},"Gestion de la chaîne de valeur",{"href":180,"dataGaLocation":45,"dataGaName":181},"/fr-fr/solutions/value-stream-management/","Value Stream Management",{"text":183,"config":184},"Données d'analyse et informations clés",{"href":185,"dataGaLocation":45,"dataGaName":186},"/fr-fr/solutions/analytics-and-insights/","Analytics and insights",{"title":188,"items":189},"GitLab pour",[190,195,200],{"text":191,"config":192},"Entreprises",{"href":193,"dataGaLocation":45,"dataGaName":194},"/fr-fr/enterprise/","enterprise",{"text":196,"config":197},"PME",{"href":198,"dataGaLocation":45,"dataGaName":199},"/fr-fr/small-business/","small business",{"text":201,"config":202},"Secteur public",{"href":203,"dataGaLocation":45,"dataGaName":204},"/fr-fr/solutions/public-sector/","public sector",{"text":206,"config":207},"Tarifs",{"href":208,"dataGaName":209,"dataGaLocation":45,"dataNavLevelOne":209},"/fr-fr/pricing/","pricing",{"text":211,"config":212,"link":214,"lists":218,"feature":301},"Ressources",{"dataNavLevelOne":213},"resources",{"text":215,"config":216},"Afficher toutes les ressources",{"href":217,"dataGaName":213,"dataGaLocation":45},"/fr-fr/resources/",[219,251,273],{"title":220,"items":221},"Premiers pas",[222,227,232,237,242,247],{"text":223,"config":224},"Installation",{"href":225,"dataGaName":226,"dataGaLocation":45},"/fr-fr/install/","install",{"text":228,"config":229},"Guides de démarrage rapide",{"href":230,"dataGaName":231,"dataGaLocation":45},"/fr-fr/get-started/","quick setup checklists",{"text":233,"config":234},"Apprentissage",{"href":235,"dataGaLocation":45,"dataGaName":236},"https://university.gitlab.com/","learn",{"text":238,"config":239},"Documentation sur le produit",{"href":240,"dataGaName":241,"dataGaLocation":45},"https://docs.gitlab.com/","product documentation",{"text":243,"config":244},"Vidéos sur les bonnes pratiques",{"href":245,"dataGaName":246,"dataGaLocation":45},"/fr-fr/getting-started-videos/","best practice videos",{"text":248,"config":249},"Intégrations",{"href":250,"dataGaName":23,"dataGaLocation":45},"/fr-fr/integrations/",{"title":252,"items":253},"Découvrir",[254,259,263,268],{"text":255,"config":256},"Histoires de succès client",{"href":257,"dataGaName":258,"dataGaLocation":45},"/fr-fr/customers/","customer success stories",{"text":260,"config":261},"Blog",{"href":262,"dataGaName":5,"dataGaLocation":45},"/fr-fr/blog/",{"text":264,"config":265},"Travail à distance",{"href":266,"dataGaName":267,"dataGaLocation":45},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":269,"config":270},"TeamOps",{"href":271,"dataGaName":272,"dataGaLocation":45},"/fr-fr/teamops/","teamops",{"title":274,"items":275},"Connecter",[276,281,286,291,296],{"text":277,"config":278},"Services GitLab",{"href":279,"dataGaName":280,"dataGaLocation":45},"/fr-fr/services/","services",{"text":282,"config":283},"Communauté",{"href":284,"dataGaName":285,"dataGaLocation":45},"/community/","community",{"text":287,"config":288},"Forum",{"href":289,"dataGaName":290,"dataGaLocation":45},"https://forum.gitlab.com/","forum",{"text":292,"config":293},"Événements",{"href":294,"dataGaName":295,"dataGaLocation":45},"/events/","events",{"text":297,"config":298},"Partenaires",{"href":299,"dataGaName":300,"dataGaLocation":45},"/fr-fr/partners/","partners",{"backgroundColor":302,"textColor":303,"text":304,"image":305,"link":309},"#2f2a6b","#fff","L'avenir du développement logiciel. Tendances et perspectives.",{"altText":306,"config":307},"carte promo The Source",{"src":308},"/images/navigation/the-source-promo-card.svg",{"text":310,"config":311},"Lire les articles les plus récents",{"href":312,"dataGaName":313,"dataGaLocation":45},"/fr-fr/the-source/","the source",{"text":315,"config":316,"lists":318},"Société",{"dataNavLevelOne":317},"company",[319],{"items":320},[321,326,332,334,339,344,349,354,359,364,369],{"text":322,"config":323},"À propos",{"href":324,"dataGaName":325,"dataGaLocation":45},"/fr-fr/company/","about",{"text":327,"config":328,"footerGa":331},"Emplois",{"href":329,"dataGaName":330,"dataGaLocation":45},"/jobs/","jobs",{"dataGaName":330},{"text":292,"config":333},{"href":294,"dataGaName":295,"dataGaLocation":45},{"text":335,"config":336},"Leadership",{"href":337,"dataGaName":338,"dataGaLocation":45},"/company/team/e-group/","leadership",{"text":340,"config":341},"Équipe",{"href":342,"dataGaName":343,"dataGaLocation":45},"/company/team/","team",{"text":345,"config":346},"Manuel",{"href":347,"dataGaName":348,"dataGaLocation":45},"https://handbook.gitlab.com/","handbook",{"text":350,"config":351},"Relations avec les investisseurs",{"href":352,"dataGaName":353,"dataGaLocation":45},"https://ir.gitlab.com/","investor relations",{"text":355,"config":356},"Centre de confiance",{"href":357,"dataGaName":358,"dataGaLocation":45},"/fr-fr/security/","trust center",{"text":360,"config":361},"Centre pour la transparence de l'IA",{"href":362,"dataGaName":363,"dataGaLocation":45},"/fr-fr/ai-transparency-center/","ai transparency center",{"text":365,"config":366},"Newsletter",{"href":367,"dataGaName":368,"dataGaLocation":45},"/company/contact/","newsletter",{"text":370,"config":371},"Presse",{"href":372,"dataGaName":373,"dataGaLocation":45},"/press/","press",{"text":375,"config":376,"lists":377},"Nous contacter",{"dataNavLevelOne":317},[378],{"items":379},[380,383,388],{"text":52,"config":381},{"href":54,"dataGaName":382,"dataGaLocation":45},"talk to sales",{"text":384,"config":385},"Aide",{"href":386,"dataGaName":387,"dataGaLocation":45},"/support/","get help",{"text":389,"config":390},"Portail clients GitLab",{"href":391,"dataGaName":392,"dataGaLocation":45},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":394,"login":395,"suggestions":402},"Fermer",{"text":396,"link":397},"Pour rechercher des dépôts et des projets, connectez-vous à",{"text":398,"config":399},"gitlab.com",{"href":59,"dataGaName":400,"dataGaLocation":401},"search login","search",{"text":403,"default":404},"Suggestions",[405,408,413,415,420,425],{"text":74,"config":406},{"href":79,"dataGaName":407,"dataGaLocation":401},"GitLab Duo (AI)",{"text":409,"config":410},"Suggestions de code (IA)",{"href":411,"dataGaName":412,"dataGaLocation":401},"/fr-fr/solutions/code-suggestions/","Code Suggestions (AI)",{"text":126,"config":414},{"href":128,"dataGaName":126,"dataGaLocation":401},{"text":416,"config":417},"GitLab sur AWS",{"href":418,"dataGaName":419,"dataGaLocation":401},"/fr-fr/partners/technology-partners/aws/","GitLab on AWS",{"text":421,"config":422},"GitLab sur Google Cloud ",{"href":423,"dataGaName":424,"dataGaLocation":401},"/fr-fr/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":426,"config":427},"Pourquoi utiliser GitLab ?",{"href":87,"dataGaName":428,"dataGaLocation":401},"Why GitLab?",{"freeTrial":430,"mobileIcon":435,"desktopIcon":440},{"text":431,"config":432},"Commencer votre essai gratuit",{"href":433,"dataGaName":50,"dataGaLocation":434},"https://gitlab.com/-/trials/new/","nav",{"altText":436,"config":437},"Icône GitLab",{"src":438,"dataGaName":439,"dataGaLocation":434},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":436,"config":441},{"src":442,"dataGaName":439,"dataGaLocation":434},"/images/brand/gitlab-logo-type.svg",{"freeTrial":444,"mobileIcon":448,"desktopIcon":450},{"text":445,"config":446},"En savoir plus sur GitLab Duo",{"href":79,"dataGaName":447,"dataGaLocation":434},"gitlab duo",{"altText":436,"config":449},{"src":438,"dataGaName":439,"dataGaLocation":434},{"altText":436,"config":451},{"src":442,"dataGaName":439,"dataGaLocation":434},"content:shared:fr-fr:main-navigation.yml","Main Navigation","shared/fr-fr/main-navigation.yml","shared/fr-fr/main-navigation",{"_path":457,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"title":458,"titleMobile":458,"button":459,"config":463,"_id":465,"_type":31,"_source":33,"_file":466,"_stem":467,"_extension":36},"/shared/fr-fr/banner","La plateforme GitLab Duo Agent est maintenant en bêta publique !",{"text":85,"config":460},{"href":461,"dataGaName":462,"dataGaLocation":45},"/fr-fr/gitlab-duo/agent-platform/","duo banner",{"layout":464},"release","content:shared:fr-fr:banner.yml","shared/fr-fr/banner.yml","shared/fr-fr/banner",{"_path":469,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":470,"_id":676,"_type":31,"title":677,"_source":33,"_file":678,"_stem":679,"_extension":36},"/shared/fr-fr/main-footer",{"text":471,"source":472,"edit":478,"contribute":483,"config":488,"items":493,"minimal":667},"Git est une marque déposée de Software Freedom Conservancy et notre utilisation de « GitLab » est sous licence",{"text":473,"config":474},"Afficher le code source de la page",{"href":475,"dataGaName":476,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":479,"config":480},"Modifier cette page",{"href":481,"dataGaName":482,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":484,"config":485},"Veuillez contribuer",{"href":486,"dataGaName":487,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":489,"facebook":490,"youtube":491,"linkedin":492},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[494,517,571,604,638],{"title":63,"links":495,"subMenu":500},[496],{"text":497,"config":498},"Plateforme DevSecOps",{"href":72,"dataGaName":499,"dataGaLocation":477},"devsecops platform",[501],{"title":206,"links":502},[503,507,512],{"text":504,"config":505},"Voir les forfaits",{"href":208,"dataGaName":506,"dataGaLocation":477},"view plans",{"text":508,"config":509},"Pourquoi choisir GitLab Premium ?",{"href":510,"dataGaName":511,"dataGaLocation":477},"/fr-fr/pricing/premium/","why premium",{"text":513,"config":514},"Pourquoi choisir GitLab Ultimate ?",{"href":515,"dataGaName":516,"dataGaLocation":477},"/fr-fr/pricing/ultimate/","why ultimate",{"title":518,"links":519},"Solutions",[520,525,528,530,535,540,544,547,550,555,557,559,561,566],{"text":521,"config":522},"Transformation digitale",{"href":523,"dataGaName":524,"dataGaLocation":477},"/fr-fr/topics/digital-transformation/","digital transformation",{"text":152,"config":526},{"href":147,"dataGaName":527,"dataGaLocation":477},"security & compliance",{"text":139,"config":529},{"href":122,"dataGaName":123,"dataGaLocation":477},{"text":531,"config":532},"Développement agile",{"href":533,"dataGaName":534,"dataGaLocation":477},"/fr-fr/solutions/agile-delivery/","agile delivery",{"text":536,"config":537},"Transformation cloud",{"href":538,"dataGaName":539,"dataGaLocation":477},"/fr-fr/topics/cloud-native/","cloud transformation",{"text":541,"config":542},"SCM",{"href":136,"dataGaName":543,"dataGaLocation":477},"source code management",{"text":126,"config":545},{"href":128,"dataGaName":546,"dataGaLocation":477},"continuous integration & delivery",{"text":178,"config":548},{"href":180,"dataGaName":549,"dataGaLocation":477},"value stream management",{"text":551,"config":552},"GitOps",{"href":553,"dataGaName":554,"dataGaLocation":477},"/fr-fr/solutions/gitops/","gitops",{"text":191,"config":556},{"href":193,"dataGaName":194,"dataGaLocation":477},{"text":196,"config":558},{"href":198,"dataGaName":199,"dataGaLocation":477},{"text":201,"config":560},{"href":203,"dataGaName":204,"dataGaLocation":477},{"text":562,"config":563},"Formation",{"href":564,"dataGaName":565,"dataGaLocation":477},"/fr-fr/solutions/education/","education",{"text":567,"config":568},"Services financiers",{"href":569,"dataGaName":570,"dataGaLocation":477},"/fr-fr/solutions/finance/","financial services",{"title":211,"links":572},[573,575,577,579,582,584,588,590,592,594,596,598,600,602],{"text":223,"config":574},{"href":225,"dataGaName":226,"dataGaLocation":477},{"text":228,"config":576},{"href":230,"dataGaName":231,"dataGaLocation":477},{"text":233,"config":578},{"href":235,"dataGaName":236,"dataGaLocation":477},{"text":238,"config":580},{"href":240,"dataGaName":581,"dataGaLocation":477},"docs",{"text":260,"config":583},{"href":262,"dataGaName":5},{"text":585,"config":586},"Histoires de réussite client",{"href":587,"dataGaLocation":477},"/customers/",{"text":255,"config":589},{"href":257,"dataGaName":258,"dataGaLocation":477},{"text":264,"config":591},{"href":266,"dataGaName":267,"dataGaLocation":477},{"text":277,"config":593},{"href":279,"dataGaName":280,"dataGaLocation":477},{"text":269,"config":595},{"href":271,"dataGaName":272,"dataGaLocation":477},{"text":282,"config":597},{"href":284,"dataGaName":285,"dataGaLocation":477},{"text":287,"config":599},{"href":289,"dataGaName":290,"dataGaLocation":477},{"text":292,"config":601},{"href":294,"dataGaName":295,"dataGaLocation":477},{"text":297,"config":603},{"href":299,"dataGaName":300,"dataGaLocation":477},{"title":315,"links":605},[606,608,610,612,614,616,618,622,627,629,631,633],{"text":322,"config":607},{"href":324,"dataGaName":317,"dataGaLocation":477},{"text":327,"config":609},{"href":329,"dataGaName":330,"dataGaLocation":477},{"text":335,"config":611},{"href":337,"dataGaName":338,"dataGaLocation":477},{"text":340,"config":613},{"href":342,"dataGaName":343,"dataGaLocation":477},{"text":345,"config":615},{"href":347,"dataGaName":348,"dataGaLocation":477},{"text":350,"config":617},{"href":352,"dataGaName":353,"dataGaLocation":477},{"text":619,"config":620},"Sustainability",{"href":621,"dataGaName":619,"dataGaLocation":477},"/sustainability/",{"text":623,"config":624},"Diversité, inclusion et appartenance (DIB)",{"href":625,"dataGaName":626,"dataGaLocation":477},"/fr-fr/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":355,"config":628},{"href":357,"dataGaName":358,"dataGaLocation":477},{"text":365,"config":630},{"href":367,"dataGaName":368,"dataGaLocation":477},{"text":370,"config":632},{"href":372,"dataGaName":373,"dataGaLocation":477},{"text":634,"config":635},"Déclaration de transparence sur l'esclavage moderne",{"href":636,"dataGaName":637,"dataGaLocation":477},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":375,"links":639},[640,643,645,647,652,657,662],{"text":641,"config":642},"Échanger avec un expert",{"href":54,"dataGaName":55,"dataGaLocation":477},{"text":384,"config":644},{"href":386,"dataGaName":387,"dataGaLocation":477},{"text":389,"config":646},{"href":391,"dataGaName":392,"dataGaLocation":477},{"text":648,"config":649},"Statut",{"href":650,"dataGaName":651,"dataGaLocation":477},"https://status.gitlab.com/","status",{"text":653,"config":654},"Conditions d'utilisation",{"href":655,"dataGaName":656},"/terms/","terms of use",{"text":658,"config":659},"Déclaration de confidentialité",{"href":660,"dataGaName":661,"dataGaLocation":477},"/fr-fr/privacy/","privacy statement",{"text":663,"config":664},"Préférences en matière de cookies",{"dataGaName":665,"dataGaLocation":477,"id":666,"isOneTrustButton":108},"cookie preferences","ot-sdk-btn",{"items":668},[669,671,674],{"text":653,"config":670},{"href":655,"dataGaName":656,"dataGaLocation":477},{"text":672,"config":673},"Politique de confidentialité",{"href":660,"dataGaName":661,"dataGaLocation":477},{"text":663,"config":675},{"dataGaName":665,"dataGaLocation":477,"id":666,"isOneTrustButton":108},"content:shared:fr-fr:main-footer.yml","Main Footer","shared/fr-fr/main-footer.yml","shared/fr-fr/main-footer",[681],{"_path":682,"_dir":683,"_draft":6,"_partial":6,"_locale":7,"content":684,"config":688,"_id":690,"_type":31,"title":18,"_source":33,"_file":691,"_stem":692,"_extension":36},"/en-us/blog/authors/michael-friedrich","authors",{"name":18,"config":685},{"headshot":686,"ctfId":687},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{"template":689},"BlogAuthor","content:en-us:blog:authors:michael-friedrich.yml","en-us/blog/authors/michael-friedrich.yml","en-us/blog/authors/michael-friedrich",{"_path":694,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"header":695,"eyebrow":696,"blurb":697,"button":698,"secondaryButton":702,"_id":704,"_type":31,"title":705,"_source":33,"_file":706,"_stem":707,"_extension":36},"/shared/fr-fr/next-steps","Commencez à livrer des logiciels de meilleurs qualité plus rapidement","Plus de 50 % des entreprises du classement Fortune 100 font confiance à GitLab","Découvrez comment la plateforme DevSecOps intelligente\n\n\npeut aider votre équipe.\n",{"text":47,"config":699},{"href":700,"dataGaName":50,"dataGaLocation":701},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":52,"config":703},{"href":54,"dataGaName":55,"dataGaLocation":701},"content:shared:fr-fr:next-steps.yml","Next Steps","shared/fr-fr/next-steps.yml","shared/fr-fr/next-steps",1753981668273]