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