[{"data":1,"prerenderedAt":7462},["ShallowReactive",2],{"/fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation":3,"navigation-fr-fr":6680,"banner-fr-fr":7113,"footer-fr-fr":7121,"blog-post-authors-fr-fr-Michael Friedrich":7359,"blog-related-posts-fr-fr-efficient-devsecops-workflows-hands-on-python-gitlab-api-automation":7374,"blog-promotions-fr-fr":7399,"next-steps-fr-fr":7453},{"id":4,"title":5,"authors":6,"body":8,"category":6659,"date":6660,"description":6661,"extension":6662,"externalUrl":6663,"faq":6663,"featured":6664,"heroImage":6665,"meta":6666,"navigation":118,"path":6667,"seo":6668,"slug":6672,"stem":6673,"tags":6674,"template":6678,"updatedDate":6663,"__hash__":6679},"blogPosts/fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.md","Utiliser l'API python-gitlab pour améliorer vos workflows DevSecOps",[7],"Michael Friedrich",{"type":9,"value":10,"toc":6636},"minimark",[11,15,31,41,44,47,52,69,85,88,194,197,207,211,218,235,244,255,309,312,353,368,471,474,478,481,490,497,558,561,577,582,591,598,1154,1170,1246,1254,1258,1282,1293,1658,1670,1743,1747,1754,1761,2028,2031,2205,2212,2216,2223,2393,2402,2406,2412,2415,2435,2439,2473,2785,2788,2792,2798,3036,3040,3050,3057,3847,3855,3859,3867,4127,4131,4147,4605,4611,4615,4631,4639,4664,5137,5141,5149,5152,5163,5167,5176,5194,5832,5883,5893,5901,5920,5924,5933,5940,5946,5958,6484,6493,6613,6619,6623,6632],[12,13,14],"p",{},"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.",[12,16,17,18,24,25,30],{},"Par exemple, en interrogeant une ",[19,20,23],"a",{"href":21,"rel":22,"title":23},"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 ",[19,26,29],{"href":27,"rel":28,"title":29},"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.",[12,32,33,34,40],{},"Dans 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 ",[19,35,39],{"href":36,"rel":37,"title":38},"https://python-gitlab.readthedocs.io/en/stable/",[],"Bibliothèque python-gitlab","bibliothèque python-gitlab"," est une bibliothèque écrite en Python, riche en fonctionnalités et facile à utiliser.",[12,42,43],{},"Dans 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.",[12,45,46],{},"Il 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.",[48,49,51],"h2",{"id":50},"premiers-pas","Premiers pas",[12,53,54,55,61,62,68],{},"La ",[19,56,60],{"href":57,"rel":58,"title":59},"https://python-gitlab.readthedocs.io/en/stable/api-usage.html",[],"Documentation python-gitlab","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 ",[19,63,67],{"href":64,"rel":65,"title":66},"https://docs.gitlab.com/api/api_resources/",[],"Documentation sur les ressources de l'API GitLab","documentation sur les ressources de l'API GitLab"," qui fournit les attributs d'objet associés.",[12,70,71,72,76,77,80,81,84],{},"Les exemples de code présentés dans cet article nécessitent Python 3.8+ et la bibliothèque ",[73,74,75],"code",{},"python-gitlab",". Des exigences supplémentaires sont spécifiées dans le fichier ",[73,78,79],{},"requirements.txt",". Un exemple nécessite ",[73,82,83],{},"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.",[12,86,87],{},"Exemple avec Homebrew sur macOS :",[89,90,95],"pre",{"className":91,"code":92,"language":93,"meta":94,"style":94},"language-shell shiki shiki-themes github-light","git clone https://gitlab.com/gitlab-da/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","shell","",[73,96,97,113,120,130,135,147,152,166,171],{"__ignoreMap":94},[98,99,102,106,110],"span",{"class":100,"line":101},"line",1,[98,103,105],{"class":104},"s7eDp","git",[98,107,109],{"class":108},"sYBdl"," clone",[98,111,112],{"class":108}," https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python.git\n",[98,114,116],{"class":100,"line":115},2,[98,117,119],{"emptyLinePlaceholder":118},true,"\n",[98,121,123,127],{"class":100,"line":122},3,[98,124,126],{"class":125},"sYu0t","cd",[98,128,129],{"class":108}," gitlab-api-python\n",[98,131,133],{"class":100,"line":132},4,[98,134,119],{"emptyLinePlaceholder":118},[98,136,138,141,144],{"class":100,"line":137},5,[98,139,140],{"class":104},"brew",[98,142,143],{"class":108}," install",[98,145,146],{"class":108}," python\n",[98,148,150],{"class":100,"line":149},6,[98,151,119],{"emptyLinePlaceholder":118},[98,153,155,158,160,163],{"class":100,"line":154},7,[98,156,157],{"class":104},"pip3",[98,159,143],{"class":108},[98,161,162],{"class":125}," -r",[98,164,165],{"class":108}," requirements.txt\n",[98,167,169],{"class":100,"line":168},8,[98,170,119],{"emptyLinePlaceholder":118},[98,172,174,177,181,184,188,191],{"class":100,"line":173},9,[98,175,176],{"class":104},"python3",[98,178,180],{"class":179},"sD7c4"," \u003C",[98,182,183],{"class":108},"scriptnam",[98,185,187],{"class":186},"sgsFI","e",[98,189,190],{"class":179},">",[98,192,193],{"class":108},".py\n",[12,195,196],{},"Les 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.",[12,198,199,200,206],{},"Nous 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 ",[19,201,205],{"href":202,"rel":203,"title":204},"https://about.gitlab.com/fr-fr/topics/devsecops/",[],"Qu'est-ce que DevSecOps ?","DevSecOps",".",[48,208,210],{"id":209},"configuration","Configuration",[12,212,213,214,217],{},"Sans configuration, python-gitlab exécutera des requêtes non authentifiées sur le serveur par défaut : ",[73,215,216],{},"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.",[12,219,220,221,227,228,234],{},"Le ",[19,222,226],{"href":223,"rel":224,"title":225},"https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#cli-configuration",[],"Fichier de configuration CLI","fichier de configuration"," 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 ",[19,229,233],{"href":230,"rel":231,"title":232},"https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#credential-helpers",[],"Credential helpers","credential helpers"," pour accéder directement aux jetons.",[12,236,237,238,206],{},"Les 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 ",[19,239,243],{"href":240,"rel":241,"title":242},"https://about.gitlab.com/fr-fr/topics/ci-cd/cicd-pipeline/",[],"Pipeline CI/CD","pipelines CI/CD",[12,245,246,247,250,251,254],{},"Vous devez lancer la configuration dans le contexte du script Python. Importez la bibliothèque ",[73,248,249],{},"os"," pour récupérer les variables d'environnement à l'aide de la méthode ",[73,252,253],{},"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.",[89,256,260],{"className":257,"code":258,"language":259,"meta":94,"style":94},"language-python shiki shiki-themes github-light","import os\n\ngl_server = os.environ.get('GL_SERVER', 'https://gitlab.com')\n\nprint(gl_server)\n","python",[73,261,262,270,274,297,301],{"__ignoreMap":94},[98,263,264,267],{"class":100,"line":101},[98,265,266],{"class":179},"import",[98,268,269],{"class":186}," os\n",[98,271,272],{"class":100,"line":115},[98,273,119],{"emptyLinePlaceholder":118},[98,275,276,279,282,285,288,291,294],{"class":100,"line":122},[98,277,278],{"class":186},"gl_server ",[98,280,281],{"class":179},"=",[98,283,284],{"class":186}," os.environ.get(",[98,286,287],{"class":108},"'GL_SERVER'",[98,289,290],{"class":186},", ",[98,292,293],{"class":108},"'https://gitlab.com'",[98,295,296],{"class":186},")\n",[98,298,299],{"class":100,"line":132},[98,300,119],{"emptyLinePlaceholder":118},[98,302,303,306],{"class":100,"line":137},[98,304,305],{"class":125},"print",[98,307,308],{"class":186},"(gl_server)\n",[12,310,311],{},"Le paramétrage dans le terminal peut se faire directement pour la commande uniquement, ou être exporté dans l'environnement shell.",[89,313,315],{"className":91,"code":314,"language":93,"meta":94,"style":94},"$ GL_SERVER=’https://gitlab.company.com’ python3 script.py\n\n$ export GL_SERVER=’https://gitlab.company.com’\n$ python3 script.py\n",[73,316,317,331,335,345],{"__ignoreMap":94},[98,318,319,322,325,328],{"class":100,"line":101},[98,320,321],{"class":104},"$",[98,323,324],{"class":108}," GL_SERVER=’https://gitlab.company.com’",[98,326,327],{"class":108}," python3",[98,329,330],{"class":108}," script.py\n",[98,332,333],{"class":100,"line":115},[98,334,119],{"emptyLinePlaceholder":118},[98,336,337,339,342],{"class":100,"line":122},[98,338,321],{"class":104},[98,340,341],{"class":108}," export",[98,343,344],{"class":108}," GL_SERVER=’https://gitlab.company.com’\n",[98,346,347,349,351],{"class":100,"line":132},[98,348,321],{"class":104},[98,350,327],{"class":108},[98,352,330],{"class":108},[12,354,355,356,359,360,363,364,367],{},"Nous 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 ",[73,357,358],{},"GL_SERVER",", et attend de l'utilisateur qu'il définisse la variable ",[73,361,362],{},"GL_TOKEN",". Si ce n'est pas le cas, le script affiche et génère des erreurs, puis appelle ",[73,365,366],{},"sys.exit(1)",", pour indiquer un statut d’erreur.",[89,369,371],{"className":257,"code":370,"language":259,"meta":94,"style":94},"import 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",[73,372,373,380,386,393,397,415,429,433,447,460],{"__ignoreMap":94},[98,374,375,377],{"class":100,"line":101},[98,376,266],{"class":179},[98,378,379],{"class":186}," gitlab\n",[98,381,382,384],{"class":100,"line":115},[98,383,266],{"class":179},[98,385,269],{"class":186},[98,387,388,390],{"class":100,"line":122},[98,389,266],{"class":179},[98,391,392],{"class":186}," sys\n",[98,394,395],{"class":100,"line":132},[98,396,119],{"emptyLinePlaceholder":118},[98,398,399,402,405,407,409,411,413],{"class":100,"line":137},[98,400,401],{"class":125},"GITLAB_SERVER",[98,403,404],{"class":179}," =",[98,406,284],{"class":186},[98,408,287],{"class":108},[98,410,290],{"class":186},[98,412,293],{"class":108},[98,414,296],{"class":186},[98,416,417,420,422,424,427],{"class":100,"line":149},[98,418,419],{"class":125},"GITLAB_TOKEN",[98,421,404],{"class":179},[98,423,284],{"class":186},[98,425,426],{"class":108},"'GL_TOKEN'",[98,428,296],{"class":186},[98,430,431],{"class":100,"line":154},[98,432,119],{"emptyLinePlaceholder":118},[98,434,435,438,441,444],{"class":100,"line":168},[98,436,437],{"class":179},"if",[98,439,440],{"class":179}," not",[98,442,443],{"class":125}," GITLAB_TOKEN",[98,445,446],{"class":186},":\n",[98,448,449,452,455,458],{"class":100,"line":173},[98,450,451],{"class":125},"    print",[98,453,454],{"class":186},"(",[98,456,457],{"class":108},"\"Please set the GL_TOKEN env variable.\"",[98,459,296],{"class":186},[98,461,463,466,469],{"class":100,"line":462},10,[98,464,465],{"class":186},"    sys.exit(",[98,467,468],{"class":125},"1",[98,470,296],{"class":186},[12,472,473],{},"Examinons maintenant un exemple plus détaillé, qui crée une connexion à l'API et effectue une requête de données.",[48,475,477],{"id":476},"gestion-des-objets-lobjet-gitlab","Gestion des objets : l'objet GitLab",[12,479,480],{},"Toute 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.",[12,482,483,484,206],{},"L'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 ",[19,485,489],{"href":486,"rel":487,"title":488},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/templates.html#gitignore-templates",[],"Modèle .gitignore pour Python","modèle .gitignore pour Python",[12,491,492],{},[19,493,496],{"href":494,"rel":495},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_object_unauthenticated.py",[],"python_gitlab_object_unauthenticated.py",[89,498,500],{"className":257,"code":499,"language":259,"meta":94,"style":94},"import gitlab\n\ngl = gitlab.Gitlab()\n\n# Get .gitignore templates without authentication\ngitignore_templates = gl.gitignores.get('Python')\n\nprint(gitignore_templates.content)\n",[73,501,502,508,512,522,526,532,547,551],{"__ignoreMap":94},[98,503,504,506],{"class":100,"line":101},[98,505,266],{"class":179},[98,507,379],{"class":186},[98,509,510],{"class":100,"line":115},[98,511,119],{"emptyLinePlaceholder":118},[98,513,514,517,519],{"class":100,"line":122},[98,515,516],{"class":186},"gl ",[98,518,281],{"class":179},[98,520,521],{"class":186}," gitlab.Gitlab()\n",[98,523,524],{"class":100,"line":132},[98,525,119],{"emptyLinePlaceholder":118},[98,527,528],{"class":100,"line":137},[98,529,531],{"class":530},"sAwPA","# Get .gitignore templates without authentication\n",[98,533,534,537,539,542,545],{"class":100,"line":149},[98,535,536],{"class":186},"gitignore_templates ",[98,538,281],{"class":179},[98,540,541],{"class":186}," gl.gitignores.get(",[98,543,544],{"class":108},"'Python'",[98,546,296],{"class":186},[98,548,549],{"class":100,"line":154},[98,550,119],{"emptyLinePlaceholder":118},[98,552,553,555],{"class":100,"line":168},[98,554,305],{"class":125},[98,556,557],{"class":186},"(gitignore_templates.content)\n",[12,559,560],{},"Dans les sections suivantes, nous vous partageons des informations détaillées sur :",[562,563,564,568,571,574],"ul",{},[565,566,567],"li",{},"La gestion et le chargement des objets,",[565,569,570],{},"La pagination des résultats,",[565,572,573],{},"Le travail avec les relations entre objets,",[565,575,576],{},"Le travail avec différents scopes de collection d'objets.",[578,579,581],"h3",{"id":580},"la-gestion-et-le-chargement-des-objets","La gestion et le chargement des objets",[12,583,584,585,590],{},"La bibliothèque python-gitlab donne accès aux ressources GitLab en utilisant ce que l’on appelle des « ",[19,586,589],{"href":587,"rel":588},"https://python-gitlab.readthedocs.io/en/stable/api-usage.html#managers",[],"Gestionnaires"," ». Chaque type de gestionnaire implémente des méthodes pour travailler avec les ensembles de données (list, get, etc.).",[12,592,593,594,597],{},"Le 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 ",[73,595,596],{},"GROUP_ID"," pour spécifier un groupe principal à partir duquel il faut commencer la recherche.",[89,599,601],{"className":257,"code":600,"language":259,"meta":94,"style":94},"#!/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\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",[73,602,603,608,612,618,624,630,634,650,655,673,685,690,701,712,721,726,750,755,761,776,781,793,808,835,840,852,865,889,894,906,938,960,965,977,1000,1025,1030,1042,1055,1078,1083,1095,1117],{"__ignoreMap":94},[98,604,605],{"class":100,"line":101},[98,606,607],{"class":530},"#!/usr/bin/env python\n",[98,609,610],{"class":100,"line":115},[98,611,119],{"emptyLinePlaceholder":118},[98,613,614,616],{"class":100,"line":122},[98,615,266],{"class":179},[98,617,379],{"class":186},[98,619,620,622],{"class":100,"line":132},[98,621,266],{"class":179},[98,623,269],{"class":186},[98,625,626,628],{"class":100,"line":137},[98,627,266],{"class":179},[98,629,392],{"class":186},[98,631,632],{"class":100,"line":149},[98,633,119],{"emptyLinePlaceholder":118},[98,635,636,638,640,642,644,646,648],{"class":100,"line":154},[98,637,401],{"class":125},[98,639,404],{"class":179},[98,641,284],{"class":186},[98,643,287],{"class":108},[98,645,290],{"class":186},[98,647,293],{"class":108},[98,649,296],{"class":186},[98,651,652],{"class":100,"line":168},[98,653,654],{"class":530},"# https://gitlab.com/gitlab-da/use-cases/\n",[98,656,657,659,661,663,666,668,671],{"class":100,"line":173},[98,658,596],{"class":125},[98,660,404],{"class":179},[98,662,284],{"class":186},[98,664,665],{"class":108},"'GL_GROUP_ID'",[98,667,290],{"class":186},[98,669,670],{"class":125},"16058698",[98,672,296],{"class":186},[98,674,675,677,679,681,683],{"class":100,"line":462},[98,676,419],{"class":125},[98,678,404],{"class":179},[98,680,284],{"class":186},[98,682,426],{"class":108},[98,684,296],{"class":186},[98,686,688],{"class":100,"line":687},11,[98,689,119],{"emptyLinePlaceholder":118},[98,691,693,695,697,699],{"class":100,"line":692},12,[98,694,437],{"class":179},[98,696,440],{"class":179},[98,698,443],{"class":125},[98,700,446],{"class":186},[98,702,704,706,708,710],{"class":100,"line":703},13,[98,705,451],{"class":125},[98,707,454],{"class":186},[98,709,457],{"class":108},[98,711,296],{"class":186},[98,713,715,717,719],{"class":100,"line":714},14,[98,716,465],{"class":186},[98,718,468],{"class":125},[98,720,296],{"class":186},[98,722,724],{"class":100,"line":723},15,[98,725,119],{"emptyLinePlaceholder":118},[98,727,729,731,733,736,738,740,744,746,748],{"class":100,"line":728},16,[98,730,516],{"class":186},[98,732,281],{"class":179},[98,734,735],{"class":186}," gitlab.Gitlab(",[98,737,401],{"class":125},[98,739,290],{"class":186},[98,741,743],{"class":742},"sqxcx","private_token",[98,745,281],{"class":179},[98,747,419],{"class":125},[98,749,296],{"class":186},[98,751,753],{"class":100,"line":752},17,[98,754,119],{"emptyLinePlaceholder":118},[98,756,758],{"class":100,"line":757},18,[98,759,760],{"class":530},"# Main\n",[98,762,764,767,769,772,774],{"class":100,"line":763},19,[98,765,766],{"class":186},"main_group ",[98,768,281],{"class":179},[98,770,771],{"class":186}," gl.groups.get(",[98,773,596],{"class":125},[98,775,296],{"class":186},[98,777,779],{"class":100,"line":778},20,[98,780,119],{"emptyLinePlaceholder":118},[98,782,784,786,788,791],{"class":100,"line":783},21,[98,785,305],{"class":125},[98,787,454],{"class":186},[98,789,790],{"class":108},"\"Sub groups\"",[98,792,296],{"class":186},[98,794,796,799,802,805],{"class":100,"line":795},22,[98,797,798],{"class":179},"for",[98,800,801],{"class":186}," sg ",[98,803,804],{"class":179},"in",[98,806,807],{"class":186}," main_group.subgroups.list():\n",[98,809,811,813,815,818,821,824,827,830,832],{"class":100,"line":810},23,[98,812,451],{"class":125},[98,814,454],{"class":186},[98,816,817],{"class":108},"\"Subgroup name: ",[98,819,820],{"class":125},"{sg}",[98,822,823],{"class":108},"\"",[98,825,826],{"class":186},".format(",[98,828,829],{"class":742},"sg",[98,831,281],{"class":179},[98,833,834],{"class":186},"sg.name))\n",[98,836,838],{"class":100,"line":837},24,[98,839,119],{"emptyLinePlaceholder":118},[98,841,843,845,847,850],{"class":100,"line":842},25,[98,844,305],{"class":125},[98,846,454],{"class":186},[98,848,849],{"class":108},"\"Projects (direct)\"",[98,851,296],{"class":186},[98,853,855,857,860,862],{"class":100,"line":854},26,[98,856,798],{"class":179},[98,858,859],{"class":186}," p ",[98,861,804],{"class":179},[98,863,864],{"class":186}," main_group.projects.list():\n",[98,866,868,870,872,875,878,880,882,884,886],{"class":100,"line":867},27,[98,869,451],{"class":125},[98,871,454],{"class":186},[98,873,874],{"class":108},"\"Project name: ",[98,876,877],{"class":125},"{p}",[98,879,823],{"class":108},[98,881,826],{"class":186},[98,883,12],{"class":742},[98,885,281],{"class":179},[98,887,888],{"class":186},"p.name))\n",[98,890,892],{"class":100,"line":891},28,[98,893,119],{"emptyLinePlaceholder":118},[98,895,897,899,901,904],{"class":100,"line":896},29,[98,898,305],{"class":125},[98,900,454],{"class":186},[98,902,903],{"class":108},"\"Projects (including subgroups)\"",[98,905,296],{"class":186},[98,907,909,911,913,915,918,921,923,926,928,931,933,935],{"class":100,"line":908},30,[98,910,798],{"class":179},[98,912,859],{"class":186},[98,914,804],{"class":179},[98,916,917],{"class":186}," main_group.projects.list(",[98,919,920],{"class":742},"include_subgroups",[98,922,281],{"class":179},[98,924,925],{"class":125},"True",[98,927,290],{"class":186},[98,929,930],{"class":742},"all",[98,932,281],{"class":179},[98,934,925],{"class":125},[98,936,937],{"class":186},"):\n",[98,939,941,944,946,948,950,952,954,956,958],{"class":100,"line":940},31,[98,942,943],{"class":125},"     print",[98,945,454],{"class":186},[98,947,874],{"class":108},[98,949,877],{"class":125},[98,951,823],{"class":108},[98,953,826],{"class":186},[98,955,12],{"class":742},[98,957,281],{"class":179},[98,959,888],{"class":186},[98,961,963],{"class":100,"line":962},32,[98,964,119],{"emptyLinePlaceholder":118},[98,966,968,970,972,975],{"class":100,"line":967},33,[98,969,305],{"class":125},[98,971,454],{"class":186},[98,973,974],{"class":108},"\"Issues\"",[98,976,296],{"class":186},[98,978,980,982,985,987,990,993,995,998],{"class":100,"line":979},34,[98,981,798],{"class":179},[98,983,984],{"class":186}," i ",[98,986,804],{"class":179},[98,988,989],{"class":186}," main_group.issues.list(",[98,991,992],{"class":742},"state",[98,994,281],{"class":179},[98,996,997],{"class":108},"'opened'",[98,999,937],{"class":186},[98,1001,1003,1005,1007,1010,1013,1015,1017,1020,1022],{"class":100,"line":1002},35,[98,1004,451],{"class":125},[98,1006,454],{"class":186},[98,1008,1009],{"class":108},"\"Issue title: ",[98,1011,1012],{"class":125},"{t}",[98,1014,823],{"class":108},[98,1016,826],{"class":186},[98,1018,1019],{"class":742},"t",[98,1021,281],{"class":179},[98,1023,1024],{"class":186},"i.title))\n",[98,1026,1028],{"class":100,"line":1027},36,[98,1029,119],{"emptyLinePlaceholder":118},[98,1031,1033,1035,1037,1040],{"class":100,"line":1032},37,[98,1034,305],{"class":125},[98,1036,454],{"class":186},[98,1038,1039],{"class":108},"\"Epics\"",[98,1041,296],{"class":186},[98,1043,1045,1047,1050,1052],{"class":100,"line":1044},38,[98,1046,798],{"class":179},[98,1048,1049],{"class":186}," e ",[98,1051,804],{"class":179},[98,1053,1054],{"class":186}," main_group.issues.list():\n",[98,1056,1058,1060,1062,1065,1067,1069,1071,1073,1075],{"class":100,"line":1057},39,[98,1059,451],{"class":125},[98,1061,454],{"class":186},[98,1063,1064],{"class":108},"\"Epic title: ",[98,1066,1012],{"class":125},[98,1068,823],{"class":108},[98,1070,826],{"class":186},[98,1072,1019],{"class":742},[98,1074,281],{"class":179},[98,1076,1077],{"class":186},"e.title))\n",[98,1079,1081],{"class":100,"line":1080},40,[98,1082,119],{"emptyLinePlaceholder":118},[98,1084,1086,1088,1090,1093],{"class":100,"line":1085},41,[98,1087,305],{"class":125},[98,1089,454],{"class":186},[98,1091,1092],{"class":108},"\"Todos\"",[98,1094,296],{"class":186},[98,1096,1098,1100,1103,1105,1108,1110,1112,1115],{"class":100,"line":1097},42,[98,1099,798],{"class":179},[98,1101,1102],{"class":186}," t ",[98,1104,804],{"class":179},[98,1106,1107],{"class":186}," gl.todos.list(",[98,1109,992],{"class":742},[98,1111,281],{"class":179},[98,1113,1114],{"class":108},"'pending'",[98,1116,937],{"class":186},[98,1118,1120,1122,1124,1127,1129,1132,1135,1137,1139,1141,1143,1146,1149,1151],{"class":100,"line":1119},43,[98,1121,451],{"class":125},[98,1123,454],{"class":186},[98,1125,1126],{"class":108},"\"Todo: ",[98,1128,1012],{"class":125},[98,1130,1131],{"class":108}," url: ",[98,1133,1134],{"class":125},"{u}",[98,1136,823],{"class":108},[98,1138,826],{"class":186},[98,1140,1019],{"class":742},[98,1142,281],{"class":179},[98,1144,1145],{"class":186},"t.body, ",[98,1147,1148],{"class":742},"u",[98,1150,281],{"class":179},[98,1152,1153],{"class":186},"t.target_url\n",[12,1155,1156,1157,1160,1161,1163,1164,1166,1167,1169],{},"Vous pouvez exécuter le script ",[73,1158,1159],{},"python_gitlab_object_manager_methods.py"," en remplaçant la variable ",[73,1162,596],{}," sur GitLab.com SaaS pour analyser votre propre groupe. Vous devez spécifier la variable ",[73,1165,358],{}," pour les instances auto-gérées. ",[73,1168,362],{}," doit fournir le jeton d'accès personnel.",[89,1171,1173],{"className":91,"code":1172,"language":93,"meta":94,"style":94},"export 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",[73,1174,1175,1188,1192,1204,1208,1219,1223,1235,1239],{"__ignoreMap":94},[98,1176,1177,1180,1183,1185],{"class":100,"line":101},[98,1178,1179],{"class":179},"export",[98,1181,1182],{"class":186}," GL_TOKEN",[98,1184,281],{"class":179},[98,1186,1187],{"class":186},"xxx\n",[98,1189,1190],{"class":100,"line":115},[98,1191,119],{"emptyLinePlaceholder":118},[98,1193,1194,1196,1199,1201],{"class":100,"line":122},[98,1195,1179],{"class":179},[98,1197,1198],{"class":186}," GL_SERVER",[98,1200,281],{"class":179},[98,1202,1203],{"class":186},"”https://gitlab.company.com”\n",[98,1205,1206],{"class":100,"line":132},[98,1207,119],{"emptyLinePlaceholder":118},[98,1209,1210,1212,1214,1216],{"class":100,"line":137},[98,1211,1179],{"class":179},[98,1213,1198],{"class":186},[98,1215,281],{"class":179},[98,1217,1218],{"class":186},"”https://gitlab.com”\n",[98,1220,1221],{"class":100,"line":149},[98,1222,119],{"emptyLinePlaceholder":118},[98,1224,1225,1227,1230,1232],{"class":100,"line":154},[98,1226,1179],{"class":179},[98,1228,1229],{"class":186}," GL_GROUP_ID",[98,1231,281],{"class":179},[98,1233,1234],{"class":125},"1234\n",[98,1236,1237],{"class":100,"line":168},[98,1238,119],{"emptyLinePlaceholder":118},[98,1240,1241,1243],{"class":100,"line":173},[98,1242,176],{"class":104},[98,1244,1245],{"class":108}," python_gitlab_object_manager_methods.py\n",[12,1247,1248,1249,206],{},"À 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 ",[19,1250,1253],{"href":1251,"rel":1252},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python",[],"ce projet",[578,1255,1257],{"id":1256},"la-pagination-des-résultats","La pagination des résultats",[12,1259,1260,1261,1267,1268,1273,1274,1277,1278,1281],{},"Par défaut, l’API GitLab ne renvoie pas tous les ensembles de résultats et exige que les clients utilisent la ",[19,1262,1266],{"href":1263,"rel":1264,"title":1265},"https://docs.gitlab.com/api/rest/#pagination",[],"Pagination GitLab","pagination"," pour parcourir toutes les pages de résultats. La bibliothèque python-gitlab permet aux utilisateurs de ",[19,1269,1272],{"href":1270,"rel":1271},"https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination",[],"définir les paramètres"," globalement dans l'objet GitLab, ou sur chaque appel ",[73,1275,1276],{},"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 ",[73,1279,1280],{},"iterator=True",", et les appels d'API sont déclenchés à la demande lors de l'accès à l'objet.",[12,1283,1284,1285,1288,1289,1292],{},"L'exemple suivant recherche le nom de groupe ",[73,1286,1287],{},"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 ",[73,1290,1291],{},"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.",[89,1294,1296],{"className":257,"code":1295,"language":259,"meta":94,"style":94},"SEARCH_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\n",[73,1297,1298,1308,1312,1317,1322,1343,1375,1379,1384,1403,1407,1417,1427,1431,1443,1457,1462,1472,1477,1481,1490,1494,1528,1532,1546,1581,1617,1624],{"__ignoreMap":94},[98,1299,1300,1303,1305],{"class":100,"line":101},[98,1301,1302],{"class":125},"SEARCH_GROUP_NAME",[98,1304,281],{"class":179},[98,1306,1307],{"class":108},"\"everyonecancontribute\"\n",[98,1309,1310],{"class":100,"line":115},[98,1311,119],{"emptyLinePlaceholder":118},[98,1313,1314],{"class":100,"line":122},[98,1315,1316],{"class":530},"# Use keyset pagination\n",[98,1318,1319],{"class":100,"line":132},[98,1320,1321],{"class":530},"# https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination\n",[98,1323,1324,1326,1328,1330,1332,1334,1336,1338,1340],{"class":100,"line":137},[98,1325,516],{"class":186},[98,1327,281],{"class":179},[98,1329,735],{"class":186},[98,1331,401],{"class":125},[98,1333,290],{"class":186},[98,1335,743],{"class":742},[98,1337,281],{"class":179},[98,1339,419],{"class":125},[98,1341,1342],{"class":186},",\n",[98,1344,1345,1348,1350,1353,1355,1358,1360,1363,1365,1368,1370,1373],{"class":100,"line":149},[98,1346,1347],{"class":742},"    pagination",[98,1349,281],{"class":179},[98,1351,1352],{"class":108},"\"keyset\"",[98,1354,290],{"class":186},[98,1356,1357],{"class":742},"order_by",[98,1359,281],{"class":179},[98,1361,1362],{"class":108},"\"id\"",[98,1364,290],{"class":186},[98,1366,1367],{"class":742},"per_page",[98,1369,281],{"class":179},[98,1371,1372],{"class":125},"100",[98,1374,296],{"class":186},[98,1376,1377],{"class":100,"line":154},[98,1378,119],{"emptyLinePlaceholder":118},[98,1380,1381],{"class":100,"line":168},[98,1382,1383],{"class":530},"# Iterate over the list, and fire new API calls in case the result set does not match yet\n",[98,1385,1386,1389,1391,1394,1397,1399,1401],{"class":100,"line":173},[98,1387,1388],{"class":186},"groups ",[98,1390,281],{"class":179},[98,1392,1393],{"class":186}," gl.groups.list(",[98,1395,1396],{"class":742},"iterator",[98,1398,281],{"class":179},[98,1400,925],{"class":125},[98,1402,296],{"class":186},[98,1404,1405],{"class":100,"line":462},[98,1406,119],{"emptyLinePlaceholder":118},[98,1408,1409,1412,1414],{"class":100,"line":687},[98,1410,1411],{"class":186},"found_page ",[98,1413,281],{"class":179},[98,1415,1416],{"class":125}," 0\n",[98,1418,1419,1422,1424],{"class":100,"line":692},[98,1420,1421],{"class":186},"start ",[98,1423,281],{"class":179},[98,1425,1426],{"class":186}," timer()\n",[98,1428,1429],{"class":100,"line":703},[98,1430,119],{"emptyLinePlaceholder":118},[98,1432,1433,1435,1438,1440],{"class":100,"line":714},[98,1434,798],{"class":179},[98,1436,1437],{"class":186}," group ",[98,1439,804],{"class":179},[98,1441,1442],{"class":186}," groups:\n",[98,1444,1445,1448,1451,1454],{"class":100,"line":723},[98,1446,1447],{"class":179},"    if",[98,1449,1450],{"class":125}," SEARCH_GROUP_NAME",[98,1452,1453],{"class":179}," ==",[98,1455,1456],{"class":186}," group.name:\n",[98,1458,1459],{"class":100,"line":728},[98,1460,1461],{"class":530},"        # print(group) # debug\n",[98,1463,1464,1467,1469],{"class":100,"line":752},[98,1465,1466],{"class":186},"        found_page ",[98,1468,281],{"class":179},[98,1470,1471],{"class":186}," groups.current_page\n",[98,1473,1474],{"class":100,"line":757},[98,1475,1476],{"class":179},"        break\n",[98,1478,1479],{"class":100,"line":763},[98,1480,119],{"emptyLinePlaceholder":118},[98,1482,1483,1486,1488],{"class":100,"line":778},[98,1484,1485],{"class":186},"end ",[98,1487,281],{"class":179},[98,1489,1426],{"class":186},[98,1491,1492],{"class":100,"line":783},[98,1493,119],{"emptyLinePlaceholder":118},[98,1495,1496,1499,1501,1504,1507,1510,1513,1516,1519,1522,1525],{"class":100,"line":795},[98,1497,1498],{"class":186},"duration ",[98,1500,281],{"class":179},[98,1502,1503],{"class":179}," f",[98,1505,1506],{"class":108},"'",[98,1508,1509],{"class":125},"{",[98,1511,1512],{"class":186},"end",[98,1514,1515],{"class":179},"-",[98,1517,1518],{"class":186},"start",[98,1520,1521],{"class":179},":.2f",[98,1523,1524],{"class":125},"}",[98,1526,1527],{"class":108},"'\n",[98,1529,1530],{"class":100,"line":810},[98,1531,119],{"emptyLinePlaceholder":118},[98,1533,1534,1536,1539,1541,1544],{"class":100,"line":837},[98,1535,437],{"class":179},[98,1537,1538],{"class":186}," found_page ",[98,1540,190],{"class":179},[98,1542,1543],{"class":125}," 0",[98,1545,446],{"class":186},[98,1547,1548,1550,1552,1555,1558,1561,1564,1567,1569,1572,1575,1578],{"class":100,"line":842},[98,1549,451],{"class":125},[98,1551,454],{"class":186},[98,1553,1554],{"class":108},"\"Pagination API example for Python with GitLab",[98,1556,1557],{"class":125},"{desc}",[98,1559,1560],{"class":108}," - found group ",[98,1562,1563],{"class":125},"{g}",[98,1565,1566],{"class":108}," on page ",[98,1568,877],{"class":125},[98,1570,1571],{"class":108},", duration ",[98,1573,1574],{"class":125},"{d}",[98,1576,1577],{"class":108},"s\"",[98,1579,1580],{"class":186},".format(\n",[98,1582,1583,1586,1588,1591,1593,1596,1598,1600,1602,1604,1606,1609,1612,1614],{"class":100,"line":854},[98,1584,1585],{"class":742},"        desc",[98,1587,281],{"class":179},[98,1589,1590],{"class":108},"\", the DevSecOps platform\"",[98,1592,290],{"class":186},[98,1594,1595],{"class":742},"g",[98,1597,281],{"class":179},[98,1599,1302],{"class":125},[98,1601,290],{"class":186},[98,1603,12],{"class":742},[98,1605,281],{"class":179},[98,1607,1608],{"class":186},"found_page, ",[98,1610,1611],{"class":742},"d",[98,1613,281],{"class":179},[98,1615,1616],{"class":186},"duration))\n",[98,1618,1619,1622],{"class":100,"line":867},[98,1620,1621],{"class":179},"else",[98,1623,446],{"class":186},[98,1625,1626,1628,1630,1633,1635,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656],{"class":100,"line":891},[98,1627,451],{"class":125},[98,1629,454],{"class":186},[98,1631,1632],{"class":108},"\"Could not find group name '",[98,1634,1563],{"class":125},[98,1636,1637],{"class":108},"', duration ",[98,1639,1574],{"class":125},[98,1641,823],{"class":108},[98,1643,826],{"class":186},[98,1645,1595],{"class":742},[98,1647,281],{"class":179},[98,1649,1302],{"class":125},[98,1651,290],{"class":186},[98,1653,1611],{"class":742},[98,1655,281],{"class":179},[98,1657,1616],{"class":186},[12,1659,1660,1661,1664,1665,1669],{},"L'exécution de ",[73,1662,1663],{},"python_gitlab_pagination.py"," a permis de trouver le groupe ",[19,1666,1287],{"href":1667,"rel":1668},"https://gitlab.com/everyonecancontribute",[]," à la page 5.",[89,1671,1673],{"className":91,"code":1672,"language":93,"meta":94,"style":94},"$ 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",[73,1674,1675,1684],{"__ignoreMap":94},[98,1676,1677,1679,1681],{"class":100,"line":101},[98,1678,321],{"class":104},[98,1680,327],{"class":108},[98,1682,1683],{"class":108}," python_gitlab_pagination.py\n",[98,1685,1686,1689,1692,1695,1698,1701,1704,1707,1710,1713,1716,1719,1722,1725,1728,1731,1734,1737,1740],{"class":100,"line":115},[98,1687,1688],{"class":104},"Pagination",[98,1690,1691],{"class":108}," API",[98,1693,1694],{"class":108}," example",[98,1696,1697],{"class":108}," for",[98,1699,1700],{"class":108}," Python",[98,1702,1703],{"class":108}," with",[98,1705,1706],{"class":108}," GitLab,",[98,1708,1709],{"class":108}," the",[98,1711,1712],{"class":108}," DevSecOps",[98,1714,1715],{"class":108}," platform",[98,1717,1718],{"class":108}," -",[98,1720,1721],{"class":108}," found",[98,1723,1724],{"class":108}," group",[98,1726,1727],{"class":108}," everyonecancontribute",[98,1729,1730],{"class":108}," on",[98,1732,1733],{"class":108}," page",[98,1735,1736],{"class":108}," 5,",[98,1738,1739],{"class":108}," duration",[98,1741,1742],{"class":108}," 8.51s\n",[578,1744,1746],{"id":1745},"le-travail-avec-les-relations-entre-objets","Le travail avec les relations entre objets",[12,1748,1749,1750,1753],{},"Lorsque 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 ",[73,1751,1752],{},"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.",[12,1755,1756,1757,1760],{},"L'exemple suivant illustre le problème en parcourant tous les projets d'un groupe et en essayant d'appeler la fonction ",[73,1758,1759],{},"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.",[89,1762,1764],{"className":257,"code":1763,"language":259,"meta":94,"style":94},"# 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",[73,1765,1766,1770,1783,1787,1792,1818,1822,1834,1839,1846,1869,1879,1902,1916,1949,1953,1958,1963,1973,1977,1982,2000,2009],{"__ignoreMap":94},[98,1767,1768],{"class":100,"line":101},[98,1769,760],{"class":530},[98,1771,1772,1775,1777,1779,1781],{"class":100,"line":115},[98,1773,1774],{"class":186},"group ",[98,1776,281],{"class":179},[98,1778,771],{"class":186},[98,1780,596],{"class":125},[98,1782,296],{"class":186},[98,1784,1785],{"class":100,"line":122},[98,1786,119],{"emptyLinePlaceholder":118},[98,1788,1789],{"class":100,"line":132},[98,1790,1791],{"class":530},"# Collect all projects in group and subgroups\n",[98,1793,1794,1797,1799,1802,1804,1806,1808,1810,1812,1814,1816],{"class":100,"line":137},[98,1795,1796],{"class":186},"projects ",[98,1798,281],{"class":179},[98,1800,1801],{"class":186}," group.projects.list(",[98,1803,920],{"class":742},[98,1805,281],{"class":179},[98,1807,925],{"class":125},[98,1809,290],{"class":186},[98,1811,930],{"class":742},[98,1813,281],{"class":179},[98,1815,925],{"class":125},[98,1817,296],{"class":186},[98,1819,1820],{"class":100,"line":149},[98,1821,119],{"emptyLinePlaceholder":118},[98,1823,1824,1826,1829,1831],{"class":100,"line":154},[98,1825,798],{"class":179},[98,1827,1828],{"class":186}," project ",[98,1830,804],{"class":179},[98,1832,1833],{"class":186}," projects:\n",[98,1835,1836],{"class":100,"line":168},[98,1837,1838],{"class":530},"    # Try running a method on a weak object\n",[98,1840,1841,1844],{"class":100,"line":173},[98,1842,1843],{"class":179},"    try",[98,1845,446],{"class":186},[98,1847,1848,1851,1853,1856,1859,1862,1865,1867],{"class":100,"line":462},[98,1849,1850],{"class":125},"       print",[98,1852,454],{"class":186},[98,1854,1855],{"class":108},"\"🤔 Project: ",[98,1857,1858],{"class":125},"{pn}",[98,1860,1861],{"class":108}," 💡 Branches: ",[98,1863,1864],{"class":125},"{b}\\n",[98,1866,823],{"class":108},[98,1868,1580],{"class":186},[98,1870,1871,1874,1876],{"class":100,"line":687},[98,1872,1873],{"class":742},"        pn",[98,1875,281],{"class":179},[98,1877,1878],{"class":186},"project.name,\n",[98,1880,1881,1884,1886,1889,1892,1894,1897,1899],{"class":100,"line":692},[98,1882,1883],{"class":742},"        b",[98,1885,281],{"class":179},[98,1887,1888],{"class":108},"\", \"",[98,1890,1891],{"class":186},".join([x.name ",[98,1893,798],{"class":179},[98,1895,1896],{"class":186}," x ",[98,1898,804],{"class":179},[98,1900,1901],{"class":186}," project.branches.list()])))\n",[98,1903,1904,1907,1910,1913],{"class":100,"line":703},[98,1905,1906],{"class":179},"    except",[98,1908,1909],{"class":125}," Exception",[98,1911,1912],{"class":179}," as",[98,1914,1915],{"class":186}," e:\n",[98,1917,1918,1921,1923,1926,1929,1932,1935,1938,1940,1942,1944,1946],{"class":100,"line":714},[98,1919,1920],{"class":125},"        print",[98,1922,454],{"class":186},[98,1924,1925],{"class":108},"\"Got exception: ",[98,1927,1928],{"class":125},"{e}",[98,1930,1931],{"class":125}," \\n",[98,1933,1934],{"class":108}," ===================================== ",[98,1936,1937],{"class":125},"\\n",[98,1939,823],{"class":108},[98,1941,826],{"class":186},[98,1943,187],{"class":742},[98,1945,281],{"class":179},[98,1947,1948],{"class":186},"e))\n",[98,1950,1951],{"class":100,"line":723},[98,1952,119],{"emptyLinePlaceholder":118},[98,1954,1955],{"class":100,"line":728},[98,1956,1957],{"class":530},"    # Retrieve a full manageable project object\n",[98,1959,1960],{"class":100,"line":752},[98,1961,1962],{"class":530},"    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n",[98,1964,1965,1968,1970],{"class":100,"line":757},[98,1966,1967],{"class":186},"    manageable_project ",[98,1969,281],{"class":179},[98,1971,1972],{"class":186}," gl.projects.get(project.id)\n",[98,1974,1975],{"class":100,"line":763},[98,1976,119],{"emptyLinePlaceholder":118},[98,1978,1979],{"class":100,"line":778},[98,1980,1981],{"class":530},"    # Print a method available on a manageable object\n",[98,1983,1984,1986,1988,1990,1992,1994,1996,1998],{"class":100,"line":783},[98,1985,451],{"class":125},[98,1987,454],{"class":186},[98,1989,1855],{"class":108},[98,1991,1858],{"class":125},[98,1993,1861],{"class":108},[98,1995,1864],{"class":125},[98,1997,823],{"class":108},[98,1999,1580],{"class":186},[98,2001,2002,2004,2006],{"class":100,"line":795},[98,2003,1873],{"class":742},[98,2005,281],{"class":179},[98,2007,2008],{"class":186},"manageable_project.name,\n",[98,2010,2011,2013,2015,2017,2019,2021,2023,2025],{"class":100,"line":810},[98,2012,1883],{"class":742},[98,2014,281],{"class":179},[98,2016,1888],{"class":108},[98,2018,1891],{"class":186},[98,2020,798],{"class":179},[98,2022,1896],{"class":186},[98,2024,804],{"class":179},[98,2026,2027],{"class":186}," manageable_project.branches.list()])))\n",[12,2029,2030],{},"Le 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.",[89,2032,2034],{"className":91,"code":2033,"language":93,"meta":94,"style":94},"$ 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",[73,2035,2036,2045,2049,2080,2084,2110,2114,2130,2138,2180,2191,2195,2200],{"__ignoreMap":94},[98,2037,2038,2040,2042],{"class":100,"line":101},[98,2039,321],{"class":104},[98,2041,327],{"class":108},[98,2043,2044],{"class":108}," python_gitlab_manageable_objects.py\n",[98,2046,2047],{"class":100,"line":115},[98,2048,119],{"emptyLinePlaceholder":118},[98,2050,2051,2054,2057,2060,2062,2065,2068,2071,2074,2077],{"class":100,"line":122},[98,2052,2053],{"class":104},"🤔",[98,2055,2056],{"class":108}," Project:",[98,2058,2059],{"class":108}," GitLab",[98,2061,1691],{"class":108},[98,2063,2064],{"class":108}," Playground",[98,2066,2067],{"class":108}," 💡",[98,2069,2070],{"class":108}," Branches:",[98,2072,2073],{"class":108}," cicd-demo-automated-comments,",[98,2075,2076],{"class":108}," docs-mr-approval-settings,",[98,2078,2079],{"class":108}," main\n",[98,2081,2082],{"class":100,"line":132},[98,2083,119],{"emptyLinePlaceholder":118},[98,2085,2086,2089,2092,2095,2098,2101,2104,2107],{"class":100,"line":137},[98,2087,2088],{"class":104},"Got",[98,2090,2091],{"class":108}," exception:",[98,2093,2094],{"class":108}," 'GroupProject'",[98,2096,2097],{"class":108}," object",[98,2099,2100],{"class":108}," has",[98,2102,2103],{"class":108}," no",[98,2105,2106],{"class":108}," attribute",[98,2108,2109],{"class":108}," 'branches'\n",[98,2111,2112],{"class":100,"line":149},[98,2113,119],{"emptyLinePlaceholder":118},[98,2115,2116,2119,2122,2125,2127],{"class":100,"line":154},[98,2117,2118],{"class":179},"\u003C",[98,2120,2121],{"class":186},"class ",[98,2123,2124],{"class":108},"'gitlab.v4.objects.projects.GroupProject'",[98,2126,190],{"class":179},[98,2128,2129],{"class":186}," was created via a\n",[98,2131,2132,2135],{"class":100,"line":168},[98,2133,2134],{"class":104},"list",[98,2136,2137],{"class":186},"() call and only a subset of the data may be present. To ensure\n",[98,2139,2140,2142,2145,2148,2151,2154,2156,2158,2161,2164,2166,2168,2171,2174,2177],{"class":100,"line":173},[98,2141,930],{"class":104},[98,2143,2144],{"class":108}," data",[98,2146,2147],{"class":108}," is",[98,2149,2150],{"class":108}," present",[98,2152,2153],{"class":108}," get",[98,2155,1709],{"class":108},[98,2157,2097],{"class":108},[98,2159,2160],{"class":108}," using",[98,2162,2163],{"class":108}," a",[98,2165,2153],{"class":108},[98,2167,454],{"class":186},[98,2169,2170],{"class":104},"object.id",[98,2172,2173],{"class":186},") ",[98,2175,2176],{"class":108},"call.",[98,2178,2179],{"class":108}," For\n",[98,2181,2182,2185,2188],{"class":100,"line":462},[98,2183,2184],{"class":104},"more",[98,2186,2187],{"class":108}," details,",[98,2189,2190],{"class":108}," see:\n",[98,2192,2193],{"class":100,"line":687},[98,2194,119],{"emptyLinePlaceholder":118},[98,2196,2197],{"class":100,"line":692},[98,2198,2199],{"class":104},"https://python-gitlab.readthedocs.io/en/v3.8.1/faq.html#attribute-error-list\n",[98,2201,2202],{"class":100,"line":703},[98,2203,2204],{"class":108}," =====================================\n",[12,2206,2207,206],{},[19,2208,2211],{"href":2209,"rel":2210},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_manageable_objects.py",[],"Consultez le script complet",[578,2213,2215],{"id":2214},"le-travail-avec-différents-scopes-de-collection-dobjets","Le travail avec différents scopes de collection d'objets",[12,2217,2218,2219,2222],{},"Parfois, 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 ",[73,2220,2221],{},"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.",[89,2224,2226],{"className":257,"code":2225,"language":259,"meta":94,"style":94},"\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",[73,2227,2228,2232,2237,2247,2251,2256,2265,2276,2280,2285,2295,2308,2312,2339,2344,2353,2358,2362,2367,2374],{"__ignoreMap":94},[98,2229,2230],{"class":100,"line":101},[98,2231,119],{"emptyLinePlaceholder":118},[98,2233,2234],{"class":100,"line":115},[98,2235,2236],{"class":530},"    # Collect all projects, or prefer projects from a group id, or a project id\n",[98,2238,2239,2242,2244],{"class":100,"line":122},[98,2240,2241],{"class":186},"    projects ",[98,2243,281],{"class":179},[98,2245,2246],{"class":186}," []\n",[98,2248,2249],{"class":100,"line":132},[98,2250,119],{"emptyLinePlaceholder":118},[98,2252,2253],{"class":100,"line":137},[98,2254,2255],{"class":530},"    # Direct project ID\n",[98,2257,2258,2260,2263],{"class":100,"line":149},[98,2259,1447],{"class":179},[98,2261,2262],{"class":125}," PROJECT_ID",[98,2264,446],{"class":186},[98,2266,2267,2270,2273],{"class":100,"line":154},[98,2268,2269],{"class":186},"        projects.append(gl.projects.get(",[98,2271,2272],{"class":125},"PROJECT_ID",[98,2274,2275],{"class":186},"))\n",[98,2277,2278],{"class":100,"line":168},[98,2279,119],{"emptyLinePlaceholder":118},[98,2281,2282],{"class":100,"line":173},[98,2283,2284],{"class":530},"    # Groups and projects inside\n",[98,2286,2287,2290,2293],{"class":100,"line":462},[98,2288,2289],{"class":179},"    elif",[98,2291,2292],{"class":125}," GROUP_ID",[98,2294,446],{"class":186},[98,2296,2297,2300,2302,2304,2306],{"class":100,"line":687},[98,2298,2299],{"class":186},"        group ",[98,2301,281],{"class":179},[98,2303,771],{"class":186},[98,2305,596],{"class":125},[98,2307,296],{"class":186},[98,2309,2310],{"class":100,"line":692},[98,2311,119],{"emptyLinePlaceholder":118},[98,2313,2314,2317,2319,2321,2323,2325,2327,2329,2331,2333,2335,2337],{"class":100,"line":703},[98,2315,2316],{"class":179},"        for",[98,2318,1828],{"class":186},[98,2320,804],{"class":179},[98,2322,1801],{"class":186},[98,2324,920],{"class":742},[98,2326,281],{"class":179},[98,2328,925],{"class":125},[98,2330,290],{"class":186},[98,2332,930],{"class":742},[98,2334,281],{"class":179},[98,2336,925],{"class":125},[98,2338,937],{"class":186},[98,2340,2341],{"class":100,"line":714},[98,2342,2343],{"class":530},"            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n",[98,2345,2346,2349,2351],{"class":100,"line":723},[98,2347,2348],{"class":186},"            manageable_project ",[98,2350,281],{"class":179},[98,2352,1972],{"class":186},[98,2354,2355],{"class":100,"line":728},[98,2356,2357],{"class":186},"            projects.append(manageable_project)\n",[98,2359,2360],{"class":100,"line":752},[98,2361,119],{"emptyLinePlaceholder":118},[98,2363,2364],{"class":100,"line":757},[98,2365,2366],{"class":530},"    # All projects on the instance (may take a while to process)\n",[98,2368,2369,2372],{"class":100,"line":763},[98,2370,2371],{"class":179},"    else",[98,2373,446],{"class":186},[98,2375,2376,2379,2381,2384,2387,2389,2391],{"class":100,"line":778},[98,2377,2378],{"class":186},"        projects ",[98,2380,281],{"class":179},[98,2382,2383],{"class":186}," gl.projects.list(",[98,2385,2386],{"class":742},"get_all",[98,2388,281],{"class":179},[98,2390,925],{"class":125},[98,2392,296],{"class":186},[12,2394,2395,2396,2401],{},"L'exemple complet se trouve dans ",[19,2397,2400],{"href":2398,"rel":2399},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py",[],"ce script"," pour lister les paramètres des règles d'approbation des merge requests pour les cibles de projet spécifiées.",[48,2403,2405],{"id":2404},"utilisation-de-lapproche-devsecops-pour-les-actions-de-lecture-api","Utilisation de l’approche DevSecOps pour les actions de lecture API",[12,2407,2408,2409,206],{},"Le jeton d'accès authentifié nécessite un scope ",[73,2410,2411],{},"read_api",[12,2413,2414],{},"Les cas d’utilisation suivants seront abordés :",[562,2416,2417,2420,2423,2426,2429,2432],{},[565,2418,2419],{},"Lister les branches par état de fusion,",[565,2421,2422],{},"Afficher les paramètres du projet pour révision : règles d'approbation des merge requests,",[565,2424,2425],{},"Inventaire : obtenir toutes les variables CI/CD protégées ou masquées,",[565,2427,2428],{},"Télécharger un fichier depuis le dépôt,",[565,2430,2431],{},"Aide à la migration : lister tous les clusters Kubernetes basés sur des certificats,",[565,2433,2434],{},"Productivité des équipes : vérifier si les merge requests existantes nécessitent un rebase après avoir fusionné une merge request de refactorisation majeure.",[578,2436,2438],{"id":2437},"lister-les-branches-par-état-de-fusion","Lister les branches par état de fusion",[12,2440,2441,2442,2448,2449,2454,2455,2460,2461,2464,2465,2468,2469,2472],{},"Pour nettoyer un projet ",[19,2443,2447],{"href":2444,"rel":2445,"title":2446},"https://about.gitlab.com/fr-fr/blog/what-is-git/",[],"Qu'est-ce que Git ?","Git",", il est courant d'évaluer le nombre de branches fusionnées et non fusionnées. En réponse à une ",[19,2450,2453],{"href":2451,"rel":2452},"https://forum.gitlab.com/t/python-gitlab-project-branch-list-filter/80257",[],"question sur le forum de la communauté GitLab"," concernant le filtrage des listes de branches, j'ai écrit un ",[19,2456,2459],{"href":2457,"rel":2458},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/tree/main",[],"script"," aidant à cela. La méthode ",[73,2462,2463],{},"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 ",[73,2466,2467],{},"merged"," sur l'objet ",[73,2470,2471],{},"branch"," est une valeur booléenne qui indique si la branche a été fusionnée ou non.",[89,2474,2476],{"className":257,"code":2475,"language":259,"meta":94,"style":94},"project = 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",[73,2477,2478,2527,2531,2536,2545,2557,2562,2566,2577,2589,2613,2617,2622,2631,2641,2648,2656,2660,2667,2672,2676,2700,2704,2709,2718,2728,2734,2740,2744,2752,2757,2761],{"__ignoreMap":94},[98,2479,2480,2483,2485,2488,2490,2492,2495,2497,2500,2502,2504,2506,2508,2510,2512,2514,2517,2519,2521,2523,2525],{"class":100,"line":101},[98,2481,2482],{"class":186},"project ",[98,2484,281],{"class":179},[98,2486,2487],{"class":186}," gl.projects.get(",[98,2489,2272],{"class":125},[98,2491,290],{"class":186},[98,2493,2494],{"class":742},"lazy",[98,2496,281],{"class":179},[98,2498,2499],{"class":125},"False",[98,2501,290],{"class":186},[98,2503,1266],{"class":742},[98,2505,281],{"class":179},[98,2507,1352],{"class":108},[98,2509,290],{"class":186},[98,2511,1357],{"class":742},[98,2513,281],{"class":179},[98,2515,2516],{"class":108},"\"updated_at\"",[98,2518,290],{"class":186},[98,2520,1367],{"class":742},[98,2522,281],{"class":179},[98,2524,1372],{"class":125},[98,2526,296],{"class":186},[98,2528,2529],{"class":100,"line":115},[98,2530,119],{"emptyLinePlaceholder":118},[98,2532,2533],{"class":100,"line":122},[98,2534,2535],{"class":530},"# Get all branches\n",[98,2537,2538,2541,2543],{"class":100,"line":132},[98,2539,2540],{"class":186},"real_branches ",[98,2542,281],{"class":179},[98,2544,2246],{"class":186},[98,2546,2547,2549,2552,2554],{"class":100,"line":137},[98,2548,798],{"class":179},[98,2550,2551],{"class":186}," branch ",[98,2553,804],{"class":179},[98,2555,2556],{"class":186}," project.branches.list():\n",[98,2558,2559],{"class":100,"line":149},[98,2560,2561],{"class":186},"    real_branches.append(branch)\n",[98,2563,2564],{"class":100,"line":154},[98,2565,119],{"emptyLinePlaceholder":118},[98,2567,2568,2570,2572,2575],{"class":100,"line":168},[98,2569,305],{"class":125},[98,2571,454],{"class":186},[98,2573,2574],{"class":108},"\"All branches\"",[98,2576,296],{"class":186},[98,2578,2579,2581,2584,2586],{"class":100,"line":173},[98,2580,798],{"class":179},[98,2582,2583],{"class":186}," rb ",[98,2585,804],{"class":179},[98,2587,2588],{"class":186}," real_branches:\n",[98,2590,2591,2593,2595,2598,2601,2603,2605,2608,2610],{"class":100,"line":462},[98,2592,451],{"class":125},[98,2594,454],{"class":186},[98,2596,2597],{"class":108},"\"Branch: ",[98,2599,2600],{"class":125},"{b}",[98,2602,823],{"class":108},[98,2604,826],{"class":186},[98,2606,2607],{"class":742},"b",[98,2609,281],{"class":179},[98,2611,2612],{"class":186},"rb.name))\n",[98,2614,2615],{"class":100,"line":687},[98,2616,119],{"emptyLinePlaceholder":118},[98,2618,2619],{"class":100,"line":692},[98,2620,2621],{"class":530},"# Get all merged branches\n",[98,2623,2624,2627,2629],{"class":100,"line":703},[98,2625,2626],{"class":186},"merged_branches_names ",[98,2628,281],{"class":179},[98,2630,2246],{"class":186},[98,2632,2633,2635,2637,2639],{"class":100,"line":714},[98,2634,798],{"class":179},[98,2636,2551],{"class":186},[98,2638,804],{"class":179},[98,2640,2588],{"class":186},[98,2642,2643,2645],{"class":100,"line":723},[98,2644,1447],{"class":179},[98,2646,2647],{"class":186}," branch.default:\n",[98,2649,2650,2653],{"class":100,"line":728},[98,2651,2652],{"class":179},"        continue",[98,2654,2655],{"class":530}," # ignore the default branch for merge status\n",[98,2657,2658],{"class":100,"line":752},[98,2659,119],{"emptyLinePlaceholder":118},[98,2661,2662,2664],{"class":100,"line":757},[98,2663,1447],{"class":179},[98,2665,2666],{"class":186}," branch.merged:\n",[98,2668,2669],{"class":100,"line":763},[98,2670,2671],{"class":186},"        merged_branches_names.append(branch.name)\n",[98,2673,2674],{"class":100,"line":778},[98,2675,119],{"emptyLinePlaceholder":118},[98,2677,2678,2680,2682,2685,2687,2689,2691,2693,2695,2697],{"class":100,"line":783},[98,2679,305],{"class":125},[98,2681,454],{"class":186},[98,2683,2684],{"class":108},"\"Branches merged: ",[98,2686,2600],{"class":125},[98,2688,823],{"class":108},[98,2690,826],{"class":186},[98,2692,2607],{"class":742},[98,2694,281],{"class":179},[98,2696,1888],{"class":108},[98,2698,2699],{"class":186},".join(merged_branches_names)))\n",[98,2701,2702],{"class":100,"line":795},[98,2703,119],{"emptyLinePlaceholder":118},[98,2705,2706],{"class":100,"line":810},[98,2707,2708],{"class":530},"# Get un-merged branches\n",[98,2710,2711,2714,2716],{"class":100,"line":837},[98,2712,2713],{"class":186},"not_merged_branches_names ",[98,2715,281],{"class":179},[98,2717,2246],{"class":186},[98,2719,2720,2722,2724,2726],{"class":100,"line":842},[98,2721,798],{"class":179},[98,2723,2551],{"class":186},[98,2725,804],{"class":179},[98,2727,2588],{"class":186},[98,2729,2730,2732],{"class":100,"line":854},[98,2731,1447],{"class":179},[98,2733,2647],{"class":186},[98,2735,2736,2738],{"class":100,"line":867},[98,2737,2652],{"class":179},[98,2739,2655],{"class":530},[98,2741,2742],{"class":100,"line":891},[98,2743,119],{"emptyLinePlaceholder":118},[98,2745,2746,2748,2750],{"class":100,"line":896},[98,2747,1447],{"class":179},[98,2749,440],{"class":179},[98,2751,2666],{"class":186},[98,2753,2754],{"class":100,"line":908},[98,2755,2756],{"class":186},"        not_merged_branches_names.append(branch.name)\n",[98,2758,2759],{"class":100,"line":940},[98,2760,119],{"emptyLinePlaceholder":118},[98,2762,2763,2765,2767,2770,2772,2774,2776,2778,2780,2782],{"class":100,"line":962},[98,2764,305],{"class":125},[98,2766,454],{"class":186},[98,2768,2769],{"class":108},"\"Branches not merged: ",[98,2771,2600],{"class":125},[98,2773,823],{"class":108},[98,2775,826],{"class":186},[98,2777,2607],{"class":742},[98,2779,281],{"class":179},[98,2781,1888],{"class":108},[98,2783,2784],{"class":186},".join(not_merged_branches_names)))\n",[12,2786,2787],{},"Le 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.",[578,2789,2791],{"id":2790},"afficher-les-paramètres-du-projet-pour-examen-règles-dapprobation-des-merge-requests","Afficher les paramètres du projet pour examen : règles d'approbation des merge requests",[12,2793,220,2794,2797],{},[19,2795,2459],{"href":2398,"rel":2796},[]," 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.",[89,2799,2801],{"className":257,"code":2800,"language":259,"meta":94,"style":94},"\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",[73,2802,2803,2807,2812,2817,2828,2845,2850,2889,2919,2923,2936,2971,2990,3017],{"__ignoreMap":94},[98,2804,2805],{"class":100,"line":101},[98,2806,119],{"emptyLinePlaceholder":118},[98,2808,2809],{"class":100,"line":115},[98,2810,2811],{"class":530},"    # Loop over projects and print the settings\n",[98,2813,2814],{"class":100,"line":122},[98,2815,2816],{"class":530},"    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_request_approvals.html\n",[98,2818,2819,2822,2824,2826],{"class":100,"line":132},[98,2820,2821],{"class":179},"    for",[98,2823,1828],{"class":186},[98,2825,804],{"class":179},[98,2827,1833],{"class":186},[98,2829,2830,2833,2836,2839,2841,2843],{"class":100,"line":137},[98,2831,2832],{"class":179},"        if",[98,2834,2835],{"class":125}," len",[98,2837,2838],{"class":186},"(project.approvalrules.list()) ",[98,2840,190],{"class":179},[98,2842,1543],{"class":125},[98,2844,446],{"class":186},[98,2846,2847],{"class":100,"line":149},[98,2848,2849],{"class":530},"            #print(project) #debug\n",[98,2851,2852,2855,2857,2860,2863,2866,2869,2871,2873,2876,2878,2881,2884,2886],{"class":100,"line":154},[98,2853,2854],{"class":125},"            print",[98,2856,454],{"class":186},[98,2858,2859],{"class":108},"\"# Project: ",[98,2861,2862],{"class":125},"{name}",[98,2864,2865],{"class":108},", ID: ",[98,2867,2868],{"class":125},"{id}\\n\\n",[98,2870,823],{"class":108},[98,2872,826],{"class":186},[98,2874,2875],{"class":742},"name",[98,2877,281],{"class":179},[98,2879,2880],{"class":186},"project.name_with_namespace, ",[98,2882,2883],{"class":742},"id",[98,2885,281],{"class":179},[98,2887,2888],{"class":186},"project.id))\n",[98,2890,2891,2893,2895,2898,2901,2904,2907,2909,2911,2914,2916],{"class":100,"line":168},[98,2892,2854],{"class":125},[98,2894,454],{"class":186},[98,2896,2897],{"class":108},"\"[MR Approval settings](",[98,2899,2900],{"class":125},"{url}",[98,2902,2903],{"class":108},"/-/settings/merge_requests)",[98,2905,2906],{"class":125},"\\n\\n",[98,2908,823],{"class":108},[98,2910,826],{"class":186},[98,2912,2913],{"class":742},"url",[98,2915,281],{"class":179},[98,2917,2918],{"class":186},"project.web_url))\n",[98,2920,2921],{"class":100,"line":173},[98,2922,119],{"emptyLinePlaceholder":118},[98,2924,2925,2928,2931,2933],{"class":100,"line":462},[98,2926,2927],{"class":179},"            for",[98,2929,2930],{"class":186}," ar ",[98,2932,804],{"class":179},[98,2934,2935],{"class":186}," project.approvalrules.list():\n",[98,2937,2938,2941,2943,2946,2948,2950,2953,2955,2957,2959,2961,2964,2966,2968],{"class":100,"line":687},[98,2939,2940],{"class":125},"                print",[98,2942,454],{"class":186},[98,2944,2945],{"class":108},"\"## Approval rule: ",[98,2947,2862],{"class":125},[98,2949,2865],{"class":108},[98,2951,2952],{"class":125},"{id}",[98,2954,823],{"class":108},[98,2956,826],{"class":186},[98,2958,2875],{"class":742},[98,2960,281],{"class":179},[98,2962,2963],{"class":186},"ar.name, ",[98,2965,2883],{"class":742},[98,2967,281],{"class":179},[98,2969,2970],{"class":186},"ar.id))\n",[98,2972,2973,2975,2977,2979,2981,2984,2986,2988],{"class":100,"line":692},[98,2974,2940],{"class":125},[98,2976,454],{"class":186},[98,2978,823],{"class":108},[98,2980,1937],{"class":125},[98,2982,2983],{"class":108},"```json",[98,2985,1937],{"class":125},[98,2987,823],{"class":108},[98,2989,296],{"class":186},[98,2991,2992,2994,2997,3000,3002,3005,3008,3011,3014],{"class":100,"line":703},[98,2993,2940],{"class":125},[98,2995,2996],{"class":186},"(json.dumps(ar.attributes, ",[98,2998,2999],{"class":742},"indent",[98,3001,281],{"class":179},[98,3003,3004],{"class":125},"2",[98,3006,3007],{"class":186},")) ",[98,3009,3010],{"class":530},"# ",[98,3012,3013],{"class":179},"TODO",[98,3015,3016],{"class":530},": can be more beautiful, but serves its purpose with pretty print JSON\n",[98,3018,3019,3021,3023,3025,3027,3030,3032,3034],{"class":100,"line":714},[98,3020,2940],{"class":125},[98,3022,454],{"class":186},[98,3024,823],{"class":108},[98,3026,1937],{"class":125},[98,3028,3029],{"class":108},"```",[98,3031,1937],{"class":125},[98,3033,823],{"class":108},[98,3035,296],{"class":186},[578,3037,3039],{"id":3038},"inventaire-obtenir-toutes-les-variables-cicd-protégées-ou-masquées","Inventaire : obtenir toutes les variables CI/CD protégées ou masquées",[12,3041,3042,3043,3049],{},"Les ",[19,3044,3048],{"href":3045,"rel":3046,"title":3047},"https://docs.gitlab.com/ci/variables/",[],"Variables CI/CD","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.",[12,3051,220,3052,3056],{},[19,3053,2459],{"href":3054,"rel":3055},"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.",[89,3058,3060],{"className":257,"code":3059,"language":259,"meta":94,"style":94},"#!/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",[73,3061,3062,3066,3070,3076,3082,3088,3092,3097,3108,3121,3169,3173,3189,3204,3220,3240,3244,3254,3265,3273,3277,3297,3301,3306,3314,3319,3327,3331,3336,3344,3353,3357,3362,3371,3384,3388,3414,3419,3428,3433,3437,3442,3446,3451,3458,3475,3493,3498,3510,3515,3521,3526,3532,3544,3552,3573,3579,3587,3599,3614,3619,3625,3636,3673,3694,3700,3705,3720,3725,3731,3742,3748,3756,3762,3767,3800,3821,3827,3832],{"__ignoreMap":94},[98,3063,3064],{"class":100,"line":101},[98,3065,607],{"class":530},[98,3067,3068],{"class":100,"line":115},[98,3069,119],{"emptyLinePlaceholder":118},[98,3071,3072,3074],{"class":100,"line":122},[98,3073,266],{"class":179},[98,3075,379],{"class":186},[98,3077,3078,3080],{"class":100,"line":132},[98,3079,266],{"class":179},[98,3081,269],{"class":186},[98,3083,3084,3086],{"class":100,"line":137},[98,3085,266],{"class":179},[98,3087,392],{"class":186},[98,3089,3090],{"class":100,"line":149},[98,3091,119],{"emptyLinePlaceholder":118},[98,3093,3094],{"class":100,"line":154},[98,3095,3096],{"class":530},"# Helper function to evaluate secrets and print the variables\n",[98,3098,3099,3102,3105],{"class":100,"line":168},[98,3100,3101],{"class":179},"def",[98,3103,3104],{"class":104}," eval_print_var",[98,3106,3107],{"class":186},"(var):\n",[98,3109,3110,3112,3115,3118],{"class":100,"line":173},[98,3111,1447],{"class":179},[98,3113,3114],{"class":186}," var.protected ",[98,3116,3117],{"class":179},"or",[98,3119,3120],{"class":186}," var.masked:\n",[98,3122,3123,3125,3127,3130,3132,3135,3137,3140,3143,3145,3147,3149,3151,3154,3156,3158,3161,3164,3166],{"class":100,"line":462},[98,3124,1920],{"class":125},[98,3126,454],{"class":186},[98,3128,3129],{"class":108},"\"🛡️🛡️🛡️ Potential secret: Variable '",[98,3131,2862],{"class":125},[98,3133,3134],{"class":108},"', protected ",[98,3136,877],{"class":125},[98,3138,3139],{"class":108},", masked: ",[98,3141,3142],{"class":125},"{m}",[98,3144,823],{"class":108},[98,3146,826],{"class":186},[98,3148,2875],{"class":742},[98,3150,281],{"class":179},[98,3152,3153],{"class":186},"var.key,",[98,3155,12],{"class":742},[98,3157,281],{"class":179},[98,3159,3160],{"class":186},"var.protected,",[98,3162,3163],{"class":742},"m",[98,3165,281],{"class":179},[98,3167,3168],{"class":186},"var.masked))\n",[98,3170,3171],{"class":100,"line":687},[98,3172,119],{"emptyLinePlaceholder":118},[98,3174,3175,3177,3179,3181,3183,3185,3187],{"class":100,"line":692},[98,3176,401],{"class":125},[98,3178,404],{"class":179},[98,3180,284],{"class":186},[98,3182,287],{"class":108},[98,3184,290],{"class":186},[98,3186,293],{"class":108},[98,3188,296],{"class":186},[98,3190,3191,3193,3195,3197,3199,3201],{"class":100,"line":703},[98,3192,419],{"class":125},[98,3194,404],{"class":179},[98,3196,284],{"class":186},[98,3198,426],{"class":108},[98,3200,2173],{"class":186},[98,3202,3203],{"class":530},"# token requires maintainer+ permissions. Instance variables require admin access.\n",[98,3205,3206,3208,3210,3212,3215,3217],{"class":100,"line":714},[98,3207,2272],{"class":125},[98,3209,404],{"class":179},[98,3211,284],{"class":186},[98,3213,3214],{"class":108},"'GL_PROJECT_ID'",[98,3216,2173],{"class":186},[98,3218,3219],{"class":530},"#optional\n",[98,3221,3222,3224,3226,3228,3230,3232,3235,3237],{"class":100,"line":723},[98,3223,596],{"class":125},[98,3225,404],{"class":179},[98,3227,284],{"class":186},[98,3229,665],{"class":108},[98,3231,290],{"class":186},[98,3233,3234],{"class":125},"8034603",[98,3236,2173],{"class":186},[98,3238,3239],{"class":530},"# https://gitlab.com/everyonecancontribute\n",[98,3241,3242],{"class":100,"line":728},[98,3243,119],{"emptyLinePlaceholder":118},[98,3245,3246,3248,3250,3252],{"class":100,"line":752},[98,3247,437],{"class":179},[98,3249,440],{"class":179},[98,3251,443],{"class":125},[98,3253,446],{"class":186},[98,3255,3256,3258,3260,3263],{"class":100,"line":757},[98,3257,451],{"class":125},[98,3259,454],{"class":186},[98,3261,3262],{"class":108},"\"🤔 Please set the GL_TOKEN env variable.\"",[98,3264,296],{"class":186},[98,3266,3267,3269,3271],{"class":100,"line":763},[98,3268,465],{"class":186},[98,3270,468],{"class":125},[98,3272,296],{"class":186},[98,3274,3275],{"class":100,"line":778},[98,3276,119],{"emptyLinePlaceholder":118},[98,3278,3279,3281,3283,3285,3287,3289,3291,3293,3295],{"class":100,"line":783},[98,3280,516],{"class":186},[98,3282,281],{"class":179},[98,3284,735],{"class":186},[98,3286,401],{"class":125},[98,3288,290],{"class":186},[98,3290,743],{"class":742},[98,3292,281],{"class":179},[98,3294,419],{"class":125},[98,3296,296],{"class":186},[98,3298,3299],{"class":100,"line":795},[98,3300,119],{"emptyLinePlaceholder":118},[98,3302,3303],{"class":100,"line":810},[98,3304,3305],{"class":530},"# Collect all projects, or prefer projects from a group id, or a project id\n",[98,3307,3308,3310,3312],{"class":100,"line":837},[98,3309,1796],{"class":186},[98,3311,281],{"class":179},[98,3313,2246],{"class":186},[98,3315,3316],{"class":100,"line":842},[98,3317,3318],{"class":530},"# Collect all groups, or prefer group from a group id\n",[98,3320,3321,3323,3325],{"class":100,"line":854},[98,3322,1388],{"class":186},[98,3324,281],{"class":179},[98,3326,2246],{"class":186},[98,3328,3329],{"class":100,"line":867},[98,3330,119],{"emptyLinePlaceholder":118},[98,3332,3333],{"class":100,"line":891},[98,3334,3335],{"class":530},"# Direct project ID\n",[98,3337,3338,3340,3342],{"class":100,"line":896},[98,3339,437],{"class":179},[98,3341,2262],{"class":125},[98,3343,446],{"class":186},[98,3345,3346,3349,3351],{"class":100,"line":908},[98,3347,3348],{"class":186},"    projects.append(gl.projects.get(",[98,3350,2272],{"class":125},[98,3352,2275],{"class":186},[98,3354,3355],{"class":100,"line":940},[98,3356,119],{"emptyLinePlaceholder":118},[98,3358,3359],{"class":100,"line":962},[98,3360,3361],{"class":530},"# Groups and projects inside\n",[98,3363,3364,3367,3369],{"class":100,"line":967},[98,3365,3366],{"class":179},"elif",[98,3368,2292],{"class":125},[98,3370,446],{"class":186},[98,3372,3373,3376,3378,3380,3382],{"class":100,"line":979},[98,3374,3375],{"class":186},"    group ",[98,3377,281],{"class":179},[98,3379,771],{"class":186},[98,3381,596],{"class":125},[98,3383,296],{"class":186},[98,3385,3386],{"class":100,"line":1002},[98,3387,119],{"emptyLinePlaceholder":118},[98,3389,3390,3392,3394,3396,3398,3400,3402,3404,3406,3408,3410,3412],{"class":100,"line":1027},[98,3391,2821],{"class":179},[98,3393,1828],{"class":186},[98,3395,804],{"class":179},[98,3397,1801],{"class":186},[98,3399,920],{"class":742},[98,3401,281],{"class":179},[98,3403,925],{"class":125},[98,3405,290],{"class":186},[98,3407,930],{"class":742},[98,3409,281],{"class":179},[98,3411,925],{"class":125},[98,3413,937],{"class":186},[98,3415,3416],{"class":100,"line":1032},[98,3417,3418],{"class":530},"        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples\n",[98,3420,3421,3424,3426],{"class":100,"line":1044},[98,3422,3423],{"class":186},"        manageable_project ",[98,3425,281],{"class":179},[98,3427,1972],{"class":186},[98,3429,3430],{"class":100,"line":1057},[98,3431,3432],{"class":186},"        projects.append(manageable_project)\n",[98,3434,3435],{"class":100,"line":1080},[98,3436,119],{"emptyLinePlaceholder":118},[98,3438,3439],{"class":100,"line":1085},[98,3440,3441],{"class":186},"    groups.append(group)\n",[98,3443,3444],{"class":100,"line":1097},[98,3445,119],{"emptyLinePlaceholder":118},[98,3447,3448],{"class":100,"line":1119},[98,3449,3450],{"class":530},"# All projects/groups on the instance (may take a while to process, use iterators to fetch on-demand).\n",[98,3452,3454,3456],{"class":100,"line":3453},44,[98,3455,1621],{"class":179},[98,3457,446],{"class":186},[98,3459,3461,3463,3465,3467,3469,3471,3473],{"class":100,"line":3460},45,[98,3462,2241],{"class":186},[98,3464,281],{"class":179},[98,3466,2383],{"class":186},[98,3468,1396],{"class":742},[98,3470,281],{"class":179},[98,3472,925],{"class":125},[98,3474,296],{"class":186},[98,3476,3478,3481,3483,3485,3487,3489,3491],{"class":100,"line":3477},46,[98,3479,3480],{"class":186},"    groups ",[98,3482,281],{"class":179},[98,3484,1393],{"class":186},[98,3486,1396],{"class":742},[98,3488,281],{"class":179},[98,3490,925],{"class":125},[98,3492,296],{"class":186},[98,3494,3496],{"class":100,"line":3495},47,[98,3497,119],{"emptyLinePlaceholder":118},[98,3499,3501,3503,3505,3508],{"class":100,"line":3500},48,[98,3502,305],{"class":125},[98,3504,454],{"class":186},[98,3506,3507],{"class":108},"\"# List of all CI/CD variables marked as secret (instance, groups, projects)\"",[98,3509,296],{"class":186},[98,3511,3513],{"class":100,"line":3512},49,[98,3514,119],{"emptyLinePlaceholder":118},[98,3516,3518],{"class":100,"line":3517},50,[98,3519,3520],{"class":530},"# https://python-gitlab.readthedocs.io/en/stable/gl_objects/variables.html\n",[98,3522,3524],{"class":100,"line":3523},51,[98,3525,119],{"emptyLinePlaceholder":118},[98,3527,3529],{"class":100,"line":3528},52,[98,3530,3531],{"class":530},"# Instance variables (if the token has permissions)\n",[98,3533,3535,3537,3539,3542],{"class":100,"line":3534},53,[98,3536,305],{"class":125},[98,3538,454],{"class":186},[98,3540,3541],{"class":108},"\"Instance variables, if accessible\"",[98,3543,296],{"class":186},[98,3545,3547,3550],{"class":100,"line":3546},54,[98,3548,3549],{"class":179},"try",[98,3551,446],{"class":186},[98,3553,3555,3557,3560,3562,3565,3567,3569,3571],{"class":100,"line":3554},55,[98,3556,2821],{"class":179},[98,3558,3559],{"class":186}," i_var ",[98,3561,804],{"class":179},[98,3563,3564],{"class":186}," gl.variables.list(",[98,3566,1396],{"class":742},[98,3568,281],{"class":179},[98,3570,925],{"class":125},[98,3572,937],{"class":186},[98,3574,3576],{"class":100,"line":3575},56,[98,3577,3578],{"class":186},"        eval_print_var(i_var)\n",[98,3580,3582,3585],{"class":100,"line":3581},57,[98,3583,3584],{"class":179},"except",[98,3586,446],{"class":186},[98,3588,3590,3592,3594,3597],{"class":100,"line":3589},58,[98,3591,451],{"class":125},[98,3593,454],{"class":186},[98,3595,3596],{"class":108},"\"No permission to fetch global instance variables, continueing without.\"",[98,3598,296],{"class":186},[98,3600,3602,3604,3606,3608,3610,3612],{"class":100,"line":3601},59,[98,3603,451],{"class":125},[98,3605,454],{"class":186},[98,3607,823],{"class":108},[98,3609,1937],{"class":125},[98,3611,823],{"class":108},[98,3613,296],{"class":186},[98,3615,3617],{"class":100,"line":3616},60,[98,3618,119],{"emptyLinePlaceholder":118},[98,3620,3622],{"class":100,"line":3621},61,[98,3623,3624],{"class":530},"# group variables (maintainer permissions for groups required)\n",[98,3626,3628,3630,3632,3634],{"class":100,"line":3627},62,[98,3629,798],{"class":179},[98,3631,1437],{"class":186},[98,3633,804],{"class":179},[98,3635,1442],{"class":186},[98,3637,3639,3641,3643,3646,3649,3652,3654,3656,3658,3661,3663,3666,3668,3670],{"class":100,"line":3638},63,[98,3640,451],{"class":125},[98,3642,454],{"class":186},[98,3644,3645],{"class":108},"\"Group ",[98,3647,3648],{"class":125},"{n}",[98,3650,3651],{"class":108},", URL: ",[98,3653,1134],{"class":125},[98,3655,823],{"class":108},[98,3657,826],{"class":186},[98,3659,3660],{"class":742},"n",[98,3662,281],{"class":179},[98,3664,3665],{"class":186},"group.full_path, ",[98,3667,1148],{"class":742},[98,3669,281],{"class":179},[98,3671,3672],{"class":186},"group.web_url))\n",[98,3674,3676,3678,3681,3683,3686,3688,3690,3692],{"class":100,"line":3675},64,[98,3677,2821],{"class":179},[98,3679,3680],{"class":186}," g_var ",[98,3682,804],{"class":179},[98,3684,3685],{"class":186}," group.variables.list(",[98,3687,1396],{"class":742},[98,3689,281],{"class":179},[98,3691,925],{"class":125},[98,3693,937],{"class":186},[98,3695,3697],{"class":100,"line":3696},65,[98,3698,3699],{"class":186},"        eval_print_var(g_var)\n",[98,3701,3703],{"class":100,"line":3702},66,[98,3704,119],{"emptyLinePlaceholder":118},[98,3706,3708,3710,3712,3714,3716,3718],{"class":100,"line":3707},67,[98,3709,451],{"class":125},[98,3711,454],{"class":186},[98,3713,823],{"class":108},[98,3715,1937],{"class":125},[98,3717,823],{"class":108},[98,3719,296],{"class":186},[98,3721,3723],{"class":100,"line":3722},68,[98,3724,119],{"emptyLinePlaceholder":118},[98,3726,3728],{"class":100,"line":3727},69,[98,3729,3730],{"class":530},"# Loop over projects and print the settings\n",[98,3732,3734,3736,3738,3740],{"class":100,"line":3733},70,[98,3735,798],{"class":179},[98,3737,1828],{"class":186},[98,3739,804],{"class":179},[98,3741,1833],{"class":186},[98,3743,3745],{"class":100,"line":3744},71,[98,3746,3747],{"class":530},"    # skip archived projects, they throw 403 errors\n",[98,3749,3751,3753],{"class":100,"line":3750},72,[98,3752,1447],{"class":179},[98,3754,3755],{"class":186}," project.archived:\n",[98,3757,3759],{"class":100,"line":3758},73,[98,3760,3761],{"class":179},"        continue\n",[98,3763,3765],{"class":100,"line":3764},74,[98,3766,119],{"emptyLinePlaceholder":118},[98,3768,3770,3772,3774,3777,3779,3781,3783,3785,3787,3789,3791,3794,3796,3798],{"class":100,"line":3769},75,[98,3771,451],{"class":125},[98,3773,454],{"class":186},[98,3775,3776],{"class":108},"\"Project ",[98,3778,3648],{"class":125},[98,3780,3651],{"class":108},[98,3782,1134],{"class":125},[98,3784,823],{"class":108},[98,3786,826],{"class":186},[98,3788,3660],{"class":742},[98,3790,281],{"class":179},[98,3792,3793],{"class":186},"project.path_with_namespace, ",[98,3795,1148],{"class":742},[98,3797,281],{"class":179},[98,3799,2918],{"class":186},[98,3801,3803,3805,3808,3810,3813,3815,3817,3819],{"class":100,"line":3802},76,[98,3804,2821],{"class":179},[98,3806,3807],{"class":186}," p_var ",[98,3809,804],{"class":179},[98,3811,3812],{"class":186}," project.variables.list(",[98,3814,1396],{"class":742},[98,3816,281],{"class":179},[98,3818,925],{"class":125},[98,3820,937],{"class":186},[98,3822,3824],{"class":100,"line":3823},77,[98,3825,3826],{"class":186},"        eval_print_var(p_var)\n",[98,3828,3830],{"class":100,"line":3829},78,[98,3831,119],{"emptyLinePlaceholder":118},[98,3833,3835,3837,3839,3841,3843,3845],{"class":100,"line":3834},79,[98,3836,451],{"class":125},[98,3838,454],{"class":186},[98,3840,823],{"class":108},[98,3842,1937],{"class":125},[98,3844,823],{"class":108},[98,3846,296],{"class":186},[12,3848,3849,3850,206],{},"Le 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 ",[19,3851,3854],{"href":3852,"rel":3853},"https://docs.gitlab.com/ci/secrets/",[],"fournisseurs externes",[578,3856,3858],{"id":3857},"télécharger-un-fichier-depuis-le-dépôt","Télécharger un fichier depuis le dépôt",[12,3860,3861,3862,3866],{},"L'objectif de ce ",[19,3863,2459],{"href":3864,"rel":3865},"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.",[89,3868,3870],{"className":257,"code":3869,"language":259,"meta":94,"style":94},"# Goal: Try to download README.md from https://gitlab.com/gitlab-da/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",[73,3871,3872,3877,3887,3897,3901,3906,3918,3964,3968,3988,4003,4010,4014,4019,4047,4054,4058,4063,4089],{"__ignoreMap":94},[98,3873,3874],{"class":100,"line":101},[98,3875,3876],{"class":530},"# Goal: Try to download README.md from https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/README.md\n",[98,3878,3879,3882,3884],{"class":100,"line":115},[98,3880,3881],{"class":125},"FILE_NAME",[98,3883,404],{"class":179},[98,3885,3886],{"class":108}," 'README.md'\n",[98,3888,3889,3892,3894],{"class":100,"line":122},[98,3890,3891],{"class":125},"BRANCH_NAME",[98,3893,404],{"class":179},[98,3895,3896],{"class":108}," 'main'\n",[98,3898,3899],{"class":100,"line":132},[98,3900,119],{"emptyLinePlaceholder":118},[98,3902,3903],{"class":100,"line":137},[98,3904,3905],{"class":530},"# Search the file in the repository tree and get the raw blob\n",[98,3907,3908,3910,3913,3915],{"class":100,"line":149},[98,3909,798],{"class":179},[98,3911,3912],{"class":186}," f ",[98,3914,804],{"class":179},[98,3916,3917],{"class":186}," project.repository_tree():\n",[98,3919,3920,3922,3924,3927,3929,3932,3934,3937,3939,3941,3943,3946,3949,3952,3954,3956,3958,3961],{"class":100,"line":154},[98,3921,451],{"class":125},[98,3923,454],{"class":186},[98,3925,3926],{"class":108},"\"File path '",[98,3928,2862],{"class":125},[98,3930,3931],{"class":108},"' with id '",[98,3933,2952],{"class":125},[98,3935,3936],{"class":108},"'\"",[98,3938,826],{"class":186},[98,3940,2875],{"class":742},[98,3942,281],{"class":179},[98,3944,3945],{"class":186},"f[",[98,3947,3948],{"class":108},"'name'",[98,3950,3951],{"class":186},"], ",[98,3953,2883],{"class":742},[98,3955,281],{"class":179},[98,3957,3945],{"class":186},[98,3959,3960],{"class":108},"'id'",[98,3962,3963],{"class":186},"]))\n",[98,3965,3966],{"class":100,"line":168},[98,3967,119],{"emptyLinePlaceholder":118},[98,3969,3970,3972,3975,3977,3980,3983,3986],{"class":100,"line":173},[98,3971,1447],{"class":179},[98,3973,3974],{"class":186}," f[",[98,3976,3948],{"class":108},[98,3978,3979],{"class":186},"] ",[98,3981,3982],{"class":179},"==",[98,3984,3985],{"class":125}," FILE_NAME",[98,3987,446],{"class":186},[98,3989,3990,3993,3995,3998,4000],{"class":100,"line":462},[98,3991,3992],{"class":186},"        f_content ",[98,3994,281],{"class":179},[98,3996,3997],{"class":186}," project.repository_raw_blob(f[",[98,3999,3960],{"class":108},[98,4001,4002],{"class":186},"])\n",[98,4004,4005,4007],{"class":100,"line":687},[98,4006,1920],{"class":125},[98,4008,4009],{"class":186},"(f_content)\n",[98,4011,4012],{"class":100,"line":692},[98,4013,119],{"emptyLinePlaceholder":118},[98,4015,4016],{"class":100,"line":703},[98,4017,4018],{"class":530},"# Alternative approach: Get the raw file from the main branch\n",[98,4020,4021,4024,4026,4029,4032,4034,4036,4038,4041,4043,4045],{"class":100,"line":714},[98,4022,4023],{"class":186},"raw_content ",[98,4025,281],{"class":179},[98,4027,4028],{"class":186}," project.files.raw(",[98,4030,4031],{"class":742},"file_path",[98,4033,281],{"class":179},[98,4035,3881],{"class":125},[98,4037,290],{"class":186},[98,4039,4040],{"class":742},"ref",[98,4042,281],{"class":179},[98,4044,3891],{"class":125},[98,4046,296],{"class":186},[98,4048,4049,4051],{"class":100,"line":723},[98,4050,305],{"class":125},[98,4052,4053],{"class":186},"(raw_content)\n",[98,4055,4056],{"class":100,"line":728},[98,4057,119],{"emptyLinePlaceholder":118},[98,4059,4060],{"class":100,"line":752},[98,4061,4062],{"class":530},"# Store the file on disk\n",[98,4064,4065,4068,4071,4073,4076,4078,4081,4083,4086],{"class":100,"line":757},[98,4066,4067],{"class":179},"with",[98,4069,4070],{"class":125}," open",[98,4072,454],{"class":186},[98,4074,4075],{"class":108},"'raw_README.md'",[98,4077,290],{"class":186},[98,4079,4080],{"class":108},"'wb'",[98,4082,2173],{"class":186},[98,4084,4085],{"class":179},"as",[98,4087,4088],{"class":186}," f:\n",[98,4090,4091,4094,4096,4098,4100,4102,4104,4106,4108,4110,4113,4115,4117,4119,4122,4124],{"class":100,"line":763},[98,4092,4093],{"class":186},"    project.files.raw(",[98,4095,4031],{"class":742},[98,4097,281],{"class":179},[98,4099,3881],{"class":125},[98,4101,290],{"class":186},[98,4103,4040],{"class":742},[98,4105,281],{"class":179},[98,4107,3891],{"class":125},[98,4109,290],{"class":186},[98,4111,4112],{"class":742},"streamed",[98,4114,281],{"class":179},[98,4116,925],{"class":125},[98,4118,290],{"class":186},[98,4120,4121],{"class":742},"action",[98,4123,281],{"class":179},[98,4125,4126],{"class":186},"f.write)\n",[578,4128,4130],{"id":4129},"aide-à-la-migration-lister-tous-les-clusters-kubernetes-basés-sur-des-certificats","Aide à la migration : lister tous les clusters Kubernetes basés sur des certificats",[12,4132,4133,4134,4140,4141,4146],{},"L'intégration des clusters ",[19,4135,4139],{"href":4136,"rel":4137,"title":4138},"https://about.gitlab.com/fr-fr/blog/kubernetes-the-container-orchestration-solution/",[],"Qu'est-ce que Kubernetes ?","Kubernetes"," basée sur des certificats dans GitLab ",[19,4142,4145],{"href":4143,"rel":4144},"https://docs.gitlab.com/update/deprecations/#self-managed-certificate-based-integration-with-kubernetes",[],"a été dépréciée",". Pour faciliter les plans de migration, l'inventaire des groupes et projets existants peut être automatisé à l'aide de l'API GitLab.",[89,4148,4150],{"className":257,"code":4149,"language":259,"meta":94,"style":94},"groups = [ ]\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",[73,4151,4152,4161,4165,4170,4179,4183,4193,4220,4230,4235,4239,4249,4258,4262,4272,4277,4287,4291,4306,4316,4320,4325,4349,4353,4363,4367,4375,4379,4384,4390,4395,4399,4409,4413,4428,4438,4442,4447,4462,4474,4484,4521,4526,4530,4545,4557,4566,4600],{"__ignoreMap":94},[98,4153,4154,4156,4158],{"class":100,"line":101},[98,4155,1388],{"class":186},[98,4157,281],{"class":179},[98,4159,4160],{"class":186}," [ ]\n",[98,4162,4163],{"class":100,"line":115},[98,4164,119],{"emptyLinePlaceholder":118},[98,4166,4167],{"class":100,"line":122},[98,4168,4169],{"class":530},"# get GROUP_ID group\n",[98,4171,4172,4175,4177],{"class":100,"line":132},[98,4173,4174],{"class":186},"groups.append(gl.groups.get(",[98,4176,596],{"class":125},[98,4178,2275],{"class":186},[98,4180,4181],{"class":100,"line":137},[98,4182,119],{"emptyLinePlaceholder":118},[98,4184,4185,4187,4189,4191],{"class":100,"line":149},[98,4186,798],{"class":179},[98,4188,1437],{"class":186},[98,4190,804],{"class":179},[98,4192,1442],{"class":186},[98,4194,4195,4197,4199,4201,4204,4206,4208,4210,4212,4214,4216,4218],{"class":100,"line":154},[98,4196,2821],{"class":179},[98,4198,801],{"class":186},[98,4200,804],{"class":179},[98,4202,4203],{"class":186}," group.subgroups.list(",[98,4205,920],{"class":742},[98,4207,281],{"class":179},[98,4209,925],{"class":125},[98,4211,290],{"class":186},[98,4213,930],{"class":742},[98,4215,281],{"class":179},[98,4217,925],{"class":125},[98,4219,937],{"class":186},[98,4221,4222,4225,4227],{"class":100,"line":168},[98,4223,4224],{"class":186},"        real_group ",[98,4226,281],{"class":179},[98,4228,4229],{"class":186}," gl.groups.get(sg.id)\n",[98,4231,4232],{"class":100,"line":173},[98,4233,4234],{"class":186},"        groups.append(real_group)\n",[98,4236,4237],{"class":100,"line":462},[98,4238,119],{"emptyLinePlaceholder":118},[98,4240,4241,4244,4246],{"class":100,"line":687},[98,4242,4243],{"class":186},"group_clusters ",[98,4245,281],{"class":179},[98,4247,4248],{"class":186}," {}\n",[98,4250,4251,4254,4256],{"class":100,"line":692},[98,4252,4253],{"class":186},"project_clusters ",[98,4255,281],{"class":179},[98,4257,4248],{"class":186},[98,4259,4260],{"class":100,"line":703},[98,4261,119],{"emptyLinePlaceholder":118},[98,4263,4264,4266,4268,4270],{"class":100,"line":714},[98,4265,798],{"class":179},[98,4267,1437],{"class":186},[98,4269,804],{"class":179},[98,4271,1442],{"class":186},[98,4273,4274],{"class":100,"line":723},[98,4275,4276],{"class":530},"    #Collect group clusters\n",[98,4278,4279,4282,4284],{"class":100,"line":728},[98,4280,4281],{"class":186},"    g_clusters ",[98,4283,281],{"class":179},[98,4285,4286],{"class":186}," group.clusters.list()\n",[98,4288,4289],{"class":100,"line":752},[98,4290,119],{"emptyLinePlaceholder":118},[98,4292,4293,4295,4297,4300,4302,4304],{"class":100,"line":757},[98,4294,1447],{"class":179},[98,4296,2835],{"class":125},[98,4298,4299],{"class":186},"(g_clusters) ",[98,4301,190],{"class":179},[98,4303,1543],{"class":125},[98,4305,446],{"class":186},[98,4307,4308,4311,4313],{"class":100,"line":763},[98,4309,4310],{"class":186},"        group_clusters[group.id] ",[98,4312,281],{"class":179},[98,4314,4315],{"class":186}," g_clusters\n",[98,4317,4318],{"class":100,"line":778},[98,4319,119],{"emptyLinePlaceholder":118},[98,4321,4322],{"class":100,"line":783},[98,4323,4324],{"class":530},"    # Collect all projects in group and subgroups and their clusters\n",[98,4326,4327,4329,4331,4333,4335,4337,4339,4341,4343,4345,4347],{"class":100,"line":795},[98,4328,2241],{"class":186},[98,4330,281],{"class":179},[98,4332,1801],{"class":186},[98,4334,920],{"class":742},[98,4336,281],{"class":179},[98,4338,925],{"class":125},[98,4340,290],{"class":186},[98,4342,930],{"class":742},[98,4344,281],{"class":179},[98,4346,925],{"class":125},[98,4348,296],{"class":186},[98,4350,4351],{"class":100,"line":810},[98,4352,119],{"emptyLinePlaceholder":118},[98,4354,4355,4357,4359,4361],{"class":100,"line":837},[98,4356,2821],{"class":179},[98,4358,1828],{"class":186},[98,4360,804],{"class":179},[98,4362,1833],{"class":186},[98,4364,4365],{"class":100,"line":842},[98,4366,3418],{"class":530},[98,4368,4369,4371,4373],{"class":100,"line":854},[98,4370,3423],{"class":186},[98,4372,281],{"class":179},[98,4374,1972],{"class":186},[98,4376,4377],{"class":100,"line":867},[98,4378,119],{"emptyLinePlaceholder":118},[98,4380,4381],{"class":100,"line":891},[98,4382,4383],{"class":530},"        # skip archived projects\n",[98,4385,4386,4388],{"class":100,"line":896},[98,4387,2832],{"class":179},[98,4389,3755],{"class":186},[98,4391,4392],{"class":100,"line":908},[98,4393,4394],{"class":179},"            continue\n",[98,4396,4397],{"class":100,"line":940},[98,4398,119],{"emptyLinePlaceholder":118},[98,4400,4401,4404,4406],{"class":100,"line":962},[98,4402,4403],{"class":186},"        p_clusters ",[98,4405,281],{"class":179},[98,4407,4408],{"class":186}," manageable_project.clusters.list()\n",[98,4410,4411],{"class":100,"line":967},[98,4412,119],{"emptyLinePlaceholder":118},[98,4414,4415,4417,4419,4422,4424,4426],{"class":100,"line":979},[98,4416,2832],{"class":179},[98,4418,2835],{"class":125},[98,4420,4421],{"class":186},"(p_clusters) ",[98,4423,190],{"class":179},[98,4425,1543],{"class":125},[98,4427,446],{"class":186},[98,4429,4430,4433,4435],{"class":100,"line":1002},[98,4431,4432],{"class":186},"            project_clusters[project.id] ",[98,4434,281],{"class":179},[98,4436,4437],{"class":186}," p_clusters\n",[98,4439,4440],{"class":100,"line":1027},[98,4441,119],{"emptyLinePlaceholder":118},[98,4443,4444],{"class":100,"line":1032},[98,4445,4446],{"class":530},"# Print summary\n",[98,4448,4449,4451,4453,4456,4458,4460],{"class":100,"line":1044},[98,4450,305],{"class":125},[98,4452,454],{"class":186},[98,4454,4455],{"class":108},"\"## Group clusters",[98,4457,2906],{"class":125},[98,4459,823],{"class":108},[98,4461,296],{"class":186},[98,4463,4464,4466,4469,4471],{"class":100,"line":1057},[98,4465,798],{"class":179},[98,4467,4468],{"class":186}," g_id, g_clusters ",[98,4470,804],{"class":179},[98,4472,4473],{"class":186}," group_clusters.items():\n",[98,4475,4476,4479,4481],{"class":100,"line":1080},[98,4477,4478],{"class":186},"    url ",[98,4480,281],{"class":179},[98,4482,4483],{"class":186}," gl.groups.get(g_id).web_url\n",[98,4485,4486,4488,4490,4493,4496,4499,4502,4504,4506,4509,4511,4514,4516,4518],{"class":100,"line":1085},[98,4487,451],{"class":125},[98,4489,454],{"class":186},[98,4491,4492],{"class":108},"\"Group ID ",[98,4494,4495],{"class":125},"{g_id}",[98,4497,4498],{"class":108},": ",[98,4500,4501],{"class":125},"{u}\\n\\n",[98,4503,823],{"class":108},[98,4505,826],{"class":186},[98,4507,4508],{"class":742},"g_id",[98,4510,281],{"class":179},[98,4512,4513],{"class":186},"g_id, ",[98,4515,1148],{"class":742},[98,4517,281],{"class":179},[98,4519,4520],{"class":186},"url))\n",[98,4522,4523],{"class":100,"line":1097},[98,4524,4525],{"class":186},"    print_clusters(g_clusters)\n",[98,4527,4528],{"class":100,"line":1119},[98,4529,119],{"emptyLinePlaceholder":118},[98,4531,4532,4534,4536,4539,4541,4543],{"class":100,"line":3453},[98,4533,305],{"class":125},[98,4535,454],{"class":186},[98,4537,4538],{"class":108},"\"## Project clusters",[98,4540,2906],{"class":125},[98,4542,823],{"class":108},[98,4544,296],{"class":186},[98,4546,4547,4549,4552,4554],{"class":100,"line":3460},[98,4548,798],{"class":179},[98,4550,4551],{"class":186}," p_id, p_clusters ",[98,4553,804],{"class":179},[98,4555,4556],{"class":186}," project_clusters.items():\n",[98,4558,4559,4561,4563],{"class":100,"line":3477},[98,4560,4478],{"class":186},[98,4562,281],{"class":179},[98,4564,4565],{"class":186}," gl.projects.get(p_id).web_url\n",[98,4567,4568,4570,4572,4575,4578,4580,4582,4584,4586,4589,4591,4594,4596,4598],{"class":100,"line":3495},[98,4569,451],{"class":125},[98,4571,454],{"class":186},[98,4573,4574],{"class":108},"\"Project ID ",[98,4576,4577],{"class":125},"{p_id}",[98,4579,4498],{"class":108},[98,4581,4501],{"class":125},[98,4583,823],{"class":108},[98,4585,826],{"class":186},[98,4587,4588],{"class":742},"p_id",[98,4590,281],{"class":179},[98,4592,4593],{"class":186},"p_id, ",[98,4595,1148],{"class":742},[98,4597,281],{"class":179},[98,4599,4520],{"class":186},[98,4601,4602],{"class":100,"line":3500},[98,4603,4604],{"class":186},"    print_clusters(p_clusters)\n",[12,4606,4607,206],{},[19,4608,2211],{"href":4609,"rel":4610},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/list_cert_based_kubernetes_clusters.py",[],[578,4612,4614],{"id":4613},"productivité-des-équipes-vérifier-si-les-merge-requests-existantes-nécessitent-un-rebase-après-avoir-fusionné-une-merge-request-de-refactorisation-majeure","Productivité des équipes : vérifier si les merge requests existantes nécessitent un rebase après avoir fusionné une merge request de refactorisation majeure",[12,4616,4617,4618,4624,4625,4630],{},"Le dépôt du ",[19,4619,4623],{"href":4620,"rel":4621,"title":4622},"https://handbook.gitlab.com/handbook/",[],"Manuel de GitLab","manuel 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 ",[19,4626,4629],{"href":4627,"rel":4628},"https://handbook.gitlab.com/handbook/about/#count-handbook-pages",[],"toutes les pages du manuel",". 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.",[12,4632,3042,4633,4638],{},[19,4634,4637],{"href":4635,"rel":4636},"https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#tasks",[],"tâches liées aux tickets"," 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.",[12,4640,4641,4642,4645,4646,4649,4650,4653,4654,4659,4660,206],{},"Le script résultant configure une liste des sources touchées par toutes les merge requests, vérifie si la merge request diffère avec ",[73,4643,4644],{},"mr.diffs.list()",", et si un modèle correspond à la valeur dans ",[73,4647,4648],{},"old_path",". Si une correspondance est trouvée, le script l'enregistre et sauvegarde la merge request dans le dictionnaire ",[73,4651,4652],{},"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 ",[19,4655,4658],{"href":4656,"rel":4657},"https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#additional-tasks",[],"descriptions des tickets",". ",[19,4661,2211],{"href":4662,"rel":4663},"https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/search_mr_contains_updated_path.py",[],[89,4665,4667],{"className":257,"code":4666,"language":259,"meta":94,"style":94},"PATH_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/api/merge_requests/#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",[73,4668,4669,4679,4686,4691,4695,4700,4705,4731,4735,4744,4748,4760,4765,4775,4790,4804,4808,4828,4838,4842,4860,4873,4890,4929,4939,4949,4959,4975,4985,4989,5004,5014,5018,5037,5041,5056,5086,5097,5110,5124],{"__ignoreMap":94},[98,4670,4671,4674,4676],{"class":100,"line":101},[98,4672,4673],{"class":125},"PATH_PATTERNS",[98,4675,404],{"class":179},[98,4677,4678],{"class":186}," [\n",[98,4680,4681,4684],{"class":100,"line":115},[98,4682,4683],{"class":108},"    'path/to/handbook/source/page.md'",[98,4685,1342],{"class":186},[98,4687,4688],{"class":100,"line":122},[98,4689,4690],{"class":186},"]\n",[98,4692,4693],{"class":100,"line":132},[98,4694,119],{"emptyLinePlaceholder":118},[98,4696,4697],{"class":100,"line":137},[98,4698,4699],{"class":530},"# Only list opened MRs\n",[98,4701,4702],{"class":100,"line":149},[98,4703,4704],{"class":530},"# https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_requests.html#project-merge-requests\n",[98,4706,4707,4710,4712,4715,4717,4719,4721,4723,4725,4727,4729],{"class":100,"line":154},[98,4708,4709],{"class":186},"mrs ",[98,4711,281],{"class":179},[98,4713,4714],{"class":186}," project.mergerequests.list(",[98,4716,992],{"class":742},[98,4718,281],{"class":179},[98,4720,997],{"class":108},[98,4722,290],{"class":186},[98,4724,1396],{"class":742},[98,4726,281],{"class":179},[98,4728,925],{"class":125},[98,4730,296],{"class":186},[98,4732,4733],{"class":100,"line":168},[98,4734,119],{"emptyLinePlaceholder":118},[98,4736,4737,4740,4742],{"class":100,"line":173},[98,4738,4739],{"class":186},"seen_mr ",[98,4741,281],{"class":179},[98,4743,4248],{"class":186},[98,4745,4746],{"class":100,"line":462},[98,4747,119],{"emptyLinePlaceholder":118},[98,4749,4750,4752,4755,4757],{"class":100,"line":687},[98,4751,798],{"class":179},[98,4753,4754],{"class":186}," mr ",[98,4756,804],{"class":179},[98,4758,4759],{"class":186}," mrs:\n",[98,4761,4762],{"class":100,"line":692},[98,4763,4764],{"class":530},"    # https://docs.gitlab.com/api/merge_requests/#list-merge-request-diffs\n",[98,4766,4767,4770,4772],{"class":100,"line":703},[98,4768,4769],{"class":186},"    real_mr ",[98,4771,281],{"class":179},[98,4773,4774],{"class":186}," project.mergerequests.get(mr.get_id())\n",[98,4776,4777,4780,4782,4785,4788],{"class":100,"line":714},[98,4778,4779],{"class":186},"    real_mr_id ",[98,4781,281],{"class":179},[98,4783,4784],{"class":186}," real_mr.attributes[",[98,4786,4787],{"class":108},"'iid'",[98,4789,4690],{"class":186},[98,4791,4792,4795,4797,4799,4802],{"class":100,"line":723},[98,4793,4794],{"class":186},"    real_mr_url ",[98,4796,281],{"class":179},[98,4798,4784],{"class":186},[98,4800,4801],{"class":108},"'web_url'",[98,4803,4690],{"class":186},[98,4805,4806],{"class":100,"line":728},[98,4807,119],{"emptyLinePlaceholder":118},[98,4809,4810,4812,4815,4817,4820,4822,4824,4826],{"class":100,"line":752},[98,4811,2821],{"class":179},[98,4813,4814],{"class":186}," diff ",[98,4816,804],{"class":179},[98,4818,4819],{"class":186}," real_mr.diffs.list(",[98,4821,1396],{"class":742},[98,4823,281],{"class":179},[98,4825,925],{"class":125},[98,4827,937],{"class":186},[98,4829,4830,4833,4835],{"class":100,"line":757},[98,4831,4832],{"class":186},"        real_diff ",[98,4834,281],{"class":179},[98,4836,4837],{"class":186}," real_mr.diffs.get(diff.id)\n",[98,4839,4840],{"class":100,"line":763},[98,4841,119],{"emptyLinePlaceholder":118},[98,4843,4844,4846,4849,4851,4854,4857],{"class":100,"line":778},[98,4845,2316],{"class":179},[98,4847,4848],{"class":186}," d ",[98,4850,804],{"class":179},[98,4852,4853],{"class":186}," real_diff.attributes[",[98,4855,4856],{"class":108},"'diffs'",[98,4858,4859],{"class":186},"]:\n",[98,4861,4862,4864,4866,4868,4871],{"class":100,"line":783},[98,4863,2927],{"class":179},[98,4865,859],{"class":186},[98,4867,804],{"class":179},[98,4869,4870],{"class":125}," PATH_PATTERNS",[98,4872,446],{"class":186},[98,4874,4875,4878,4880,4882,4885,4888],{"class":100,"line":795},[98,4876,4877],{"class":179},"                if",[98,4879,859],{"class":186},[98,4881,804],{"class":179},[98,4883,4884],{"class":186}," d[",[98,4886,4887],{"class":108},"'old_path'",[98,4889,4859],{"class":186},[98,4891,4892,4895,4897,4900,4902,4905,4908,4911,4914,4917,4919,4922,4925,4927],{"class":100,"line":810},[98,4893,4894],{"class":125},"                    print",[98,4896,454],{"class":186},[98,4898,4899],{"class":108},"\"MATCH: ",[98,4901,877],{"class":125},[98,4903,4904],{"class":108}," in MR ",[98,4906,4907],{"class":125},"{mr_id}",[98,4909,4910],{"class":108},", status '",[98,4912,4913],{"class":125},"{s}",[98,4915,4916],{"class":108},"', title '",[98,4918,1012],{"class":125},[98,4920,4921],{"class":108},"' - URL: ",[98,4923,4924],{"class":125},"{mr_url}",[98,4926,823],{"class":108},[98,4928,1580],{"class":186},[98,4930,4931,4934,4936],{"class":100,"line":837},[98,4932,4933],{"class":742},"                        p",[98,4935,281],{"class":179},[98,4937,4938],{"class":186},"p,\n",[98,4940,4941,4944,4946],{"class":100,"line":842},[98,4942,4943],{"class":742},"                        mr_id",[98,4945,281],{"class":179},[98,4947,4948],{"class":186},"real_mr_id,\n",[98,4950,4951,4954,4956],{"class":100,"line":854},[98,4952,4953],{"class":742},"                        s",[98,4955,281],{"class":179},[98,4957,4958],{"class":186},"mr_status,\n",[98,4960,4961,4964,4966,4969,4972],{"class":100,"line":867},[98,4962,4963],{"class":742},"                        t",[98,4965,281],{"class":179},[98,4967,4968],{"class":186},"real_mr.attributes[",[98,4970,4971],{"class":108},"'title'",[98,4973,4974],{"class":186},"],\n",[98,4976,4977,4980,4982],{"class":100,"line":891},[98,4978,4979],{"class":742},"                        mr_url",[98,4981,281],{"class":179},[98,4983,4984],{"class":186},"real_mr_url))\n",[98,4986,4987],{"class":100,"line":896},[98,4988,119],{"emptyLinePlaceholder":118},[98,4990,4991,4994,4996,4999,5001],{"class":100,"line":908},[98,4992,4993],{"class":179},"                    if",[98,4995,440],{"class":179},[98,4997,4998],{"class":186}," real_mr_id ",[98,5000,804],{"class":179},[98,5002,5003],{"class":186}," seen_mr:\n",[98,5005,5006,5009,5011],{"class":100,"line":940},[98,5007,5008],{"class":186},"                        seen_mr[real_mr_id] ",[98,5010,281],{"class":179},[98,5012,5013],{"class":186}," real_mr\n",[98,5015,5016],{"class":100,"line":962},[98,5017,119],{"emptyLinePlaceholder":118},[98,5019,5020,5022,5024,5026,5028,5031,5033,5035],{"class":100,"line":967},[98,5021,305],{"class":125},[98,5023,454],{"class":186},[98,5025,823],{"class":108},[98,5027,1937],{"class":125},[98,5029,5030],{"class":108},"# MRs to update",[98,5032,1937],{"class":125},[98,5034,823],{"class":108},[98,5036,296],{"class":186},[98,5038,5039],{"class":100,"line":979},[98,5040,119],{"emptyLinePlaceholder":118},[98,5042,5043,5045,5048,5051,5053],{"class":100,"line":1002},[98,5044,798],{"class":179},[98,5046,5047],{"class":125}," id",[98,5049,5050],{"class":186},", real_mr ",[98,5052,804],{"class":179},[98,5054,5055],{"class":186}," seen_mr.items():\n",[98,5057,5058,5060,5062,5065,5067,5070,5072,5075,5077,5080,5082,5084],{"class":100,"line":1027},[98,5059,451],{"class":125},[98,5061,454],{"class":186},[98,5063,5064],{"class":108},"\"- [ ] !",[98,5066,4907],{"class":125},[98,5068,5069],{"class":108}," - ",[98,5071,4924],{"class":125},[98,5073,5074],{"class":108},"+ Status: ",[98,5076,4913],{"class":125},[98,5078,5079],{"class":108},", Title: ",[98,5081,1012],{"class":125},[98,5083,823],{"class":108},[98,5085,1580],{"class":186},[98,5087,5088,5091,5093,5095],{"class":100,"line":1032},[98,5089,5090],{"class":742},"        mr_id",[98,5092,281],{"class":179},[98,5094,2883],{"class":125},[98,5096,1342],{"class":186},[98,5098,5099,5102,5104,5106,5108],{"class":100,"line":1044},[98,5100,5101],{"class":742},"        mr_url",[98,5103,281],{"class":179},[98,5105,4968],{"class":186},[98,5107,4801],{"class":108},[98,5109,4974],{"class":186},[98,5111,5112,5115,5117,5119,5122],{"class":100,"line":1057},[98,5113,5114],{"class":742},"        s",[98,5116,281],{"class":179},[98,5118,4968],{"class":186},[98,5120,5121],{"class":108},"'detailed_merge_status'",[98,5123,4974],{"class":186},[98,5125,5126,5129,5131,5133,5135],{"class":100,"line":1080},[98,5127,5128],{"class":742},"        t",[98,5130,281],{"class":179},[98,5132,4968],{"class":186},[98,5134,4971],{"class":108},[98,5136,3963],{"class":186},[48,5138,5140],{"id":5139},"cas-dutilisation-devsecops-pour-les-actions-décriture-de-lapi","Cas d’utilisation DevSecOps pour les actions d'écriture de l’API",[12,5142,5143,5144,206],{},"Le jeton d'accès authentifié nécessite une ",[19,5145,5148],{"href":5146,"rel":5147},"https://docs.gitlab.com/user/profile/personal_access_tokens/#personal-access-token-scopes",[],"portée d’autorisation complète de l’API",[12,5150,5151],{},"Les cas d’utilisation suivants sont abordés :",[562,5153,5154,5157,5160],{},[565,5155,5156],{},"Déplacer des epics d’un groupe à l’autre,",[565,5158,5159],{},"Conformité : vérifier que les paramètres du projet ne sont pas remplacés,",[565,5161,5162],{},"Prendre des notes, générer un aperçu de la date d'échéance,",[578,5164,5166],{"id":5165},"déplacer-des-epics-dun-groupe-à-lautre","Déplacer des epics d’un groupe à l’autre",[12,5168,5169,5170,5175],{},"Vous devez parfois déplacer des epics dans un autre groupe. Une question posée dans le Slack de GitLab nous a incité à examiner une ",[19,5171,5174],{"href":5172,"rel":5173},"https://gitlab.com/gitlab-org/gitlab/-/issues/12689",[],"proposition de fonctionnalité pour l'interface utilisateur",", 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.",[12,5177,5178,5179,5182,5183,5186,5187,5190,5191,206],{},"Le 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 ",[73,5180,5181],{},"save()",". Les tickets attribués à l'epic doivent être recréés dans l'epic cible. L'appel ",[73,5184,5185],{},"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 ",[73,5188,5189],{},"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 ",[73,5192,5193],{},"closed",[89,5195,5197],{"className":257,"code":5196,"language":259,"meta":94,"style":94},"#!/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",[73,5198,5199,5203,5207,5212,5217,5222,5227,5231,5237,5243,5249,5253,5269,5274,5293,5298,5317,5322,5340,5352,5356,5366,5376,5384,5388,5408,5412,5416,5421,5434,5447,5451,5456,5470,5475,5479,5489,5499,5509,5519,5523,5528,5538,5546,5554,5559,5563,5568,5578,5582,5587,5592,5596,5601,5613,5629,5633,5638,5643,5663,5668,5680,5685,5695,5700,5704,5738,5756,5774,5778,5783,5793,5798,5817],{"__ignoreMap":94},[98,5200,5201],{"class":100,"line":101},[98,5202,607],{"class":530},[98,5204,5205],{"class":100,"line":115},[98,5206,119],{"emptyLinePlaceholder":118},[98,5208,5209],{"class":100,"line":122},[98,5210,5211],{"class":530},"# Description: Show how epics can be moved between groups, including title, description, labels, child epics and issues.\n",[98,5213,5214],{"class":100,"line":132},[98,5215,5216],{"class":530},"# Requirements: python-gitlab Python libraries. GitLab API write access, and maintainer access to all configured groups/projects.\n",[98,5218,5219],{"class":100,"line":137},[98,5220,5221],{"class":530},"# Author: Michael Friedrich \u003Cmfriedrich@gitlab.com>\n",[98,5223,5224],{"class":100,"line":149},[98,5225,5226],{"class":530},"# License: MIT, (c) 2023-present GitLab B.V.\n",[98,5228,5229],{"class":100,"line":154},[98,5230,119],{"emptyLinePlaceholder":118},[98,5232,5233,5235],{"class":100,"line":168},[98,5234,266],{"class":179},[98,5236,379],{"class":186},[98,5238,5239,5241],{"class":100,"line":173},[98,5240,266],{"class":179},[98,5242,269],{"class":186},[98,5244,5245,5247],{"class":100,"line":462},[98,5246,266],{"class":179},[98,5248,392],{"class":186},[98,5250,5251],{"class":100,"line":687},[98,5252,119],{"emptyLinePlaceholder":118},[98,5254,5255,5257,5259,5261,5263,5265,5267],{"class":100,"line":692},[98,5256,401],{"class":125},[98,5258,404],{"class":179},[98,5260,284],{"class":186},[98,5262,287],{"class":108},[98,5264,290],{"class":186},[98,5266,293],{"class":108},[98,5268,296],{"class":186},[98,5270,5271],{"class":100,"line":703},[98,5272,5273],{"class":530},"# https://gitlab.com/gitlab-da/use-cases/gitlab-api\n",[98,5275,5276,5279,5281,5283,5286,5288,5291],{"class":100,"line":714},[98,5277,5278],{"class":125},"SOURCE_GROUP_ID",[98,5280,404],{"class":179},[98,5282,284],{"class":186},[98,5284,5285],{"class":108},"'GL_SOURCE_GROUP_ID'",[98,5287,290],{"class":186},[98,5289,5290],{"class":125},"62378643",[98,5292,296],{"class":186},[98,5294,5295],{"class":100,"line":723},[98,5296,5297],{"class":530},"# https://gitlab.com/gitlab-da/use-cases/gitlab-api/epic-move-target\n",[98,5299,5300,5303,5305,5307,5310,5312,5315],{"class":100,"line":728},[98,5301,5302],{"class":125},"TARGET_GROUP_ID",[98,5304,404],{"class":179},[98,5306,284],{"class":186},[98,5308,5309],{"class":108},"'GL_TARGET_GROUP_ID'",[98,5311,290],{"class":186},[98,5313,5314],{"class":125},"62742177",[98,5316,296],{"class":186},[98,5318,5319],{"class":100,"line":752},[98,5320,5321],{"class":530},"# https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1\n",[98,5323,5324,5327,5329,5331,5334,5336,5338],{"class":100,"line":757},[98,5325,5326],{"class":125},"EPIC_ID",[98,5328,404],{"class":179},[98,5330,284],{"class":186},[98,5332,5333],{"class":108},"'GL_EPIC_ID'",[98,5335,290],{"class":186},[98,5337,468],{"class":125},[98,5339,296],{"class":186},[98,5341,5342,5344,5346,5348,5350],{"class":100,"line":763},[98,5343,419],{"class":125},[98,5345,404],{"class":179},[98,5347,284],{"class":186},[98,5349,426],{"class":108},[98,5351,296],{"class":186},[98,5353,5354],{"class":100,"line":778},[98,5355,119],{"emptyLinePlaceholder":118},[98,5357,5358,5360,5362,5364],{"class":100,"line":783},[98,5359,437],{"class":179},[98,5361,440],{"class":179},[98,5363,443],{"class":125},[98,5365,446],{"class":186},[98,5367,5368,5370,5372,5374],{"class":100,"line":795},[98,5369,451],{"class":125},[98,5371,454],{"class":186},[98,5373,457],{"class":108},[98,5375,296],{"class":186},[98,5377,5378,5380,5382],{"class":100,"line":810},[98,5379,465],{"class":186},[98,5381,468],{"class":125},[98,5383,296],{"class":186},[98,5385,5386],{"class":100,"line":837},[98,5387,119],{"emptyLinePlaceholder":118},[98,5389,5390,5392,5394,5396,5398,5400,5402,5404,5406],{"class":100,"line":842},[98,5391,516],{"class":186},[98,5393,281],{"class":179},[98,5395,735],{"class":186},[98,5397,401],{"class":125},[98,5399,290],{"class":186},[98,5401,743],{"class":742},[98,5403,281],{"class":179},[98,5405,419],{"class":125},[98,5407,296],{"class":186},[98,5409,5410],{"class":100,"line":854},[98,5411,119],{"emptyLinePlaceholder":118},[98,5413,5414],{"class":100,"line":867},[98,5415,760],{"class":530},[98,5417,5418],{"class":100,"line":891},[98,5419,5420],{"class":530},"# Goal: Move epic to target group, including title, body, labels, and child epics and issues.\n",[98,5422,5423,5426,5428,5430,5432],{"class":100,"line":896},[98,5424,5425],{"class":186},"source_group ",[98,5427,281],{"class":179},[98,5429,771],{"class":186},[98,5431,5278],{"class":125},[98,5433,296],{"class":186},[98,5435,5436,5439,5441,5443,5445],{"class":100,"line":908},[98,5437,5438],{"class":186},"target_group ",[98,5440,281],{"class":179},[98,5442,771],{"class":186},[98,5444,5302],{"class":125},[98,5446,296],{"class":186},[98,5448,5449],{"class":100,"line":940},[98,5450,119],{"emptyLinePlaceholder":118},[98,5452,5453],{"class":100,"line":962},[98,5454,5455],{"class":530},"# Create a new target epic and copy all its items, then close the source epic.\n",[98,5457,5458,5461,5463,5466,5468],{"class":100,"line":967},[98,5459,5460],{"class":186},"source_epic ",[98,5462,281],{"class":179},[98,5464,5465],{"class":186}," source_group.epics.get(",[98,5467,5326],{"class":125},[98,5469,296],{"class":186},[98,5471,5472],{"class":100,"line":979},[98,5473,5474],{"class":530},"# print(source_epic) #debug\n",[98,5476,5477],{"class":100,"line":1002},[98,5478,119],{"emptyLinePlaceholder":118},[98,5480,5481,5484,5486],{"class":100,"line":1027},[98,5482,5483],{"class":186},"epic_title ",[98,5485,281],{"class":179},[98,5487,5488],{"class":186}," source_epic.title\n",[98,5490,5491,5494,5496],{"class":100,"line":1032},[98,5492,5493],{"class":186},"epic_description ",[98,5495,281],{"class":179},[98,5497,5498],{"class":186}," source_epic.description\n",[98,5500,5501,5504,5506],{"class":100,"line":1044},[98,5502,5503],{"class":186},"epic_labels ",[98,5505,281],{"class":179},[98,5507,5508],{"class":186}," source_epic.labels\n",[98,5510,5511,5514,5516],{"class":100,"line":1057},[98,5512,5513],{"class":186},"epic_issues ",[98,5515,281],{"class":179},[98,5517,5518],{"class":186}," source_epic.issues.list()\n",[98,5520,5521],{"class":100,"line":1080},[98,5522,119],{"emptyLinePlaceholder":118},[98,5524,5525],{"class":100,"line":1085},[98,5526,5527],{"class":530},"# Create the epic with minimal attributes\n",[98,5529,5530,5533,5535],{"class":100,"line":1097},[98,5531,5532],{"class":186},"target_epic ",[98,5534,281],{"class":179},[98,5536,5537],{"class":186}," target_group.epics.create({\n",[98,5539,5540,5543],{"class":100,"line":1119},[98,5541,5542],{"class":108},"    'title'",[98,5544,5545],{"class":186},": epic_title,\n",[98,5547,5548,5551],{"class":100,"line":3453},[98,5549,5550],{"class":108},"    'description'",[98,5552,5553],{"class":186},": epic_description,\n",[98,5555,5556],{"class":100,"line":3460},[98,5557,5558],{"class":186},"})\n",[98,5560,5561],{"class":100,"line":3477},[98,5562,119],{"emptyLinePlaceholder":118},[98,5564,5565],{"class":100,"line":3495},[98,5566,5567],{"class":530},"# Assign the list\n",[98,5569,5570,5573,5575],{"class":100,"line":3500},[98,5571,5572],{"class":186},"target_epic.labels ",[98,5574,281],{"class":179},[98,5576,5577],{"class":186}," epic_labels\n",[98,5579,5580],{"class":100,"line":3512},[98,5581,119],{"emptyLinePlaceholder":118},[98,5583,5584],{"class":100,"line":3517},[98,5585,5586],{"class":530},"# Persist the changes in the new epic\n",[98,5588,5589],{"class":100,"line":3523},[98,5590,5591],{"class":186},"target_epic.save()\n",[98,5593,5594],{"class":100,"line":3528},[98,5595,119],{"emptyLinePlaceholder":118},[98,5597,5598],{"class":100,"line":3534},[98,5599,5600],{"class":530},"# Epic issues need to be re-assigned in a loop\n",[98,5602,5603,5605,5608,5610],{"class":100,"line":3546},[98,5604,798],{"class":179},[98,5606,5607],{"class":186}," epic_issue ",[98,5609,804],{"class":179},[98,5611,5612],{"class":186}," epic_issues:\n",[98,5614,5615,5618,5620,5623,5626],{"class":100,"line":3554},[98,5616,5617],{"class":186},"    ei ",[98,5619,281],{"class":179},[98,5621,5622],{"class":186}," target_epic.issues.create({",[98,5624,5625],{"class":108},"'issue_id'",[98,5627,5628],{"class":186},": epic_issue.id})\n",[98,5630,5631],{"class":100,"line":3575},[98,5632,119],{"emptyLinePlaceholder":118},[98,5634,5635],{"class":100,"line":3581},[98,5636,5637],{"class":530},"# Child epics need to update their parent_id to the new epic\n",[98,5639,5640],{"class":100,"line":3589},[98,5641,5642],{"class":530},"# Need to search in all epics, use lazy object loading\n",[98,5644,5645,5647,5650,5652,5655,5657,5659,5661],{"class":100,"line":3601},[98,5646,798],{"class":179},[98,5648,5649],{"class":186}," sge ",[98,5651,804],{"class":179},[98,5653,5654],{"class":186}," source_group.epics.list(",[98,5656,2494],{"class":742},[98,5658,281],{"class":179},[98,5660,925],{"class":125},[98,5662,937],{"class":186},[98,5664,5665],{"class":100,"line":3616},[98,5666,5667],{"class":530},"    # this epic has the source epic as parent epic?\n",[98,5669,5670,5672,5675,5677],{"class":100,"line":3621},[98,5671,1447],{"class":179},[98,5673,5674],{"class":186}," sge.parent_id ",[98,5676,3982],{"class":179},[98,5678,5679],{"class":186}," source_epic.id:\n",[98,5681,5682],{"class":100,"line":3627},[98,5683,5684],{"class":530},"        # Update the parent id\n",[98,5686,5687,5690,5692],{"class":100,"line":3638},[98,5688,5689],{"class":186},"        sge.parent_id ",[98,5691,281],{"class":179},[98,5693,5694],{"class":186}," target_epic.id\n",[98,5696,5697],{"class":100,"line":3675},[98,5698,5699],{"class":186},"        sge.save()\n",[98,5701,5702],{"class":100,"line":3696},[98,5703,119],{"emptyLinePlaceholder":118},[98,5705,5706,5708,5710,5713,5716,5719,5722,5725,5728,5730,5733,5736],{"class":100,"line":3702},[98,5707,305],{"class":125},[98,5709,454],{"class":186},[98,5711,5712],{"class":108},"\"Copied source epic ",[98,5714,5715],{"class":125},"{source_id}",[98,5717,5718],{"class":108}," (",[98,5720,5721],{"class":125},"{source_url}",[98,5723,5724],{"class":108},") to target epic ",[98,5726,5727],{"class":125},"{target_id}",[98,5729,5718],{"class":108},[98,5731,5732],{"class":125},"{target_url}",[98,5734,5735],{"class":108},")\"",[98,5737,1580],{"class":186},[98,5739,5740,5743,5745,5748,5751,5753],{"class":100,"line":3707},[98,5741,5742],{"class":742},"    source_id",[98,5744,281],{"class":179},[98,5746,5747],{"class":186},"source_epic.id, ",[98,5749,5750],{"class":742},"source_url",[98,5752,281],{"class":179},[98,5754,5755],{"class":186},"source_epic.web_url,\n",[98,5757,5758,5761,5763,5766,5769,5771],{"class":100,"line":3722},[98,5759,5760],{"class":742},"    target_id",[98,5762,281],{"class":179},[98,5764,5765],{"class":186},"target_epic.id, ",[98,5767,5768],{"class":742},"target_url",[98,5770,281],{"class":179},[98,5772,5773],{"class":186},"target_epic.web_url))\n",[98,5775,5776],{"class":100,"line":3727},[98,5777,119],{"emptyLinePlaceholder":118},[98,5779,5780],{"class":100,"line":3733},[98,5781,5782],{"class":530},"# Close the old epic\n",[98,5784,5785,5788,5790],{"class":100,"line":3744},[98,5786,5787],{"class":186},"source_epic.state_event ",[98,5789,281],{"class":179},[98,5791,5792],{"class":108}," 'close'\n",[98,5794,5795],{"class":100,"line":3750},[98,5796,5797],{"class":186},"source_epic.save()\n",[98,5799,5800,5802,5804,5807,5809,5811,5813,5815],{"class":100,"line":3758},[98,5801,305],{"class":125},[98,5803,454],{"class":186},[98,5805,5806],{"class":108},"\"Closed source epic ",[98,5808,5715],{"class":125},[98,5810,5718],{"class":108},[98,5812,5721],{"class":125},[98,5814,5735],{"class":108},[98,5816,1580],{"class":186},[98,5818,5819,5821,5823,5825,5827,5829],{"class":100,"line":3764},[98,5820,5742],{"class":742},[98,5822,281],{"class":179},[98,5824,5747],{"class":186},[98,5826,5750],{"class":742},[98,5828,281],{"class":179},[98,5830,5831],{"class":186},"source_epic.web_url))\n",[89,5833,5835],{"className":91,"code":5834,"language":93,"meta":94,"style":94},"$  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",[73,5836,5837,5847,5869],{"__ignoreMap":94},[98,5838,5839,5841,5844],{"class":100,"line":101},[98,5840,321],{"class":104},[98,5842,5843],{"class":108},"  python3",[98,5845,5846],{"class":108}," move_epic_between_groups.py\n",[98,5848,5849,5852,5855,5858,5861,5864,5867],{"class":100,"line":115},[98,5850,5851],{"class":104},"Copied",[98,5853,5854],{"class":108}," source",[98,5856,5857],{"class":108}," epic",[98,5859,5860],{"class":125}," 725341",[98,5862,5863],{"class":186}," (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1) to target epic 725358 (",[98,5865,5866],{"class":104},"https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/6",[98,5868,296],{"class":186},[98,5870,5871,5874,5876,5878,5880],{"class":100,"line":122},[98,5872,5873],{"class":104},"Closed",[98,5875,5854],{"class":108},[98,5877,5857],{"class":108},[98,5879,5860],{"class":125},[98,5881,5882],{"class":186}," (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1)\n",[12,5884,5885,5886,5892],{},"L'",[19,5887,5891],{"href":5888,"rel":5889,"title":5890},"https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/5",[],"Epic cible","epic cible"," a été créée et affiche le résultat attendu : même titre, description, labels, epic enfant et tickets.",[12,5894,5895,5900],{},[5896,5897],"img",{"alt":5898,"src":5899},"Tutoriel sur l'API GitLab : déplacer des epics","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398206/blog/Content%20Images/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_moved_epic_with_all_attributes.png","{: .shadow}",[12,5902,5903,5907,5908,5913,5914,5919],{},[5904,5905,5906],"strong",{},"Exercice :"," le script ne copie pas encore les ",[19,5909,5912],{"href":5910,"rel":5911},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/notes.html",[],"commentaires"," et les ",[19,5915,5918],{"href":5916,"rel":5917},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/discussions.html",[],"fils de discussion",". Faites des recherches et aidez-nous à mettre à jour le script. Les merge requests sont les bienvenues !",[578,5921,5923],{"id":5922},"conformité-vérifier-que-les-paramètres-du-projet-ne-sont-pas-remplacés","Conformité : vérifier que les paramètres du projet ne sont pas remplacés",[12,5925,5926,5927,5932],{},"Les 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’",[19,5928,5931],{"href":5929,"rel":5930,"title":5931},"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.",[12,5934,5935,5936,5939],{},"Dans l'exemple suivant, seule la branche ",[73,5937,5938],{},"main"," est protégée.",[12,5941,5942,5900],{},[5896,5943],{"alt":5944,"src":5945},"API python-gitlab : protection des branches","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398206/blog/Content%20Images/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main.png",[12,5947,5948,5949,5952,5953,206],{},"Supposons qu'une nouvelle branche ",[73,5950,5951],{},"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 ",[19,5954,5957],{"href":5955,"rel":5956},"https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html",[],"documentation python-gitlab sur les branches protégées",[89,5959,5961],{"className":257,"code":5960,"language":259,"meta":94,"style":94},"#!/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",[73,5962,5963,5967,5971,5977,5983,5989,5993,6009,6013,6029,6041,6045,6055,6063,6076,6086,6091,6098,6108,6116,6120,6125,6129,6139,6149,6157,6161,6181,6185,6189,6201,6205,6209,6233,6237,6247,6251,6255,6263,6267,6272,6281,6285,6297,6307,6334,6344,6354,6364,6369,6373,6378,6382,6397,6402,6418,6441,6451,6459,6470,6479],{"__ignoreMap":94},[98,5964,5965],{"class":100,"line":101},[98,5966,607],{"class":530},[98,5968,5969],{"class":100,"line":115},[98,5970,119],{"emptyLinePlaceholder":118},[98,5972,5973,5975],{"class":100,"line":122},[98,5974,266],{"class":179},[98,5976,379],{"class":186},[98,5978,5979,5981],{"class":100,"line":132},[98,5980,266],{"class":179},[98,5982,269],{"class":186},[98,5984,5985,5987],{"class":100,"line":137},[98,5986,266],{"class":179},[98,5988,392],{"class":186},[98,5990,5991],{"class":100,"line":149},[98,5992,119],{"emptyLinePlaceholder":118},[98,5994,5995,5997,5999,6001,6003,6005,6007],{"class":100,"line":154},[98,5996,401],{"class":125},[98,5998,404],{"class":179},[98,6000,284],{"class":186},[98,6002,287],{"class":108},[98,6004,290],{"class":186},[98,6006,293],{"class":108},[98,6008,296],{"class":186},[98,6010,6011],{"class":100,"line":168},[98,6012,654],{"class":530},[98,6014,6015,6017,6019,6021,6023,6025,6027],{"class":100,"line":173},[98,6016,596],{"class":125},[98,6018,404],{"class":179},[98,6020,284],{"class":186},[98,6022,665],{"class":108},[98,6024,290],{"class":186},[98,6026,670],{"class":125},[98,6028,296],{"class":186},[98,6030,6031,6033,6035,6037,6039],{"class":100,"line":462},[98,6032,419],{"class":125},[98,6034,404],{"class":179},[98,6036,284],{"class":186},[98,6038,426],{"class":108},[98,6040,296],{"class":186},[98,6042,6043],{"class":100,"line":687},[98,6044,119],{"emptyLinePlaceholder":118},[98,6046,6047,6050,6052],{"class":100,"line":692},[98,6048,6049],{"class":125},"PROTECTED_BRANCHES",[98,6051,404],{"class":179},[98,6053,6054],{"class":186}," {\n",[98,6056,6057,6060],{"class":100,"line":703},[98,6058,6059],{"class":108},"    'main'",[98,6061,6062],{"class":186},": {\n",[98,6064,6065,6068,6071,6074],{"class":100,"line":714},[98,6066,6067],{"class":108},"        'merge_access_level'",[98,6069,6070],{"class":186},": gitlab.const.AccessLevel.",[98,6072,6073],{"class":125},"MAINTAINER",[98,6075,1342],{"class":186},[98,6077,6078,6081,6083],{"class":100,"line":723},[98,6079,6080],{"class":108},"        'push_access_level'",[98,6082,6070],{"class":186},[98,6084,6085],{"class":125},"MAINTAINER\n",[98,6087,6088],{"class":100,"line":728},[98,6089,6090],{"class":186},"    },\n",[98,6092,6093,6096],{"class":100,"line":752},[98,6094,6095],{"class":108},"    'production'",[98,6097,6062],{"class":186},[98,6099,6100,6102,6104,6106],{"class":100,"line":757},[98,6101,6067],{"class":108},[98,6103,6070],{"class":186},[98,6105,6073],{"class":125},[98,6107,1342],{"class":186},[98,6109,6110,6112,6114],{"class":100,"line":763},[98,6111,6080],{"class":108},[98,6113,6070],{"class":186},[98,6115,6085],{"class":125},[98,6117,6118],{"class":100,"line":778},[98,6119,6090],{"class":186},[98,6121,6122],{"class":100,"line":783},[98,6123,6124],{"class":186},"}\n",[98,6126,6127],{"class":100,"line":795},[98,6128,119],{"emptyLinePlaceholder":118},[98,6130,6131,6133,6135,6137],{"class":100,"line":810},[98,6132,437],{"class":179},[98,6134,440],{"class":179},[98,6136,443],{"class":125},[98,6138,446],{"class":186},[98,6140,6141,6143,6145,6147],{"class":100,"line":837},[98,6142,451],{"class":125},[98,6144,454],{"class":186},[98,6146,457],{"class":108},[98,6148,296],{"class":186},[98,6150,6151,6153,6155],{"class":100,"line":842},[98,6152,465],{"class":186},[98,6154,468],{"class":125},[98,6156,296],{"class":186},[98,6158,6159],{"class":100,"line":854},[98,6160,119],{"emptyLinePlaceholder":118},[98,6162,6163,6165,6167,6169,6171,6173,6175,6177,6179],{"class":100,"line":867},[98,6164,516],{"class":186},[98,6166,281],{"class":179},[98,6168,735],{"class":186},[98,6170,401],{"class":125},[98,6172,290],{"class":186},[98,6174,743],{"class":742},[98,6176,281],{"class":179},[98,6178,419],{"class":125},[98,6180,296],{"class":186},[98,6182,6183],{"class":100,"line":891},[98,6184,119],{"emptyLinePlaceholder":118},[98,6186,6187],{"class":100,"line":896},[98,6188,760],{"class":530},[98,6190,6191,6193,6195,6197,6199],{"class":100,"line":908},[98,6192,1774],{"class":186},[98,6194,281],{"class":179},[98,6196,771],{"class":186},[98,6198,596],{"class":125},[98,6200,296],{"class":186},[98,6202,6203],{"class":100,"line":940},[98,6204,119],{"emptyLinePlaceholder":118},[98,6206,6207],{"class":100,"line":962},[98,6208,1791],{"class":530},[98,6210,6211,6213,6215,6217,6219,6221,6223,6225,6227,6229,6231],{"class":100,"line":967},[98,6212,1796],{"class":186},[98,6214,281],{"class":179},[98,6216,1801],{"class":186},[98,6218,920],{"class":742},[98,6220,281],{"class":179},[98,6222,925],{"class":125},[98,6224,290],{"class":186},[98,6226,930],{"class":742},[98,6228,281],{"class":179},[98,6230,925],{"class":125},[98,6232,296],{"class":186},[98,6234,6235],{"class":100,"line":979},[98,6236,119],{"emptyLinePlaceholder":118},[98,6238,6239,6241,6243,6245],{"class":100,"line":1002},[98,6240,798],{"class":179},[98,6242,1828],{"class":186},[98,6244,804],{"class":179},[98,6246,1833],{"class":186},[98,6248,6249],{"class":100,"line":1027},[98,6250,1957],{"class":530},[98,6252,6253],{"class":100,"line":1032},[98,6254,1962],{"class":530},[98,6256,6257,6259,6261],{"class":100,"line":1044},[98,6258,1967],{"class":186},[98,6260,281],{"class":179},[98,6262,1972],{"class":186},[98,6264,6265],{"class":100,"line":1057},[98,6266,119],{"emptyLinePlaceholder":118},[98,6268,6269],{"class":100,"line":1080},[98,6270,6271],{"class":530},"    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html\n",[98,6273,6274,6277,6279],{"class":100,"line":1085},[98,6275,6276],{"class":186},"    protected_branch_names ",[98,6278,281],{"class":179},[98,6280,2246],{"class":186},[98,6282,6283],{"class":100,"line":1097},[98,6284,119],{"emptyLinePlaceholder":118},[98,6286,6287,6289,6292,6294],{"class":100,"line":1119},[98,6288,2821],{"class":179},[98,6290,6291],{"class":186}," pb ",[98,6293,804],{"class":179},[98,6295,6296],{"class":186}," manageable_project.protectedbranches.list():\n",[98,6298,6299,6302,6304],{"class":100,"line":3453},[98,6300,6301],{"class":186},"        manageable_protected_branch ",[98,6303,281],{"class":179},[98,6305,6306],{"class":186}," manageable_project.protectedbranches.get(pb.name)\n",[98,6308,6309,6311,6313,6316,6318,6321,6324,6327,6330,6332],{"class":100,"line":3460},[98,6310,1920],{"class":125},[98,6312,454],{"class":186},[98,6314,6315],{"class":108},"\"Protected branch name: ",[98,6317,3648],{"class":125},[98,6319,6320],{"class":108},", merge_access_level: ",[98,6322,6323],{"class":125},"{mal}",[98,6325,6326],{"class":108},", push_access_level: ",[98,6328,6329],{"class":125},"{pal}",[98,6331,823],{"class":108},[98,6333,1580],{"class":186},[98,6335,6336,6339,6341],{"class":100,"line":3477},[98,6337,6338],{"class":742},"            n",[98,6340,281],{"class":179},[98,6342,6343],{"class":186},"manageable_protected_branch.name,\n",[98,6345,6346,6349,6351],{"class":100,"line":3495},[98,6347,6348],{"class":742},"            mal",[98,6350,281],{"class":179},[98,6352,6353],{"class":186},"manageable_protected_branch.merge_access_levels,\n",[98,6355,6356,6359,6361],{"class":100,"line":3500},[98,6357,6358],{"class":742},"            pal",[98,6360,281],{"class":179},[98,6362,6363],{"class":186},"manageable_protected_branch.push_access_levels\n",[98,6365,6366],{"class":100,"line":3512},[98,6367,6368],{"class":186},"        ))\n",[98,6370,6371],{"class":100,"line":3517},[98,6372,119],{"emptyLinePlaceholder":118},[98,6374,6375],{"class":100,"line":3523},[98,6376,6377],{"class":186},"        protected_branch_names.append(manageable_protected_branch.name)\n",[98,6379,6380],{"class":100,"line":3528},[98,6381,119],{"emptyLinePlaceholder":118},[98,6383,6384,6386,6389,6391,6394],{"class":100,"line":3534},[98,6385,2821],{"class":179},[98,6387,6388],{"class":186}," branch_to_protect, levels ",[98,6390,804],{"class":179},[98,6392,6393],{"class":125}," PROTECTED_BRANCHES",[98,6395,6396],{"class":186},".items():\n",[98,6398,6399],{"class":100,"line":3546},[98,6400,6401],{"class":530},"        # Fix missing protected branches\n",[98,6403,6404,6406,6409,6412,6415],{"class":100,"line":3554},[98,6405,2832],{"class":179},[98,6407,6408],{"class":186}," branch_to_protect ",[98,6410,6411],{"class":179},"not",[98,6413,6414],{"class":179}," in",[98,6416,6417],{"class":186}," protected_branch_names:\n",[98,6419,6420,6422,6424,6427,6429,6432,6434,6436,6438],{"class":100,"line":3575},[98,6421,2854],{"class":125},[98,6423,454],{"class":186},[98,6425,6426],{"class":108},"\"Adding branch ",[98,6428,3648],{"class":125},[98,6430,6431],{"class":108}," to protected branches settings\"",[98,6433,826],{"class":186},[98,6435,3660],{"class":742},[98,6437,281],{"class":179},[98,6439,6440],{"class":186},"branch_to_protect))\n",[98,6442,6443,6446,6448],{"class":100,"line":3581},[98,6444,6445],{"class":186},"            p_branch ",[98,6447,281],{"class":179},[98,6449,6450],{"class":186}," manageable_project.protectedbranches.create({\n",[98,6452,6453,6456],{"class":100,"line":3589},[98,6454,6455],{"class":108},"                'name'",[98,6457,6458],{"class":186},": branch_to_protect,\n",[98,6460,6461,6464,6466,6468],{"class":100,"line":3601},[98,6462,6463],{"class":108},"                'merge_access_level'",[98,6465,6070],{"class":186},[98,6467,6073],{"class":125},[98,6469,1342],{"class":186},[98,6471,6472,6475,6477],{"class":100,"line":3616},[98,6473,6474],{"class":108},"                'push_access_level'",[98,6476,6070],{"class":186},[98,6478,6085],{"class":125},[98,6480,6481],{"class":100,"line":3621},[98,6482,6483],{"class":186},"            })\n",[12,6485,6486,6487,6489,6490,6492],{},"L'exécution du script affiche la branche ",[73,6488,5938],{}," existante, ainsi qu’une note indiquant que la branche ",[73,6491,5951],{}," sera mise à jour. La capture d'écran des paramètres du dépôt démontre cette action.",[89,6494,6496],{"className":91,"code":6495,"language":93,"meta":94,"style":94},"$ 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",[73,6497,6498,6510,6591],{"__ignoreMap":94},[98,6499,6500,6502,6504,6507],{"class":100,"line":101},[98,6501,321],{"class":104},[98,6503,327],{"class":108},[98,6505,6506],{"class":108}," enforce_protected_branches.py",[98,6508,6509],{"class":108}," ─╯\n",[98,6511,6512,6515,6518,6521,6524,6527,6530,6532,6535,6538,6541,6544,6547,6550,6553,6556,6559,6562,6565,6567,6569,6571,6574,6576,6578,6580,6582,6584,6586,6588],{"class":100,"line":115},[98,6513,6514],{"class":104},"Protected",[98,6516,6517],{"class":108}," branch",[98,6519,6520],{"class":108}," name:",[98,6522,6523],{"class":108}," main,",[98,6525,6526],{"class":108}," merge_access_level:",[98,6528,6529],{"class":186}," [{",[98,6531,3960],{"class":104},[98,6533,6534],{"class":125},":",[98,6536,6537],{"class":108}," 67294702,",[98,6539,6540],{"class":108}," 'access_level':",[98,6542,6543],{"class":108}," 40,",[98,6545,6546],{"class":108}," 'access_level_description':",[98,6548,6549],{"class":108}," 'Maintainers',",[98,6551,6552],{"class":108}," 'user_id':",[98,6554,6555],{"class":108}," None,",[98,6557,6558],{"class":108}," 'group_id':",[98,6560,6561],{"class":108}," None}],",[98,6563,6564],{"class":108}," push_access_level:",[98,6566,6529],{"class":186},[98,6568,3960],{"class":104},[98,6570,6534],{"class":125},[98,6572,6573],{"class":108}," 68546039,",[98,6575,6540],{"class":108},[98,6577,6543],{"class":108},[98,6579,6546],{"class":108},[98,6581,6549],{"class":108},[98,6583,6552],{"class":108},[98,6585,6555],{"class":108},[98,6587,6558],{"class":108},[98,6589,6590],{"class":108}," None}]\n",[98,6592,6593,6596,6598,6601,6604,6607,6610],{"class":100,"line":122},[98,6594,6595],{"class":104},"Adding",[98,6597,6517],{"class":108},[98,6599,6600],{"class":108}," production",[98,6602,6603],{"class":108}," to",[98,6605,6606],{"class":108}," protected",[98,6608,6609],{"class":108}," branches",[98,6611,6612],{"class":108}," settings\n",[12,6614,6615,5900],{},[5896,6616],{"alt":6617,"src":6618},"Capture d'écran de code en Python avec GitLab","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398207/blog/Content%20Images/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main_production.png",[578,6620,6622],{"id":6621},"prise-de-notes-générer-un-aperçu-de-la-date-déchéance","Prise de notes : générer un aperçu de la date d'échéance",[12,6624,6625,6626,6631],{},"Une ",[19,6627,6630],{"href":6628,"rel":6629},"https://news.ycombinator.com/item?id=32155848",[],"discussion de Hacker News sur les outils de prise de notes"," 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.",[6633,6634,6635],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":94,"searchDepth":115,"depth":115,"links":6637},[6638,6639,6640,6646,6654],{"id":50,"depth":115,"text":51},{"id":209,"depth":115,"text":210},{"id":476,"depth":115,"text":477,"children":6641},[6642,6643,6644,6645],{"id":580,"depth":122,"text":581},{"id":1256,"depth":122,"text":1257},{"id":1745,"depth":122,"text":1746},{"id":2214,"depth":122,"text":2215},{"id":2404,"depth":115,"text":2405,"children":6647},[6648,6649,6650,6651,6652,6653],{"id":2437,"depth":122,"text":2438},{"id":2790,"depth":122,"text":2791},{"id":3038,"depth":122,"text":3039},{"id":3857,"depth":122,"text":3858},{"id":4129,"depth":122,"text":4130},{"id":4613,"depth":122,"text":4614},{"id":5139,"depth":115,"text":5140,"children":6655},[6656,6657,6658],{"id":5165,"depth":122,"text":5166},{"id":5922,"depth":122,"text":5923},{"id":6621,"depth":122,"text":6622},"engineering","2023-02-01","Vous souhaitez améliorer vos workflows DevSecOps ? Découvrez dans ce tutoriel des exemples et bonnes pratiques d’utilisation de l’API python-gitlab.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659883/Blog/Hero%20Images/post-cover-image.jpg",{},"/fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation",{"title":5,"description":6661,"ogTitle":5,"ogDescription":6661,"noIndex":6664,"ogImage":6665,"ogUrl":6669,"ogSiteName":6670,"ogType":6671,"canonicalUrls":6669},"https://about.gitlab.com/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","https://about.gitlab.com","article","efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","fr-fr/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation",[6675,6676,205,6677],"integrations","tutorial","DevSecOps platform","BlogPost","e83nVmsDlUKuKYUF7HOgllweyr0giGnCfvSSnHP80JY",{"logo":6681,"freeTrial":6686,"sales":6691,"login":6696,"items":6701,"search":7028,"minimal":7064,"duo":7083,"switchNav":7092,"pricingDeployment":7103},{"config":6682},{"href":6683,"dataGaName":6684,"dataGaLocation":6685},"/fr-fr/","gitlab logo","header",{"text":6687,"config":6688},"Commencer un essai gratuit",{"href":6689,"dataGaName":6690,"dataGaLocation":6685},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/fr-fr&glm_content=default-saas-trial/","free trial",{"text":6692,"config":6693},"Demander une démo",{"href":6694,"dataGaName":6695,"dataGaLocation":6685},"/fr-fr/sales/?contact-topic=request-demo","sales",{"text":6697,"config":6698},"Connexion",{"href":6699,"dataGaName":6700,"dataGaLocation":6685},"https://gitlab.com/users/sign_in/","sign in",[6702,6731,6833,6838,6950,7006],{"text":6703,"config":6704,"menu":6706},"Plateforme",{"dataNavLevelOne":6705},"platform",{"type":6707,"columns":6708},"cards",[6709,6715,6723],{"title":6703,"description":6710,"link":6711},"La plateforme d'orchestration intelligente pour le DevSecOps",{"text":6712,"config":6713},"Explorer notre plateforme",{"href":6714,"dataGaName":6705,"dataGaLocation":6685},"/fr-fr/platform/",{"title":6716,"description":6717,"link":6718},"GitLab Duo Agent Platform","L'IA agentique pour l'ensemble du cycle de développement logiciel",{"text":6719,"config":6720},"Découvrir GitLab Duo",{"href":6721,"dataGaName":6722,"dataGaLocation":6685},"/fr-fr/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":6724,"description":6725,"link":6726},"Pourquoi GitLab ?","Découvrez les principales raisons pour lesquelles les entreprises choisissent GitLab",{"text":6727,"config":6728},"En savoir plus",{"href":6729,"dataGaName":6730,"dataGaLocation":6685},"/fr-fr/why-gitlab/","why gitlab",{"text":6732,"left":118,"config":6733,"menu":6735},"Produit",{"dataNavLevelOne":6734},"solutions",{"type":6736,"link":6737,"columns":6741,"feature":6812},"lists",{"text":6738,"config":6739},"Voir toutes les solutions",{"href":6740,"dataGaName":6734,"dataGaLocation":6685},"/fr-fr/solutions/",[6742,6767,6790],{"title":6743,"description":6744,"link":6745,"items":6750},"Automatisation","CI/CD et automatisation pour accélérer le déploiement",{"config":6746},{"icon":6747,"href":6748,"dataGaName":6749,"dataGaLocation":6685},"AutomatedCodeAlt","/fr-fr/solutions/delivery-automation/","automated software delivery",[6751,6755,6758,6763],{"text":6752,"config":6753},"CI/CD",{"href":6754,"dataGaLocation":6685,"dataGaName":6752},"/fr-fr/solutions/continuous-integration/",{"text":6716,"config":6756},{"href":6721,"dataGaLocation":6685,"dataGaName":6757},"gitlab duo agent platform - product menu",{"text":6759,"config":6760},"Gestion du code source",{"href":6761,"dataGaLocation":6685,"dataGaName":6762},"/fr-fr/solutions/source-code-management/","Source Code Management",{"text":6764,"config":6765},"Livraison de logiciels automatisée",{"href":6748,"dataGaLocation":6685,"dataGaName":6766},"Automated software delivery",{"title":6768,"description":6769,"link":6770,"items":6775},"Sécurité","Livrez du code plus rapidement sans compromettre la sécurité",{"config":6771},{"href":6772,"dataGaName":6773,"dataGaLocation":6685,"icon":6774},"/fr-fr/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[6776,6780,6785],{"text":6777,"config":6778},"Tests de sécurité des applications",{"href":6772,"dataGaName":6779,"dataGaLocation":6685},"Application security testing",{"text":6781,"config":6782},"Sécurité de la chaîne d'approvisionnement logicielle",{"href":6783,"dataGaLocation":6685,"dataGaName":6784},"/fr-fr/solutions/supply-chain/","Software supply chain security",{"text":6786,"config":6787},"Conformité logicielle",{"href":6788,"dataGaName":6789,"dataGaLocation":6685},"/fr-fr/solutions/software-compliance/","software compliance",{"title":6791,"link":6792,"items":6797},"Mesures",{"config":6793},{"icon":6794,"href":6795,"dataGaName":6796,"dataGaLocation":6685},"DigitalTransformation","/fr-fr/solutions/visibility-measurement/","visibility and measurement",[6798,6802,6807],{"text":6799,"config":6800},"Visibilité et mesures",{"href":6795,"dataGaLocation":6685,"dataGaName":6801},"Visibility and Measurement",{"text":6803,"config":6804},"Gestion de la chaîne de valeur",{"href":6805,"dataGaLocation":6685,"dataGaName":6806},"/fr-fr/solutions/value-stream-management/","Value Stream Management",{"text":6808,"config":6809},"Analyses et informations",{"href":6810,"dataGaLocation":6685,"dataGaName":6811},"/fr-fr/solutions/analytics-and-insights/","Analytics and insights",{"title":6813,"type":6736,"items":6814},"GitLab",[6815,6821,6827],{"text":6816,"config":6817},"Entreprises",{"icon":6818,"href":6819,"dataGaLocation":6685,"dataGaName":6820},"Building","/fr-fr/enterprise/","enterprise",{"text":6822,"config":6823},"Pour les PME",{"icon":6824,"href":6825,"dataGaLocation":6685,"dataGaName":6826},"Work","/fr-fr/small-business/","small business",{"text":6828,"config":6829},"Pour le secteur public",{"icon":6830,"href":6831,"dataGaLocation":6685,"dataGaName":6832},"Organization","/fr-fr/solutions/public-sector/","public sector",{"text":6834,"config":6835},"Tarifs",{"href":6836,"dataGaName":6837,"dataGaLocation":6685,"dataNavLevelOne":6837},"/fr-fr/pricing/","pricing",{"text":6839,"config":6840,"menu":6842},"Ressources",{"dataNavLevelOne":6841},"resources",{"type":6736,"link":6843,"columns":6847,"feature":6939},{"text":6844,"config":6845},"Afficher toutes les ressources",{"href":6846,"dataGaName":6841,"dataGaLocation":6685},"/fr-fr/resources/",[6848,6879,6906],{"title":51,"items":6849},[6850,6855,6860,6865,6870,6875],{"text":6851,"config":6852},"Installation",{"href":6853,"dataGaName":6854,"dataGaLocation":6685},"/fr-fr/install/","install",{"text":6856,"config":6857},"Guides de démarrage",{"href":6858,"dataGaName":6859,"dataGaLocation":6685},"/fr-fr/get-started/","quick setup checklists",{"text":6861,"config":6862},"Apprentissage",{"href":6863,"dataGaLocation":6685,"dataGaName":6864},"https://university.gitlab.com/","learn",{"text":6866,"config":6867},"Documentation",{"href":6868,"dataGaName":6869,"dataGaLocation":6685},"https://docs.gitlab.com/","product documentation",{"text":6871,"config":6872},"Vidéos sur les bonnes pratiques",{"href":6873,"dataGaName":6874,"dataGaLocation":6685},"/fr-fr/getting-started-videos/","best practice videos",{"text":6876,"config":6877},"Intégrations",{"href":6878,"dataGaName":6675,"dataGaLocation":6685},"/fr-fr/integrations/",{"title":6880,"items":6881},"Découvrir",[6882,6887,6892,6897,6901],{"text":6883,"config":6884},"Témoignages clients",{"href":6885,"dataGaName":6886,"dataGaLocation":6685},"/fr-fr/customers/","customer success stories",{"text":6888,"config":6889},"Blog",{"href":6890,"dataGaName":6891,"dataGaLocation":6685},"/fr-fr/blog/","blog",{"text":6893,"config":6894},"Demo Hub",{"href":6895,"dataGaName":6896,"dataGaLocation":6685},"/fr-fr/demo-hub/","demo hub",{"text":6898,"config":6899},"The Source",{"href":6900,"dataGaName":6891,"dataGaLocation":6685},"/fr-fr/the-source/",{"text":6902,"config":6903},"Travail à distance",{"href":6904,"dataGaName":6905,"dataGaLocation":6685},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":6907,"items":6908},"Connecter",[6909,6914,6919,6924,6929,6934],{"text":6910,"config":6911},"Services GitLab",{"href":6912,"dataGaName":6913,"dataGaLocation":6685},"/fr-fr/services/","services",{"text":6915,"config":6916},"Contribuer",{"href":6917,"dataGaName":6918,"dataGaLocation":6685},"https://contributors.gitlab.com","contribute",{"text":6920,"config":6921},"Communauté",{"href":6922,"dataGaName":6923,"dataGaLocation":6685},"/community/","community",{"text":6925,"config":6926},"Forum",{"href":6927,"dataGaName":6928,"dataGaLocation":6685},"https://forum.gitlab.com/","forum",{"text":6930,"config":6931},"Événements",{"href":6932,"dataGaName":6933,"dataGaLocation":6685},"/events/","events",{"text":6935,"config":6936},"Partenaires",{"href":6937,"dataGaName":6938,"dataGaLocation":6685},"/fr-fr/partners/","partners",{"config":6940,"title":6943,"text":6944,"link":6945},{"background":6941,"textColor":6942},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","Nouveautés de GitLab","Découvrez les dernières fonctionnalités et améliorations.",{"text":6946,"config":6947},"Lire les articles les plus récents",{"href":6948,"dataGaName":6949,"dataGaLocation":6685},"/fr-fr/whats-new/","whats new",{"text":6951,"config":6952,"menu":6954},"Société",{"dataNavLevelOne":6953},"company",{"type":6736,"columns":6955},[6956],{"items":6957},[6958,6963,6969,6971,6976,6981,6986,6991,6996,7001],{"text":6959,"config":6960},"À propos",{"href":6961,"dataGaName":6962,"dataGaLocation":6685},"/fr-fr/company/","about",{"text":6964,"config":6965,"footerGa":6968},"Carrières",{"href":6966,"dataGaName":6967,"dataGaLocation":6685},"/jobs/","jobs",{"dataGaName":6967},{"text":6930,"config":6970},{"href":6932,"dataGaName":6933,"dataGaLocation":6685},{"text":6972,"config":6973},"Leadership",{"href":6974,"dataGaName":6975,"dataGaLocation":6685},"/company/team/e-group/","leadership",{"text":6977,"config":6978},"Manuel",{"href":6979,"dataGaName":6980,"dataGaLocation":6685},"https://handbook.gitlab.com/","handbook",{"text":6982,"config":6983},"Relations avec les investisseurs",{"href":6984,"dataGaName":6985,"dataGaLocation":6685},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":6987,"config":6988},"Trust Center",{"href":6989,"dataGaName":6990,"dataGaLocation":6685},"/fr-fr/security/","trust center",{"text":6992,"config":6993},"Centre pour la transparence de l'IA",{"href":6994,"dataGaName":6995,"dataGaLocation":6685},"/fr-fr/ai-transparency-center/","ai transparency center",{"text":6997,"config":6998},"Newsletter",{"href":6999,"dataGaName":7000,"dataGaLocation":6685},"/company/contact/#contact-forms","newsletter",{"text":7002,"config":7003},"Presse",{"href":7004,"dataGaName":7005,"dataGaLocation":6685},"/press/","press",{"text":7007,"config":7008,"menu":7009},"Nous contacter",{"dataNavLevelOne":6953},{"type":6736,"columns":7010},[7011],{"items":7012},[7013,7018,7023],{"text":7014,"config":7015},"Contacter l'équipe commerciale",{"href":7016,"dataGaName":7017,"dataGaLocation":6685},"/fr-fr/sales/","talk to sales",{"text":7019,"config":7020},"Assistance GitLab",{"href":7021,"dataGaName":7022,"dataGaLocation":6685},"https://support.gitlab.com/hc/en-us","support portal",{"text":7024,"config":7025},"Portail clients GitLab",{"href":7026,"dataGaName":7027,"dataGaLocation":6685},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":7029,"login":7030,"suggestions":7037},"Fermer",{"text":7031,"link":7032},"Pour rechercher des dépôts et des projets, connectez-vous à",{"text":7033,"config":7034},"GitLab.com",{"href":6699,"dataGaName":7035,"dataGaLocation":7036},"search login","search",{"text":7038,"default":7039},"Suggestions",[7040,7043,7048,7050,7055,7060],{"text":6716,"config":7041},{"href":6721,"dataGaName":7042,"dataGaLocation":7036},"GitLab Duo Agent Platform",{"text":7044,"config":7045},"Suggestions de code (IA)",{"href":7046,"dataGaName":7047,"dataGaLocation":7036},"/fr-fr/solutions/code-suggestions/","Code Suggestions (AI)",{"text":6752,"config":7049},{"href":6754,"dataGaName":6752,"dataGaLocation":7036},{"text":7051,"config":7052},"GitLab sur AWS",{"href":7053,"dataGaName":7054,"dataGaLocation":7036},"/fr-fr/partners/technology-partners/aws/","GitLab on AWS",{"text":7056,"config":7057},"GitLab sur Google Cloud",{"href":7058,"dataGaName":7059,"dataGaLocation":7036},"/fr-fr/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":7061,"config":7062},"Pourquoi utiliser GitLab ?",{"href":6729,"dataGaName":7063,"dataGaLocation":7036},"Why GitLab?",{"freeTrial":7065,"mobileIcon":7070,"desktopIcon":7075,"secondaryButton":7078},{"text":7066,"config":7067},"Commencer votre essai gratuit",{"href":7068,"dataGaName":6690,"dataGaLocation":7069},"https://gitlab.com/-/trials/new/","nav",{"altText":7071,"config":7072},"Icône GitLab",{"src":7073,"dataGaName":7074,"dataGaLocation":7069},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":7071,"config":7076},{"src":7077,"dataGaName":7074,"dataGaLocation":7069},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":7079,"config":7080},"Commencer",{"href":7081,"dataGaName":7082,"dataGaLocation":7069},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/fr-fr/get-started/","get started",{"freeTrial":7084,"mobileIcon":7088,"desktopIcon":7090},{"text":7085,"config":7086},"En savoir plus sur GitLab Duo",{"href":6721,"dataGaName":7087,"dataGaLocation":7069},"gitlab duo",{"altText":7071,"config":7089},{"src":7073,"dataGaName":7074,"dataGaLocation":7069},{"altText":7071,"config":7091},{"src":7077,"dataGaName":7074,"dataGaLocation":7069},{"button":7093,"mobileIcon":7098,"desktopIcon":7100},{"text":7094,"config":7095},"/switch",{"href":7096,"dataGaName":7097,"dataGaLocation":7069},"#contact","switch",{"altText":7071,"config":7099},{"src":7073,"dataGaName":7074,"dataGaLocation":7069},{"altText":7071,"config":7101},{"src":7102,"dataGaName":7074,"dataGaLocation":7069},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":7104,"mobileIcon":7109,"desktopIcon":7111},{"text":7105,"config":7106},"Retour aux tarifs",{"href":6836,"dataGaName":7107,"dataGaLocation":7069,"icon":7108},"back to pricing","GoBack",{"altText":7071,"config":7110},{"src":7073,"dataGaName":7074,"dataGaLocation":7069},{"altText":7071,"config":7112},{"src":7077,"dataGaName":7074,"dataGaLocation":7069},{"title":7114,"button":7115,"config":7119},"GitLab Orbit est arrivé : la couche de contexte pour les agents d'IA.",{"text":6727,"config":7116},{"href":7117,"dataGaName":7118,"dataGaLocation":6685},"/fr-fr/gitlab-orbit/","orbit",{"layout":7120,"disabled":6664},"release",{"data":7122},{"text":7123,"source":7124,"edit":7130,"contribute":7135,"config":7139,"items":7144,"minimal":7351},"Git est une marque déposée de Software Freedom Conservancy et notre utilisation de « GitLab » est sous licence.",{"text":7125,"config":7126},"Afficher le code source de la page",{"href":7127,"dataGaName":7128,"dataGaLocation":7129},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":7131,"config":7132},"Modifier cette page",{"href":7133,"dataGaName":7134,"dataGaLocation":7129},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":6915,"config":7136},{"href":7137,"dataGaName":7138,"dataGaLocation":7129},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":7140,"facebook":7141,"youtube":7142,"linkedin":7143},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[7145,7191,7243,7286,7318],{"title":6834,"links":7146,"subMenu":7161},[7147,7151,7156],{"text":7148,"config":7149},"Voir les forfaits",{"href":6836,"dataGaName":7150,"dataGaLocation":7129},"view plans",{"text":7152,"config":7153},"GitLab Premium",{"href":7154,"dataGaName":7155,"dataGaLocation":7129},"/fr-fr/pricing/premium/","why premium",{"text":7157,"config":7158},"GitLab Ultimate",{"href":7159,"dataGaName":7160,"dataGaLocation":7129},"/fr-fr/pricing/ultimate/","why ultimate",[7162],{"title":7007,"links":7163},[7164,7166,7168,7171,7176,7181,7186],{"text":7014,"config":7165},{"href":7016,"dataGaName":6695,"dataGaLocation":7129},{"text":7019,"config":7167},{"href":7021,"dataGaName":7022,"dataGaLocation":7129},{"text":7169,"config":7170},"Portail clients",{"href":7026,"dataGaName":7027,"dataGaLocation":7129},{"text":7172,"config":7173},"Statut",{"href":7174,"dataGaName":7175,"dataGaLocation":7129},"https://status.gitlab.com/","status",{"text":7177,"config":7178},"Conditions d'utilisation",{"href":7179,"dataGaName":7180,"dataGaLocation":7129},"/terms/","terms of use",{"text":7182,"config":7183},"Politique de confidentialité",{"href":7184,"dataGaName":7185,"dataGaLocation":7129},"/fr-fr/privacy/","privacy statement",{"text":7187,"config":7188},"Gestion des cookies",{"dataGaName":7189,"dataGaLocation":7129,"id":7190,"isOneTrustButton":118},"cookie preferences","ot-sdk-btn",{"title":6732,"links":7192,"subMenu":7201},[7193,7197],{"text":7194,"config":7195},"Plateforme DevSecOps",{"href":6714,"dataGaName":7196,"dataGaLocation":7129},"devsecops platform",{"text":7198,"config":7199},"Développement assisté par l'IA",{"href":6721,"dataGaName":7200,"dataGaLocation":7129},"ai-assisted development",[7202],{"title":7203,"links":7204},"Thèmes",[7205,7209,7214,7219,7224,7228,7233,7238],{"text":6752,"config":7206},{"href":7207,"dataGaName":7208,"dataGaLocation":7129},"/fr-fr/topics/ci-cd/","cicd",{"text":7210,"config":7211},"GitOps",{"href":7212,"dataGaName":7213,"dataGaLocation":7129},"/fr-fr/topics/gitops/","gitops",{"text":7215,"config":7216},"DevOps",{"href":7217,"dataGaName":7218,"dataGaLocation":7129},"/fr-fr/topics/devops/","devops",{"text":7220,"config":7221},"Contrôle de version",{"href":7222,"dataGaName":7223,"dataGaLocation":7129},"/fr-fr/topics/version-control/","version control",{"text":205,"config":7225},{"href":7226,"dataGaName":7227,"dataGaLocation":7129},"/fr-fr/topics/devsecops/","devsecops",{"text":7229,"config":7230},"Cloud-native",{"href":7231,"dataGaName":7232,"dataGaLocation":7129},"/fr-fr/topics/cloud-native/","cloud native",{"text":7234,"config":7235},"IA pour la programmation",{"href":7236,"dataGaName":7237,"dataGaLocation":7129},"/fr-fr/topics/devops/ai-for-coding/","ai for coding",{"text":7239,"config":7240},"IA agentique",{"href":7241,"dataGaName":7242,"dataGaLocation":7129},"/fr-fr/topics/agentic-ai/","agentic ai",{"title":7244,"links":7245},"Solutions",[7246,7249,7251,7256,7259,7262,7265,7268,7270,7273,7276,7281],{"text":6777,"config":7247},{"href":6772,"dataGaName":7248,"dataGaLocation":7129},"Application Security Testing",{"text":6764,"config":7250},{"href":6748,"dataGaName":6749,"dataGaLocation":7129},{"text":7252,"config":7253},"Développement Agile",{"href":7254,"dataGaName":7255,"dataGaLocation":7129},"/fr-fr/solutions/agile-delivery/","agile delivery",{"text":6759,"config":7257},{"href":6761,"dataGaName":7258,"dataGaLocation":7129},"source code management",{"text":6752,"config":7260},{"href":6754,"dataGaName":7261,"dataGaLocation":7129},"continuous integration & delivery",{"text":6803,"config":7263},{"href":6805,"dataGaName":7264,"dataGaLocation":7129},"value stream management",{"text":7210,"config":7266},{"href":7267,"dataGaName":7213,"dataGaLocation":7129},"/fr-fr/solutions/gitops/",{"text":6816,"config":7269},{"href":6819,"dataGaName":6820,"dataGaLocation":7129},{"text":7271,"config":7272},"PME",{"href":6825,"dataGaName":6826,"dataGaLocation":7129},{"text":7274,"config":7275},"Secteur public",{"href":6831,"dataGaName":6832,"dataGaLocation":7129},{"text":7277,"config":7278},"Éducation",{"href":7279,"dataGaName":7280,"dataGaLocation":7129},"/fr-fr/solutions/education/","education",{"text":7282,"config":7283},"Services financiers",{"href":7284,"dataGaName":7285,"dataGaLocation":7129},"/fr-fr/solutions/finance/","financial services",{"title":6839,"links":7287},[7288,7290,7292,7294,7297,7299,7302,7304,7306,7308,7310,7312,7314,7316],{"text":6851,"config":7289},{"href":6853,"dataGaName":6854,"dataGaLocation":7129},{"text":6856,"config":7291},{"href":6858,"dataGaName":6859,"dataGaLocation":7129},{"text":6861,"config":7293},{"href":6863,"dataGaName":6864,"dataGaLocation":7129},{"text":6866,"config":7295},{"href":6868,"dataGaName":7296,"dataGaLocation":7129},"docs",{"text":6888,"config":7298},{"href":6890,"dataGaName":6891,"dataGaLocation":7129},{"text":7300,"config":7301},"Nouveautés",{"href":6948,"dataGaName":6949,"dataGaLocation":7129},{"text":6883,"config":7303},{"href":6885,"dataGaName":6886,"dataGaLocation":7129},{"text":6902,"config":7305},{"href":6904,"dataGaName":6905,"dataGaLocation":7129},{"text":6910,"config":7307},{"href":6912,"dataGaName":6913,"dataGaLocation":7129},{"text":6915,"config":7309},{"href":6917,"dataGaName":6918,"dataGaLocation":7129},{"text":6920,"config":7311},{"href":6922,"dataGaName":6923,"dataGaLocation":7129},{"text":6925,"config":7313},{"href":6927,"dataGaName":6928,"dataGaLocation":7129},{"text":6930,"config":7315},{"href":6932,"dataGaName":6933,"dataGaLocation":7129},{"text":6935,"config":7317},{"href":6937,"dataGaName":6938,"dataGaLocation":7129},{"title":6951,"links":7319},[7320,7322,7324,7326,7328,7330,7335,7340,7342,7344,7346],{"text":6959,"config":7321},{"href":6961,"dataGaName":6953,"dataGaLocation":7129},{"text":6964,"config":7323},{"href":6966,"dataGaName":6967,"dataGaLocation":7129},{"text":6972,"config":7325},{"href":6974,"dataGaName":6975,"dataGaLocation":7129},{"text":6977,"config":7327},{"href":6979,"dataGaName":6980,"dataGaLocation":7129},{"text":6982,"config":7329},{"href":6984,"dataGaName":6985,"dataGaLocation":7129},{"text":7331,"config":7332},"Développement durable",{"href":7333,"dataGaName":7334,"dataGaLocation":7129},"/sustainability/","Sustainability",{"text":7336,"config":7337},"Diversité, inclusion et appartenance (DIB)",{"href":7338,"dataGaName":7339,"dataGaLocation":7129},"/fr-fr/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":6987,"config":7341},{"href":6989,"dataGaName":6990,"dataGaLocation":7129},{"text":6997,"config":7343},{"href":6999,"dataGaName":7000,"dataGaLocation":7129},{"text":7002,"config":7345},{"href":7004,"dataGaName":7005,"dataGaLocation":7129},{"text":7347,"config":7348},"Déclaration de transparence sur l'esclavage moderne",{"href":7349,"dataGaName":7350,"dataGaLocation":7129},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":7352},[7353,7355,7357],{"text":7177,"config":7354},{"href":7179,"dataGaName":7180,"dataGaLocation":7129},{"text":7187,"config":7356},{"dataGaName":7189,"dataGaLocation":7129,"id":7190,"isOneTrustButton":118},{"text":7182,"config":7358},{"href":7184,"dataGaName":7185,"dataGaLocation":7129},[7360],{"id":7361,"title":7,"body":6663,"config":7362,"content":7364,"description":6663,"extension":7368,"meta":7369,"navigation":118,"path":7370,"seo":7371,"stem":7372,"__hash__":7373},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":7363},"BlogAuthor",{"name":7,"config":7365},{"headshot":7366,"ctfId":7367},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[7375,7383,7391],{"title":7376,"description":7377,"heroImage":7378,"category":6659,"date":7379,"authors":7380,"slug":7382,"externalUrl":6663},"Green DevOps : l'importance de mesurer le bilan carbone de vos pipelines CI/CD","Les pipelines CI/CD ont un bilan carbone caché. Découvrez pourquoi il est crucial de le mesurer et comment vous lancer avec Eco CI et Carmen dans GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-22",[7381],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":7384,"description":7385,"heroImage":7386,"category":6659,"date":7387,"authors":7388,"slug":7390,"externalUrl":6663},"Comment mettre en place l'observabilité CI/CD à grande échelle","Ce guide pratique consacré à l'analyse des pipelines GitLab aide les utilisateurs de GitLab Self-Managed à obtenir des indicateurs opérationnels exploitables grâce à Prometheus et Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/f_auto,q_auto,c_lfill/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-05-18",[7389],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"title":7392,"description":7393,"heroImage":7394,"category":6659,"date":7395,"authors":7396,"slug":7398,"externalUrl":6663},"5 modèles de pipeline GitLab pour résoudre vos enjeux d'ingénierie","Découvrez comment faire évoluer votre approche CI/CD avec des modèles composables pour les monorepos, les microservices, les environnements et la gouvernance.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-05-06",[7397],"Omid Khan","5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"promotions":7400},[7401,7415,7427,7439],{"id":7402,"categories":7403,"header":7405,"text":7406,"button":7407,"image":7412},"ai-modernization",[7404],"ai","L'IA tient-elle ses promesses à grande échelle ?","Le questionnaire ne prendra pas plus de 5 minutes.",{"text":7408,"config":7409},"Obtenez votre score de maturité IA",{"href":7410,"dataGaName":7411,"dataGaLocation":6891},"/fr-fr/assessments/ai-modernization-assessment/","modernization assessment",{"config":7413},{"src":7414},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":7416,"categories":7417,"header":7419,"text":7406,"button":7420,"image":7424},"devops-modernization",[7418,7227],"product","Vous contentez-vous de gérer des outils ou de livrer des innovations ?",{"text":7421,"config":7422},"Obtenez votre score de maturité DevOps",{"href":7423,"dataGaName":7411,"dataGaLocation":6891},"/fr-fr/assessments/devops-modernization-assessment/",{"config":7425},{"src":7426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":7428,"categories":7429,"header":7431,"text":7406,"button":7432,"image":7436},"security-modernization",[7430],"security","Faut-il sacrifier la rapidité pour garantir la sécurité ?",{"text":7433,"config":7434},"Obtenez votre score de maturité en matière de sécurité",{"href":7435,"dataGaName":7411,"dataGaLocation":6891},"/fr-fr/assessments/security-modernization-assessment/",{"config":7437},{"src":7438},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":7440,"paths":7441,"header":7444,"text":7445,"button":7446,"image":7451},"github-azure-migration",[7442,7443],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Votre équipe est-elle prête pour la migration de GitHub vers Azure ?","GitHub a déjà commencé sa migration vers Azure. Découvrez ce que cela signifie pour vous.",{"text":7447,"config":7448},"Découvrez les différences entre GitLab et GitHub",{"href":7449,"dataGaName":7450,"dataGaLocation":6891},"/fr-fr/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":7452},{"src":7426},{"header":7454,"blurb":7455,"button":7456,"secondaryButton":7460},"Commencez à développer plus rapidement dès aujourd'hui","Découvrez ce que votre équipe peut accomplir avec la plateforme d'orchestration intelligente pour le DevSecOps.\n",{"text":6687,"config":7457},{"href":7458,"dataGaName":6690,"dataGaLocation":7459},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/fr-fr/","feature",{"text":7014,"config":7461},{"href":7016,"dataGaName":6695,"dataGaLocation":7459},1786803763229]