[{"data":1,"prerenderedAt":707},["ShallowReactive",2],{"/de-de/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/":3,"navigation-de-de":38,"banner-de-de":457,"footer-de-de":470,"Michael Friedrich":679,"next-steps-de-de":692},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":28,"_id":31,"_type":32,"title":33,"_source":34,"_file":35,"_stem":36,"_extension":37},"/de-de/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},"Effiziente DevSecOps-Workflows: Praktische python-gitlab-API-Automatisierung","Die Python-GitLab-Bibliothek ist eine nützliche Basis für die GitLab-API. In diesem Tutorial erfährst du mehr über praktische Beispiele und bewährte Verfahren.","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\": \"Effiziente DevSecOps-Workflows: Praktische python-gitlab-API-Automatisierung\",\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,"updatedDate":27},[18],"Michael Friedrich","2023-02-01","Ein oft zitiertes Sprichwort aus einer Konferenzpräsentation lautet:\n„Manuelle Arbeit ist ein Fehler“. Bei sich wiederholenden Aufgaben in\nArbeitsabläufen ist es ratsam, so viel wie möglich zu automatisieren. Ein\nBeispiel dafür ist die Nutzung einer REST-API zur Bestandsaufnahme von\nEinstellungen oder das Erstellen neuer Kommentare in GitLab-Issues und\nMerge-Anfragen mittels API-Aktionen.\n\n\n## GitLab-API-Interaktion: Möglichkeiten und Unterstützung durch\nAPI-Abstraktionsbibliotheken\n\n\nDie Interaktion mit der REST-API von GitLab kann auf verschiedene Weise\nerfolgen: entweder durch HTTP-Anfragen mit curl (oder hurl) in der\nBefehlszeile oder durch das Schreiben von Skripten in einer\nProgrammiersprache. Letzteres kann dazu führen, dass der Code für\nHTTP-Anfragen und das Parsen der JSON-Antworten neu entwickelt werden muss.\nGlücklicherweise unterstützt die umfangreiche GitLab-Community viele\nProgrammiersprachen mit API-Abstraktionsbibliotheken.\n\n\nDiese Bibliotheken bieten Unterstützung für alle API-Attribute, fügen\nHilfsfunktionen zum Abrufen, Erstellen und Löschen von Objekten hinzu und\nhelfen Entwicklern dabei, sich auf ihre Kernaufgaben zu konzentrieren. Die\n[python-gitlab Bibliothek](https://python-gitlab.readthedocs.io/en/stable/)\nist ein besonders funktionsreiches und einfach zu verwendendes Beispiel für\neine solche Bibliothek in Python.\n\n\nIn diesem Blogbeitrag wird die grundlegende Nutzung der\npython-gitlab-Bibliothek erläutert, einschließlich der Arbeit mit\nAPI-Objekten, Attributen, Paginierung und Resultsets. Zudem werden\nspezifischere Anwendungsfälle vorgestellt, in denen Daten gesammelt,\nZusammenfassungen gedruckt und Daten in die API geschrieben werden, um\nKommentare und Commits zu erstellen. Viele dieser Anwendungsfälle basieren\nauf Fragen aus der Community in Foren, auf Hacker News oder in Issues.\n\n\n## Inhaltsverzeichnis\n\n- [GitLab-API-Interaktion: Möglichkeiten und Unterstützung durch\nAPI-Abstraktionsbibliotheken](#gitlab-api-interaktion-möglichkeiten-und-unterstützung-durch-api-abstraktionsbibliotheken)\n\n- [Los geht's: python-gitlab Install](#los-geht's-python-gitlab-install)\n\n- [GitLab API Python: Konfiguration](#gitlab-api-python-konfiguration)\n\n- [Objekte verwalten: das\nGitLab-Objekt](#objekte-verwalten-das-gitlab-objekt)\n  - [Objektmanager und Laden von Objekten](#objektmanager-und-laden-von-objekten)\n  - [Paginierung der Ergebnisse](#paginierung-der-ergebnisse)\n  - [Arbeiten mit Objektbeziehungen](#arbeiten-mit-objektbeziehungen)\n  - [Arbeiten mit verschiedenen Objektsammlungsbereichen](#arbeiten-mit-verschiedenen-objektsammlungsbereichen)\n- [DevSecOps-Anwendungsfälle für\nAPI-Leseaktionen](#devsecops-anwendungsfälle-für-api-leseaktionen)\n  - [Zweige nach Zusammenführungsstatus auflisten](#zweige-nach-zusammenführungsstatus-auflisten)\n  - [Drucken von Projekteinstellungen zur Überprüfung: MR-Genehmigungsregeln](#drucken-von-projekteinstellungen-zur-überprüfung-mr-genehmigungsregeln)\n  - [Inventarisierung: Abrufen aller CI/CD-Variablen, die geschützt oder maskiert sind](#inventarisierung-abrufen-aller-cicd-variablen-die-geschützt-oder-maskiert-sind)\n  - [Herunterladen einer Datei aus dem Repository](#herunterladen-einer-datei-aus-dem-repository)\n  - [Hilfe zur Migration: Auflistung aller zertifikatsbasierten Kubernetes-Cluster](#hilfe-zur-migration-auflistung-aller-zertifikatsbasierten-kubernetes-cluster)\n  - [Team-Effizienz: Prüfe, ob bestehende Merge-Requests nach dem Mergen einer großen Refactoring-MR neu gebasht werden müssen](#team-effizienz-prüfe-ob-bestehende-merge-requests-nach-dem-mergen-einer-großen-refactoring-mr-neu-gebasht-werden-müssen)\n- [DevSecOps-Anwendungsfälle für\nAPI-Schreibaktionen](#devsecops-anwendungsfälle-für-api-schreibaktionen)\n  - [Verschieben von Epics zwischen Gruppen](#verschieben-von-epics-zwischen-gruppen)\n  - [Automatisierung des Verschiebens von Epics](#automatisierung-des-verschiebens-von-epics)\n  - [Compliance: Sicherstellen, dass Projekteinstellungen nicht überschrieben werden](#compliance-sicherstellen-dass-projekteinstellungen-nicht-überschrieben-werden)\n  - [Notizen machen, Fälligkeitsübersicht erstellen](#notizen-machen-fälligkeitsübersicht-erstellen)\n\n## Los geht's: python-gitlab Install\n\n\nDie Dokumentation von python-gitlab bietet eine umfassende Einführung in die\nNutzung, einschließlich Anleitungen für den Einstieg, Informationen zu\nObjekttypen und deren Methoden sowie kombinierte Workflow-Beispiele.\nErgänzend dazu ist die Dokumentation der GitLab-API-Ressourcen hilfreich, da\nsie die verfügbaren Objektattribute detailliert beschreibt. Zusammen sind\ndiese beiden Dokumentationen die besten Ressourcen für den Einstieg.\n\n\nDie Code-Beispiele in diesem Blogbeitrag setzen Python 3.8+ und die\npython-gitlab-Bibliothek voraus. Weitere notwendige Abhängigkeiten sind in\nder Datei requirements.txt aufgeführt. Ein Beispiel erfordert die Bibliothek\npyyaml zum Parsen von YAML-Konfigurationen. Um den Code der Anwendungsfälle\nnachzuvollziehen und zu üben, empfiehlt es sich, das Projekt zu klonen, die\nAnforderungen zu installieren und die Skripte auszuführen.\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\nDie Skripte verwenden absichtlich keine gemeinsam genutzte Bibliothek, die\nz. B. generische Funktionen für das Lesen von Parametern oder zusätzliche\nHilfsfunktionen bereitstellt. Die Idee ist, einfach zu verstehende Beispiele\nzu zeigen, die eigenständig zum Testen verwendet werden können und lediglich\ndie Installation der python-gitlab-Bibliothek erfordern.\n\n\nEs wird empfohlen, den Code für den Produktionseinsatz zu verbessern. Dies\nkann auch beim Aufbau eines gewarteten API-Tooling-Projekts helfen, das z.\nB. Container-Images und CI/CD-Vorlagen für Entwickler enthält, die auf einer\nDevSecOps-Plattform genutzt werden können.\n\n\n## GitLab API Python: Konfiguration\n\n\nOhne Konfiguration führt python-gitlab unauthentifizierte Anfragen an den\nStandardserver https://gitlab.com aus. Die häufigsten\nKonfigurationseinstellungen beziehen sich auf die GitLab-Instanz, mit der\neine Verbindung hergestellt werden soll, und die Authentifizierungsmethode\ndurch Angabe von Zugriffstokens. python-gitlab unterstützt verschiedene\nArten der Konfiguration: Eine Konfigurationsdatei oder Umgebungsvariablen.\n\n\nDie Konfigurationsdatei ist für die API-Bibliotheksbindungen und die CLI\nverfügbar (die CLI wird in diesem Blogpost nicht erläutert). Die\nKonfigurationsdatei unterstützt Credential Helpers für den direkten Zugriff\nauf Token.\n\n\nUmgebungsvariablen als alternative Konfigurationsmethode bieten eine\neinfache Möglichkeit, das Skript auf dem Terminal auszuführen, in\nContainer-Images zu integrieren und sie für die Ausführung in\nCI/CD-Pipelines vorzubereiten.\n\n\nDie Konfiguration muss in den Kontext des Python-Skripts geladen werden.\nBeginne mit dem Import der os-Bibliothek, um die Umgebungsvariablen mit der\nMethode os.environ.get() abzurufen. Der erste Parameter gibt den Schlüssel\nan, der zweite Parameter legt den Standardwert fest, wenn die Variable in\nder Umgebung nicht verfügbar ist.\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\nDie Parametrisierung auf dem Terminal kann direkt nur für den Befehl\nerfolgen oder in die Shell-Umgebung exportiert werden.\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\nEs wird empfohlen, Sicherheitsprüfungen hinzuzufügen, um sicherzustellen,\ndass alle Variablen gesetzt sind, bevor das Programm weiter ausgeführt wird.\nDer folgende Ausschnitt importiert die erforderlichen Bibliotheken, liest\ndie Umgebungsvariable GL_SERVER und erwartet, dass der Benutzer die Variable\nGL_TOKEN setzt. Wenn dies nicht der Fall ist, gibt das Skript Fehler aus und\nruft sys.exit(1) auf, um einen Fehlerstatus anzuzeigen.\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\nFolgend siehst du ein ausführlicheres Beispiel, das eine Verbindung zur API\nherstellt und eine tatsächliche Datenabfrage durchführt.\n\n\n## Objekte verwalten: das GitLab-Objekt\n\n\nFür jede Interaktion mit der API muss das GitLab-Objekt instanziiert werden.\nDies ist der Einstiegspunkt für die Konfiguration des GitLab-Servers für die\nVerbindung, die Authentifizierung mithilfe von Zugriffstokens und weitere\nglobale Einstellungen für die Paginierung, das Laden von Objekten und mehr.\n\n\nIm folgenden Beispiel wird eine nicht authentifizierte Anfrage an gitlab.com\nausgeführt. Es ist möglich, auf öffentliche API-Endpunkte zuzugreifen und\nzum Beispiel eine bestimmte [.gitignore Vorlage für\nPython](https://python-gitlab.readthedocs.io/en/stable/gl_objects/templates.html#gitignore-templates)\nabzurufen.\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\nDie nächsten Abschnitte geben weitere Einblicke in:\n\n\n- Objektmanager und Laden von Objekten\n\n- Paginierung von Ergebnissen\n\n- Arbeiten mit Objektbeziehungen\n\n- Arbeiten mit verschiedenen Objektsammlungsbereichen\n\n\n### Objektmanager und Laden von Objekten\n\n\nDie python-gitlab-Bibliothek ermöglicht den Zugriff auf GitLab-Ressourcen\nüber sogenannte\n„[Manager](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#managers)\".\nJeder Managertyp implementiert Methoden zur Arbeit mit den Datensätzen\n(list, get, etc.).\n\n\nDas Skript zeigt, wie man auf Untergruppen, direkte Projekte, alle Projekte\neinschließlich Untergruppen, Issues, Epics und To-dos zugreifen kann. Diese\nMethoden und der API-Endpunkt erfordern eine Authentifizierung für den\nZugriff auf alle Attribute. Der Codeschnipsel verwendet daher Variablen, um\ndas Authentifizierungs-Token abzurufen sowie die GROUP_ID-Variable, um eine\nHauptgruppe anzugeben, bei der die Suche beginnen soll.\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\nDu kannst das Skript\n[`python_gitlab_object_manager_methods.py`](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_object_manager_methods.py)\nausführen, indem du die GROUP_ID-Variable auf GitLab.com SaaS für deine\neigene zu analysierende Gruppe überschreibst. Die Variable GL_SERVER muss\nfür selbstverwaltete Instanzziele angegeben werden. GL_TOKEN muss das\npersönliche Zugriffstoken enthalten.\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\nIn Zukunft werden die Beispiel-Snippets die Python-Header und das Parsen von\nUmgebungsvariablen nicht mehr zeigen, um sich auf den Algorithmus und die\nFunktionalität zu konzentrieren. Alle Skripte sind Open Source unter der\nMIT-Lizenz und in [diesem\nProjekt](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python)\nverfügbar.\n\n\n### Paginierung der Ergebnisse\n\n\nStandardmäßig gibt die GitLab-API nicht alle Ergebnissätze zurück und\nerfordert, dass die Clients die Paginierung verwenden, um durch alle\nErgebnisseiten zu iterieren. Mit der python-gitlab-Bibliothek können\nBenutzer die Einstellungen global im GitLab-Objekt oder bei jedem\nlist()-Aufruf\n[festlegen](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination).\nStandardmäßig würden alle Ergebnissätze API-Anfragen auslösen, was die\nSkriptausführung verlangsamen kann. Die empfohlene Methode ist die\nVerwendung von iterator=True, die ein Generatorobjekt zurückgibt, und\nAPI-Aufrufe werden beim Zugriff auf das Objekt bei Bedarf ausgelöst.\n\n\nDas folgende Beispiel sucht nach dem Gruppennamen everyonecancontribute und\nverwendet eine\n[Paginierung](https://docs.gitlab.com/ee/api/rest/index.html#pagination) der\nSchlüsselsätze mit 100 Ergebnissen auf jeder Seite. Der Iterator wird bei\ngl.groups.list(iterator=True) auf true gesetzt, um bei Bedarf neue\nErgebnissätze abzurufen. Wird der gesuchte Gruppenname gefunden, bricht die\nSchleife ab und gibt eine Zusammenfassung aus, einschließlich der Messung\nder Dauer der gesamten Suchanfrage.\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\n\nBeim Ausführen von python_gitlab_pagination.py wurde die Gruppe\n[everyonecancontribute](https://gitlab.com/everyonecancontribute) auf Seite\n5 gefunden.\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### Arbeiten mit Objektbeziehungen\n\n\nBei der Arbeit mit Objektbeziehungen – z. B. beim Sammeln aller Projekte in\neiner bestimmten Gruppe – müssen zusätzliche Schritte unternommen werden.\nDie zurückgegebenen Projektobjekte enthalten standardmäßig nur begrenzte\nAttribute. Für verwaltbare Objekte ist ein zusätzlicher get()-Aufruf\nerforderlich, der das vollständige Projektobjekt von der API im Hintergrund\nanfordert. Dieser On-Demand-Workflow hilft, Wartezeiten und Datenverkehr zu\nvermeiden, indem er die sofort zurückgegebenen Attribute reduziert.\n\n\nDas folgende Beispiel veranschaulicht das Problem, indem es eine Schleife\ndurch alle Projekte in einer Gruppe durchläuft und versucht, die Funktion\nproject.branches.list() aufzurufen, was eine Ausnahme im try/except-Flow\nauslöst. Im zweiten Beispiel wird ein verwaltbares Projektobjekt ermittelt\nund der Funktionsaufruf erneut versucht.\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\nDer Exception-Handler in der python-gitlab-Bibliothek gibt die Fehlermeldung\naus und verlinkt auch auf die Dokumentation. Es ist hilfreich, bei der\nFehlersuche zu beachten, dass Objekte möglicherweise nicht verwaltet werden\nkönnen, wenn du nicht auf Objektattribute oder Funktionsaufrufe zugreifen\nkannst.\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\nHier findest du das vollständige\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_manageable_objects.py).\n\n\n### Arbeiten mit verschiedenen Objektsammlungsbereichen\n\n\nManchmal muss das Skript alle Projekte aus einer selbstverwalteten Instanz,\naus einer Gruppe mit Untergruppen oder aus einem einzelnen Projekt sammeln.\nLetzteres ist hilfreich, um die erforderlichen Attribute schneller testen zu\nkönnen, und der Gruppenabruf hilft später beim Testen im großen Maßstab. Das\nfolgende Snippet sammelt alle Projektobjekte in der projects-Liste und fügt\nObjekte aus verschiedenen eingehenden Konfigurationen hinzu. Du wirst auch\nwieder das verwaltbare Objektmuster für Projekte in Gruppen sehen.\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\nDas vollständige Beispiel befindet sich in [diesem\nSkript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py)\nfür die Auflistung der Einstellungen der MR-Genehmigungsregeln für bestimmte\nProjektziele.\n\n\n## DevSecOps-Anwendungsfälle für API-Leseaktionen\n\n\nDas authentifizierte Zugriffstoken benötigt den Bereich [`read_api`\nscope](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes).\n\n\nDie folgenden Anwendungsfälle werden diskutiert:\n\n\n- Zweige nach Zusammenführungsstatus auflisten\n\n- Drucken von Projekteinstellungen zur Überprüfung: MR-Genehmigungsregeln\n\n- Inventarisierung: Abrufen aller CI/CD-Variablen, die geschützt oder\nmaskiert sind\n\n- Herunterladen einer Datei aus dem Repository\n\n- Hilfe zur Migration: Auflistung aller zertifikatsbasierten\nKubernetes-Cluster\n\n- Team-Effizienz: Prüfen Sie, ob bestehende Merge-Requests nach dem Mergen\neiner großen Refactoring-MR neu gebasht werden müssen\n\n\n### Zweige nach Zusammenführungsstatus auflisten\n\n\nEin häufiges Anliegen ist es, in einem Projekt ein wenig Git-Housekeeping zu\nbetreiben und zu sehen, wie viele zusammengeführte und nicht\nzusammengeführte Zweige im Umlauf sind. Eine\n[Frage](https://forum.gitlab.com/t/python-gitlab-project-branch-list-filter/80257)\nim GitLab-Community-Forum zum Filtern von Zweiglisten hat mich dazu\ninspiriert, ein\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_branches_by_state.py)\nzu schreiben, mit dem sich dieses Ziel erreichen lässt. Die Methode\nbranches.list() gibt alle Zweigobjekte zurück, die in einer temporären Liste\nfür die spätere Verarbeitung in zwei Schleifen gespeichert werden: Sammeln\nder Namen der zusammengeführten Zweige und der Namen der nicht\nzusammengeführten Zweige. Das Attribut merged des branch-Objekts ist ein\nboolescher Wert, der angibt, ob der Zweig zusammengeführt wurde.\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\nDer Arbeitsablauf ist absichtlich schrittweise zu lesen. Du kannst die\nOptimierung des Python-Codes für die bedingte Zweignamensammlung üben.\n\n\n### Drucken von Projekteinstellungen zur Überprüfung: MR-Genehmigungsregeln\n\n\nDas folgende\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py)\ngeht durch alle gesammelten Projektobjekte und prüft, ob Genehmigungsregeln\nangegeben sind. Wenn die Länge der Liste größer als Null ist, durchläuft es\ndie Liste in einem Loop und druckt die Einstellungen mit einer JSON-Ph\npretty-print-Methode aus.\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### Inventarisierung: Abrufen aller CI/CD-Variablen, die geschützt oder\nmaskiert sind\n\n\n[CI/CD Variablen](https://docs.gitlab.com/ee/ci/variables/) sind hilfreich\nfür die Pipeline-Parametrisierung und können global auf der Instanz, in\nGruppen und in Projekten konfiguriert werden. Auch Daten, Passwörter und\nandere sensible Informationen können dort gespeichert werden. Manchmal kann\nes notwendig sein, sich einen Überblick über alle CI/CD-Variablen zu\nverschaffen, die entweder geschützt oder maskiert sind, um ein Gefühl dafür\nzu bekommen, wie viele Variablen aktualisiert werden müssen, wenn Token zum\nBeispiel rotieren.\n\n\nDas folgende\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_variables_masked_or_protected.py)\nruft alle Gruppen und Projekte ab und versucht, die CI/CD-Variablen der\nglobalen Instanz (erfordert Admin-Rechte), der Gruppen und Projekte\n(erfordert Maintainer-/Eigentümer-Rechte) zu sammeln. Es gibt alle\nCI/CD-Variablen aus, die entweder geschützt oder maskiert sind, und fügt\nhinzu, dass ein möglicher geheimer Wert gespeichert ist.\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\nDas Skript druckt die Variablenwerte absichtlich nicht aus; dies soll als\nÜbung für sichere Umgebungen dienen. Für die Speicherung von Daten empfiehlt\nsich die Verwendung [externer\nAnbieter](https://docs.gitlab.com/ee/ci/secrets/).\n\n\n### Herunterladen einer Datei aus dem Repository\n\n\nZiel des\n[Skripts](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_raw_file_content.py)\nist es, einen Dateipfad von einem angegebenen Verzweigungsnamen\nherunterzuladen und dessen Inhalt in einer neuen Datei zu speichern.\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### Hilfe zur Migration: Auflistung aller zertifikatsbasierten\nKubernetes-Cluster\n\n\nDie zertifikatsbasierte Integration von Kubernetes-Clustern in GitLab wurde\n[abgeschafft](https://docs.gitlab.com/ee/update/deprecations.html#self-managed-certificate-based-integration-with-kubernetes).\nUm Migrationspläne zu unterstützen, kann die Erfassung bestehender Gruppen\nund Projekte mithilfe der GitLab-API automatisiert werden.\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\nHier findest du das vollständige\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/list_cert_based_kubernetes_clusters.py).\n\n\n### Team-Effizienz: Prüfe, ob bestehende Merge-Requests nach dem Mergen\neiner großen Refactoring-MR neu gebasht werden müssen\n\n\nDas [GitLab-Handbuch-Repository](https://handbook.gitlab.com/handbook/) ist ein großes Monorepo mit\nzahlreichen Merge-Requests, die erstellt, geprüft, genehmigt und\nzusammengeführt werden müssen. Einige Prüfungen dauern länger als andere,\ninsbesondere wenn Zusammenführungsanfragen mehrere Seiten betreffen,\nbeispielsweise wenn ein String umbenannt wird oder Änderungen auf\n[alle](/handbook/about/#count-handbook-pages) Handbuchseiten ausgedehnt\nwerden. Das Marketing-Handbuch wurde umstrukturiert, was zu vielen\nVerschiebungen oder Umbenennungen von Verzeichnissen und Pfaden führte.\n\n\nMit der Zeit nahmen die Issues zu, und es bestand die Sorge, dass andere\nMerge-Anfragen nach dem Zusammenführen der großen Änderungen auf Konflikte\nstoßen könnten. Es wurde festgestellt, dass Python-Gitlab in der Lage ist,\nalle Merge-Requests in einem bestimmten Projekt abzurufen, einschließlich\nDetails über den Git-Zweig, geänderte Quellpfade und vieles mehr.\n\n\nDas daraus resultierende Skript konfiguriert eine Liste von Quellpfaden, die\nvon allen pythongitlab-Merge-Requests berührt werden, und vergleicht die\nDiffs der Merge-Requests mit mr.diffs.list(), um festzustellen, ob ein\nMuster mit dem Wert in old_path. übereinstimmt. Bei einer Übereinstimmung\nprotokolliert das Skript diese und speichert die Zusammenführungsanforderung\nim seen_mr-Wörterbuch für die spätere Zusammenfassung. Zusätzlich werden\nAttribute gesammelt, um eine Markdown-Aufgabenliste mit URLs zum leichteren\nEinfügen in Issue-Beschreibungen zu erstellen.\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## DevSecOps-Anwendungsfälle für API-Schreibaktionen\n\n\nDas authentifizierte Zugriffstoken benötigt den vollen Anwendungsbereich der\n[`api`](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes).\n\n\nDie folgenden Anwendungsfälle werden diskutiert:\n\n\n- Verschieben von Epics zwischen Gruppen\n\n- Compliance: Sicherstellen, dass Projekteinstellungen nicht überschrieben\nwerden\n\n- Notizen machen, Fälligkeitsübersicht erstellen\n\n\n### Verschieben von Epics zwischen Gruppen\n\n\nManchmal ist es erforderlich, Epics, ähnlich wie Issues, in eine andere\nGruppe zu verschieben. Eine Frage im GitLab-Marketing-Slack-Kanal hat dazu\ngeführt, einen\n[Funktionsvorschlag](https://gitlab.com/gitlab-org/gitlab/-/issues/12689)\nfür die Benutzeroberfläche und die\n[Schnellaktionen](/blog/improve-your-gitlab-productivity-with-these-10-tips/)\nzu prüfen und später über das Schreiben eines API-Skripts nachzudenken, um\ndie Schritte zu automatisieren.\n\n\n### Automatisierung des Verschiebens von Epics\n\n\nDie Idee ist einfach: Ein Epic wird von einer Quellgruppe in eine Zielgruppe\nverschoben, wobei Titel, Beschreibung und Labels kopiert werden. Da Epics es\nerlauben, Themen zu gruppieren, müssen sie auch dem Ziel-Epic neu zugewiesen\nwerden. Parent-Child-Epic-Relationships müssen dabei berücksichtigt werden:\nAlle Child-Epics der Quell-Epics müssen dem Ziel-Epic neu zugewiesen werden.\n\n\nDas folgende Skript sucht zunächst alle\n[Attribute](https://python-gitlab.readthedocs.io/en/stable/gl_objects/epics.html)\ndes Quellepos und erstellt dann ein neues Zielepos mit den minimalen\nAttributen: Titel und Beschreibung. Die Liste der Bezeichnungen wird kopiert\nund die Änderungen werden mit dem save()-Aufruf beibehalten. Die Ausgaben,\ndie dem Epos zugeordnet sind, müssen im Zielepos neu erstellt werden.\n\n\nDer create()-Aufruf erzeugt das Beziehungselement und nicht ein neues\nIssue-Objekt selbst. Das Verschieben von Child-Epics erfordert einen anderen\nAnsatz, da die Beziehung umgekehrt ist: Die parent_id des Child-Epics muss\nmit der ID des Quell-Epics verglichen und bei Übereinstimmung auf die ID des\nZiel-Epics aktualisiert werden. Nachdem alles erfolgreich kopiert wurde,\nmuss das Quell-Epos in den closed-Zustand versetzt werden.\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\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\nDas\n[Ziel-Epic](https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/5)\nwurde erstellt und zeigt das erwartete Ergebnis: Derselbe Titel, dieselbe\nBeschreibung, dieselben Bezeichnungen, dasselbe untergeordnete Epic und\ndieselben Issues.\n\n\n![Target epic which has all attributes copied from the source epic: title,\ndescription, labels, child epics,\nissues](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**Übung:** Das Skript kopiert noch keine\n[Kommentare](https://python-gitlab.readthedocs.io/en/stable/gl_objects/notes.html)\nund\n[Diskussionsstränge](https://python-gitlab.readthedocs.io/en/stable/gl_objects/discussions.html).\nRecherchiere und hilf mit, das Skript zu aktualisieren – Merge-Requests\nwillkommen!\n\n\n### Compliance: Sicherstellen, dass Projekteinstellungen nicht überschrieben\nwerden\n\n\nProjekt- und Gruppeneinstellungen können versehentlich von Teammitgliedern\nmit Administratorrechten geändert werden. Die Compliance-Anforderungen\nmüssen erfüllt werden. Ein weiterer Anwendungsfall ist die Verwaltung der\nKonfiguration mit Infrastructure-as-Code-Tools, um sicherzustellen, dass die\nKonfiguration von GitLab-Instanzen, -Gruppen, -Projekten usw. erhalten\nbleibt und immer dieselbe ist. Tools wie Ansible oder Terraform können ein\nAPI-Skript aufrufen oder die Python-GitLab-Bibliothek verwenden, um Aufgaben\nzur Verwaltung von Einstellungen auszuführen.\n\n\nIm folgenden Beispiel ist nur der main-Zweig geschützt.\n\n\n![GitLab project settings for repositories and protected branches, main\nbranch](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main.png){:\n.shadow}\n\n\nNehmen wir an, dass ein neuer production-Zweig hinzugefügt wurde und\nebenfalls geschützt werden soll. Das folgende\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/enforce_protected_branches.py)\ndefiniert das Wörterbuch der geschützten Zweige und ihre Zugriffsebenen für\nPush-/Merge-Berechtigungen auf Maintainer-Ebene und baut die Vergleichslogik\nauf der Grundlage der\n[python-gitlab-Dokumentation](https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html)\nzu geschützten Zweigen auf.\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\nWenn das Skript ausgeführt wird, werden der bestehende main-Zweig und ein\nHinweis, dass die production aktualisiert wird, ausgegeben. Der Screenshot\naus den Repository-Einstellungen verdeutlicht diese Aktion.\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![GitLab project settings for repositories and protected branches, main and\nproduction\nbranch](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### Notizen machen, Fälligkeitsübersicht erstellen\n\n\nEine Diskussion auf [Hacker\nNews](https://news.ycombinator.com/item?id=32155848) über Tools zum\nErstellen von Notizen hat mich dazu inspiriert, eine Übersicht in Form einer\nMarkdown-Tabelle zu erstellen, die aus Dateien, die Notizen aufnehmen,\ngeholt und nach dem geparsten Fälligkeitsdatum sortiert wird. Das\n[Skript](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/generate_snippets_index_by_due_date.py)\nist hier zu finden und etwas komplexer zu verstehen.\n\n\n\u003C!--\n\n# 2022-07-19 Notes\n\n\nHN topic about taking notes: https://news.ycombinator.com/item?id=32152935\n\n\n-->\n\n\n***Die englischsprachige\n[Originalversion](https://about.gitlab.com/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/)\ndieses Artikels wurde bereits aktualisiert und erhält einige weitere Tipps,\nwelche wir der deutschen Version beizeiten hinzufügen werden.***\n","engineering",[23,24,25,26],"integrations","tutorial","DevSecOps","DevSecOps platform","2025-05-16",{"slug":29,"featured":6,"template":30},"efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","BlogPost","content:de-de:blog:efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml","yaml","Efficient Devsecops Workflows Hands On Python Gitlab Api Automation","content","de-de/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml","de-de/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation","yml",{"_path":39,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":41,"_id":453,"_type":32,"title":454,"_source":34,"_file":455,"_stem":456,"_extension":37},"/shared/de-de/main-navigation","de-de",{"logo":42,"freeTrial":47,"sales":52,"login":57,"items":62,"search":394,"minimal":430,"duo":444},{"config":43},{"href":44,"dataGaName":45,"dataGaLocation":46},"/de-de/","gitlab logo","header",{"text":48,"config":49},"Kostenlose Testversion anfordern",{"href":50,"dataGaName":51,"dataGaLocation":46},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":53,"config":54},"Vertrieb kontaktieren",{"href":55,"dataGaName":56,"dataGaLocation":46},"/de-de/sales/","sales",{"text":58,"config":59},"Anmelden",{"href":60,"dataGaName":61,"dataGaLocation":46},"https://gitlab.com/users/sign_in/","sign in",[63,107,206,211,315,375],{"text":64,"config":65,"cards":67,"footer":90},"Plattform",{"dataNavLevelOne":66},"platform",[68,74,82],{"title":64,"description":69,"link":70},"Die umfassendste KI-basierte DevSecOps-Plattform",{"text":71,"config":72},"Erkunde unsere Plattform",{"href":73,"dataGaName":66,"dataGaLocation":46},"/de-de/platform/",{"title":75,"description":76,"link":77},"GitLab Duo (KI)","Entwickle Software schneller mit KI in jeder Phase der Entwicklung",{"text":78,"config":79},"Lerne GitLab Duo kennen",{"href":80,"dataGaName":81,"dataGaLocation":46},"/de-de/gitlab-duo/","gitlab duo ai",{"title":83,"description":84,"link":85},"Gründe, die für GitLab sprechen","10 Gründe, warum Unternehmen sich für GitLab entscheiden",{"text":86,"config":87},"Mehr erfahren",{"href":88,"dataGaName":89,"dataGaLocation":46},"/de-de/why-gitlab/","why gitlab",{"title":91,"items":92},"Erste Schritte mit",[93,98,103],{"text":94,"config":95},"Platform Engineering",{"href":96,"dataGaName":97,"dataGaLocation":46},"/de-de/solutions/platform-engineering/","platform engineering",{"text":99,"config":100},"Entwicklererfahrung",{"href":101,"dataGaName":102,"dataGaLocation":46},"/de-de/developer-experience/","Developer experience",{"text":104,"config":105},"MLOps",{"href":106,"dataGaName":104,"dataGaLocation":46},"/de-de/topics/devops/the-role-of-ai-in-devops/",{"text":108,"left":109,"config":110,"link":112,"lists":116,"footer":188},"Produkt",true,{"dataNavLevelOne":111},"solutions",{"text":113,"config":114},"Alle Lösungen anzeigen",{"href":115,"dataGaName":111,"dataGaLocation":46},"/de-de/solutions/",[117,143,166],{"title":118,"description":119,"link":120,"items":125},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":121},{"icon":122,"href":123,"dataGaName":124,"dataGaLocation":46},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[126,130,134,139],{"text":127,"config":128},"CI/CD",{"href":129,"dataGaLocation":46,"dataGaName":127},"/de-de/solutions/continuous-integration/",{"text":131,"config":132},"KI-unterstützte Entwicklung",{"href":80,"dataGaLocation":46,"dataGaName":133},"AI assisted development",{"text":135,"config":136},"Quellcodeverwaltung",{"href":137,"dataGaLocation":46,"dataGaName":138},"/de-de/solutions/source-code-management/","Source Code Management",{"text":140,"config":141},"Automatisierte Softwarebereitstellung",{"href":123,"dataGaLocation":46,"dataGaName":142},"Automated software delivery",{"title":144,"description":145,"link":146,"items":151},"Sicherheit","Entwickle schneller, ohne die Sicherheit zu gefährden",{"config":147},{"href":148,"dataGaName":149,"dataGaLocation":46,"icon":150},"/de-de/solutions/security-compliance/","security and compliance","ShieldCheckLight",[152,157,162],{"text":153,"config":154},"Application Security Testing",{"href":155,"dataGaName":156,"dataGaLocation":46},"/solutions/application-security-testing/","Application security testing",{"text":158,"config":159},"Schutz der Software-Lieferkette",{"href":160,"dataGaLocation":46,"dataGaName":161},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":163,"config":164},"Software Compliance",{"href":165,"dataGaName":163,"dataGaLocation":46},"/solutions/software-compliance/",{"title":167,"link":168,"items":173},"Bewertung",{"config":169},{"icon":170,"href":171,"dataGaName":172,"dataGaLocation":46},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[174,178,183],{"text":175,"config":176},"Sichtbarkeit und Bewertung",{"href":171,"dataGaLocation":46,"dataGaName":177},"Visibility and Measurement",{"text":179,"config":180},"Wertstrommanagement",{"href":181,"dataGaLocation":46,"dataGaName":182},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":184,"config":185},"Analysen und Einblicke",{"href":186,"dataGaLocation":46,"dataGaName":187},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":189,"items":190},"GitLab für",[191,196,201],{"text":192,"config":193},"Enterprise",{"href":194,"dataGaLocation":46,"dataGaName":195},"/de-de/enterprise/","enterprise",{"text":197,"config":198},"Kleinunternehmen",{"href":199,"dataGaLocation":46,"dataGaName":200},"/de-de/small-business/","small business",{"text":202,"config":203},"den öffentlichen Sektor",{"href":204,"dataGaLocation":46,"dataGaName":205},"/de-de/solutions/public-sector/","public sector",{"text":207,"config":208},"Preise",{"href":209,"dataGaName":210,"dataGaLocation":46,"dataNavLevelOne":210},"/de-de/pricing/","pricing",{"text":212,"config":213,"link":215,"lists":219,"feature":302},"Ressourcen",{"dataNavLevelOne":214},"resources",{"text":216,"config":217},"Alle Ressourcen anzeigen",{"href":218,"dataGaName":214,"dataGaLocation":46},"/de-de/resources/",[220,252,274],{"title":221,"items":222},"Erste Schritte",[223,228,233,238,243,248],{"text":224,"config":225},"Installieren",{"href":226,"dataGaName":227,"dataGaLocation":46},"/de-de/install/","install",{"text":229,"config":230},"Kurzanleitungen",{"href":231,"dataGaName":232,"dataGaLocation":46},"/de-de/get-started/","quick setup checklists",{"text":234,"config":235},"Lernen",{"href":236,"dataGaLocation":46,"dataGaName":237},"https://university.gitlab.com/","learn",{"text":239,"config":240},"Produktdokumentation",{"href":241,"dataGaName":242,"dataGaLocation":46},"https://docs.gitlab.com/","product documentation",{"text":244,"config":245},"Best-Practice-Videos",{"href":246,"dataGaName":247,"dataGaLocation":46},"/de-de/getting-started-videos/","best practice videos",{"text":249,"config":250},"Integrationen",{"href":251,"dataGaName":23,"dataGaLocation":46},"/de-de/integrations/",{"title":253,"items":254},"Entdecken",[255,260,264,269],{"text":256,"config":257},"Kundenerfolge",{"href":258,"dataGaName":259,"dataGaLocation":46},"/de-de/customers/","customer success stories",{"text":261,"config":262},"Blog",{"href":263,"dataGaName":5,"dataGaLocation":46},"/de-de/blog/",{"text":265,"config":266},"Remote",{"href":267,"dataGaName":268,"dataGaLocation":46},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":270,"config":271},"TeamOps",{"href":272,"dataGaName":273,"dataGaLocation":46},"/de-de/teamops/","teamops",{"title":275,"items":276},"Vernetzen",[277,282,287,292,297],{"text":278,"config":279},"GitLab-Services",{"href":280,"dataGaName":281,"dataGaLocation":46},"/de-de/services/","services",{"text":283,"config":284},"Community",{"href":285,"dataGaName":286,"dataGaLocation":46},"/community/","community",{"text":288,"config":289},"Forum",{"href":290,"dataGaName":291,"dataGaLocation":46},"https://forum.gitlab.com/","forum",{"text":293,"config":294},"Veranstaltungen",{"href":295,"dataGaName":296,"dataGaLocation":46},"/events/","events",{"text":298,"config":299},"Partner",{"href":300,"dataGaName":301,"dataGaLocation":46},"/partners/","partners",{"backgroundColor":303,"textColor":304,"text":305,"image":306,"link":310},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":307,"config":308},"the source promo card",{"src":309},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":311,"config":312},"Lies die News",{"href":313,"dataGaName":314,"dataGaLocation":46},"/de-de/the-source/","the source",{"text":316,"config":317,"lists":319},"Unternehmen",{"dataNavLevelOne":318},"company",[320],{"items":321},[322,327,333,335,340,345,350,355,360,365,370],{"text":323,"config":324},"Über",{"href":325,"dataGaName":326,"dataGaLocation":46},"/de-de/company/","about",{"text":328,"config":329,"footerGa":332},"Karriere",{"href":330,"dataGaName":331,"dataGaLocation":46},"/jobs/","jobs",{"dataGaName":331},{"text":293,"config":334},{"href":295,"dataGaName":296,"dataGaLocation":46},{"text":336,"config":337},"Geschäftsführung",{"href":338,"dataGaName":339,"dataGaLocation":46},"/company/team/e-group/","leadership",{"text":341,"config":342},"Team",{"href":343,"dataGaName":344,"dataGaLocation":46},"/company/team/","team",{"text":346,"config":347},"Handbuch",{"href":348,"dataGaName":349,"dataGaLocation":46},"https://handbook.gitlab.com/","handbook",{"text":351,"config":352},"Investor Relations",{"href":353,"dataGaName":354,"dataGaLocation":46},"https://ir.gitlab.com/","investor relations",{"text":356,"config":357},"Trust Center",{"href":358,"dataGaName":359,"dataGaLocation":46},"/de-de/security/","trust center",{"text":361,"config":362},"AI Transparency Center",{"href":363,"dataGaName":364,"dataGaLocation":46},"/de-de/ai-transparency-center/","ai transparency center",{"text":366,"config":367},"Newsletter",{"href":368,"dataGaName":369,"dataGaLocation":46},"/company/contact/","newsletter",{"text":371,"config":372},"Presse",{"href":373,"dataGaName":374,"dataGaLocation":46},"/press/","press",{"text":376,"config":377,"lists":378},"Kontakt",{"dataNavLevelOne":318},[379],{"items":380},[381,384,389],{"text":53,"config":382},{"href":55,"dataGaName":383,"dataGaLocation":46},"talk to sales",{"text":385,"config":386},"Support",{"href":387,"dataGaName":388,"dataGaLocation":46},"/support/","get help",{"text":390,"config":391},"Kundenportal",{"href":392,"dataGaName":393,"dataGaLocation":46},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":395,"login":396,"suggestions":403},"Schließen",{"text":397,"link":398},"Um Repositories und Projekte zu durchsuchen, melde dich an bei",{"text":399,"config":400},"gitlab.com",{"href":60,"dataGaName":401,"dataGaLocation":402},"search login","search",{"text":404,"default":405},"Vorschläge",[406,409,414,416,421,426],{"text":75,"config":407},{"href":80,"dataGaName":408,"dataGaLocation":402},"GitLab Duo (AI)",{"text":410,"config":411},"Code Suggestions (KI)",{"href":412,"dataGaName":413,"dataGaLocation":402},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":127,"config":415},{"href":129,"dataGaName":127,"dataGaLocation":402},{"text":417,"config":418},"GitLab auf AWS",{"href":419,"dataGaName":420,"dataGaLocation":402},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":422,"config":423},"GitLab auf Google Cloud",{"href":424,"dataGaName":425,"dataGaLocation":402},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":427,"config":428},"Warum GitLab?",{"href":88,"dataGaName":429,"dataGaLocation":402},"Why GitLab?",{"freeTrial":431,"mobileIcon":436,"desktopIcon":441},{"text":432,"config":433},"Kostenlos testen",{"href":434,"dataGaName":51,"dataGaLocation":435},"https://gitlab.com/-/trials/new/","nav",{"altText":437,"config":438},"GitLab-Symbol",{"src":439,"dataGaName":440,"dataGaLocation":435},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":437,"config":442},{"src":443,"dataGaName":440,"dataGaLocation":435},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"freeTrial":445,"mobileIcon":449,"desktopIcon":451},{"text":446,"config":447},"Erfahre mehr über GitLab Duo",{"href":80,"dataGaName":448,"dataGaLocation":435},"gitlab duo",{"altText":437,"config":450},{"src":439,"dataGaName":440,"dataGaLocation":435},{"altText":437,"config":452},{"src":443,"dataGaName":440,"dataGaLocation":435},"content:shared:de-de:main-navigation.yml","Main Navigation","shared/de-de/main-navigation.yml","shared/de-de/main-navigation",{"_path":458,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"title":459,"button":460,"config":465,"_id":467,"_type":32,"_source":34,"_file":468,"_stem":469,"_extension":37},"/shared/de-de/banner","GitLab Duo Agent Platform ist jetzt in öffentlicher Beta!",{"text":461,"config":462},"Beta testen",{"href":463,"dataGaName":464,"dataGaLocation":46},"/de-de/gitlab-duo/agent-platform/","duo banner",{"layout":466},"release","content:shared:de-de:banner.yml","shared/de-de/banner.yml","shared/de-de/banner",{"_path":471,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":472,"_id":675,"_type":32,"title":676,"_source":34,"_file":677,"_stem":678,"_extension":37},"/shared/de-de/main-footer",{"text":473,"source":474,"edit":480,"contribute":485,"config":490,"items":495,"minimal":667},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":475,"config":476},"Quelltext der Seite anzeigen",{"href":477,"dataGaName":478,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":481,"config":482},"Diese Seite bearbeiten",{"href":483,"dataGaName":484,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":486,"config":487},"Beteilige dich",{"href":488,"dataGaName":489,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":491,"facebook":492,"youtube":493,"linkedin":494},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[496,519,574,603,637],{"title":64,"links":497,"subMenu":502},[498],{"text":499,"config":500},"DevSecOps-Plattform",{"href":73,"dataGaName":501,"dataGaLocation":479},"devsecops platform",[503],{"title":207,"links":504},[505,509,514],{"text":506,"config":507},"Tarife anzeigen",{"href":209,"dataGaName":508,"dataGaLocation":479},"view plans",{"text":510,"config":511},"Vorteile von Premium",{"href":512,"dataGaName":513,"dataGaLocation":479},"/de-de/pricing/premium/","why premium",{"text":515,"config":516},"Vorteile von Ultimate",{"href":517,"dataGaName":518,"dataGaLocation":479},"/de-de/pricing/ultimate/","why ultimate",{"title":520,"links":521},"Lösungen",[522,527,530,532,537,542,546,549,552,557,559,561,564,569],{"text":523,"config":524},"Digitale Transformation",{"href":525,"dataGaName":526,"dataGaLocation":479},"/de-de/topics/digital-transformation/","digital transformation",{"text":528,"config":529},"Sicherheit und Compliance",{"href":155,"dataGaName":156,"dataGaLocation":479},{"text":140,"config":531},{"href":123,"dataGaName":124,"dataGaLocation":479},{"text":533,"config":534},"Agile Entwicklung",{"href":535,"dataGaName":536,"dataGaLocation":479},"/de-de/solutions/agile-delivery/","agile delivery",{"text":538,"config":539},"Cloud-Transformation",{"href":540,"dataGaName":541,"dataGaLocation":479},"/de-de/topics/cloud-native/","cloud transformation",{"text":543,"config":544},"SCM",{"href":137,"dataGaName":545,"dataGaLocation":479},"source code management",{"text":127,"config":547},{"href":129,"dataGaName":548,"dataGaLocation":479},"continuous integration & delivery",{"text":179,"config":550},{"href":181,"dataGaName":551,"dataGaLocation":479},"value stream management",{"text":553,"config":554},"GitOps",{"href":555,"dataGaName":556,"dataGaLocation":479},"/de-de/solutions/gitops/","gitops",{"text":192,"config":558},{"href":194,"dataGaName":195,"dataGaLocation":479},{"text":197,"config":560},{"href":199,"dataGaName":200,"dataGaLocation":479},{"text":562,"config":563},"Öffentlicher Sektor",{"href":204,"dataGaName":205,"dataGaLocation":479},{"text":565,"config":566},"Bildungswesen",{"href":567,"dataGaName":568,"dataGaLocation":479},"/de-de/solutions/education/","education",{"text":570,"config":571},"Finanzdienstleistungen",{"href":572,"dataGaName":573,"dataGaLocation":479},"/de-de/solutions/finance/","financial services",{"title":212,"links":575},[576,578,580,582,585,587,589,591,593,595,597,599,601],{"text":224,"config":577},{"href":226,"dataGaName":227,"dataGaLocation":479},{"text":229,"config":579},{"href":231,"dataGaName":232,"dataGaLocation":479},{"text":234,"config":581},{"href":236,"dataGaName":237,"dataGaLocation":479},{"text":239,"config":583},{"href":241,"dataGaName":584,"dataGaLocation":479},"docs",{"text":261,"config":586},{"href":263,"dataGaName":5,"dataGaLocation":479},{"text":256,"config":588},{"href":258,"dataGaName":259,"dataGaLocation":479},{"text":265,"config":590},{"href":267,"dataGaName":268,"dataGaLocation":479},{"text":278,"config":592},{"href":280,"dataGaName":281,"dataGaLocation":479},{"text":270,"config":594},{"href":272,"dataGaName":273,"dataGaLocation":479},{"text":283,"config":596},{"href":285,"dataGaName":286,"dataGaLocation":479},{"text":288,"config":598},{"href":290,"dataGaName":291,"dataGaLocation":479},{"text":293,"config":600},{"href":295,"dataGaName":296,"dataGaLocation":479},{"text":298,"config":602},{"href":300,"dataGaName":301,"dataGaLocation":479},{"title":316,"links":604},[605,607,609,611,613,615,617,621,626,628,630,632],{"text":323,"config":606},{"href":325,"dataGaName":318,"dataGaLocation":479},{"text":328,"config":608},{"href":330,"dataGaName":331,"dataGaLocation":479},{"text":336,"config":610},{"href":338,"dataGaName":339,"dataGaLocation":479},{"text":341,"config":612},{"href":343,"dataGaName":344,"dataGaLocation":479},{"text":346,"config":614},{"href":348,"dataGaName":349,"dataGaLocation":479},{"text":351,"config":616},{"href":353,"dataGaName":354,"dataGaLocation":479},{"text":618,"config":619},"Sustainability",{"href":620,"dataGaName":618,"dataGaLocation":479},"/sustainability/",{"text":622,"config":623},"Vielfalt, Inklusion und Zugehörigkeit",{"href":624,"dataGaName":625,"dataGaLocation":479},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":356,"config":627},{"href":358,"dataGaName":359,"dataGaLocation":479},{"text":366,"config":629},{"href":368,"dataGaName":369,"dataGaLocation":479},{"text":371,"config":631},{"href":373,"dataGaName":374,"dataGaLocation":479},{"text":633,"config":634},"Transparenzerklärung zu moderner Sklaverei",{"href":635,"dataGaName":636,"dataGaLocation":479},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":638,"links":639},"Nimm Kontakt auf",[640,643,645,647,652,657,662],{"text":641,"config":642},"Sprich mit einem Experten/einer Expertin",{"href":55,"dataGaName":56,"dataGaLocation":479},{"text":385,"config":644},{"href":387,"dataGaName":388,"dataGaLocation":479},{"text":390,"config":646},{"href":392,"dataGaName":393,"dataGaLocation":479},{"text":648,"config":649},"Status",{"href":650,"dataGaName":651,"dataGaLocation":479},"https://status.gitlab.com/","status",{"text":653,"config":654},"Nutzungsbedingungen",{"href":655,"dataGaName":656,"dataGaLocation":479},"/terms/","terms of use",{"text":658,"config":659},"Datenschutzerklärung",{"href":660,"dataGaName":661,"dataGaLocation":479},"/de-de/privacy/","privacy statement",{"text":663,"config":664},"Cookie-Einstellungen",{"dataGaName":665,"dataGaLocation":479,"id":666,"isOneTrustButton":109},"cookie preferences","ot-sdk-btn",{"items":668},[669,671,673],{"text":653,"config":670},{"href":655,"dataGaName":656,"dataGaLocation":479},{"text":658,"config":672},{"href":660,"dataGaName":661,"dataGaLocation":479},{"text":663,"config":674},{"dataGaName":665,"dataGaLocation":479,"id":666,"isOneTrustButton":109},"content:shared:de-de:main-footer.yml","Main Footer","shared/de-de/main-footer.yml","shared/de-de/main-footer",[680],{"_path":681,"_dir":682,"_draft":6,"_partial":6,"_locale":7,"content":683,"config":687,"_id":689,"_type":32,"title":18,"_source":34,"_file":690,"_stem":691,"_extension":37},"/en-us/blog/authors/michael-friedrich","authors",{"name":18,"config":684},{"headshot":685,"ctfId":686},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{"template":688},"BlogAuthor","content:en-us:blog:authors:michael-friedrich.yml","en-us/blog/authors/michael-friedrich.yml","en-us/blog/authors/michael-friedrich",{"_path":693,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"header":694,"eyebrow":695,"blurb":696,"button":697,"secondaryButton":701,"_id":703,"_type":32,"title":704,"_source":34,"_file":705,"_stem":706,"_extension":37},"/shared/de-de/next-steps","Stelle jetzt bessere Software schneller bereit","Mehr als 50 % der Fortune-100-Unternehmen vertrauen GitLab","Erlebe, was dein Team mit der intelligenten\n\n\nDevSecOps-Plattform erreichen kann.\n",{"text":48,"config":698},{"href":699,"dataGaName":51,"dataGaLocation":700},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":53,"config":702},{"href":55,"dataGaName":56,"dataGaLocation":700},"content:shared:de-de:next-steps.yml","Next Steps","shared/de-de/next-steps.yml","shared/de-de/next-steps",1759347775450]