{"id":26965,"date":"2025-02-28T14:34:25","date_gmt":"2025-02-28T13:34:25","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=26965"},"modified":"2025-02-28T14:34:26","modified_gmt":"2025-02-28T13:34:26","slug":"wie-baut-man-eine-ci-cd-pipeline-mit-jenkins-auf","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/28\/wie-baut-man-eine-ci-cd-pipeline-mit-jenkins-auf\/","title":{"rendered":"Wie baut man eine CI\/CD Pipeline mit Jenkins auf?"},"content":{"rendered":"<div class=\"wp-block-post-excerpt\"><p class=\"wp-block-post-excerpt__excerpt\">Im Rahmen der Vorlesung &#8220;System Engineering und Management (143101a)&#8221; haben wir es uns zum Ziel gesetzt, mehr \u00fcber CI\/CD Pipelines zu lernen und eine eigene Pipeline f\u00fcr ein kleines Projekt aufzusetzen. Wir haben uns dabei entschieden, Jenkins f\u00fcr die CI\/CD Pipeline einzusetzen und eine kleine ToDo App mit dem Framework Flutter zu entwickeln. Im Verlauf des Projektes sind wir dabei auf unterschiedliche Probleme gesto\u00dfen, die es zu beseitigen galt. <\/p><\/div>\n\n\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2099\" height=\"652\" data-attachment-id=\"27438\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/28\/wie-baut-man-eine-ci-cd-pipeline-mit-jenkins-auf\/todo-list-cicd-2\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1.png\" data-orig-size=\"2099,652\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ToDo List CICD\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-1024x318.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1.png\" alt=\"\" class=\"wp-image-27438\" style=\"width:769px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1.png 2099w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-300x93.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-1024x318.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-768x239.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-1536x477.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/ToDo-List-CICD-1-2048x636.png 2048w\" sizes=\"auto, (max-width: 2099px) 100vw, 2099px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Jenkins<\/h2>\n\n\n\n<p>Jenkins ist ein Open-Source-Tool f\u00fcr Continuous Integration (CI), Continuous Delivery\/Deployment (CD), Automatisierung und DevOps. Mit Jenkins lassen sich CI\/CD-Pipelines erstellen, um Software-Builds, Tests und Deployments zu automatisieren. Als flexibler Automatisierungsserver kann Jenkins sowohl als einfacher CI-Server als auch als umfassender Continuous-Delivery-Hub eingesetzt werden. Durch eine Vielzahl an Plugins l\u00e4sst sich Jenkins in weitere Tools der kontinuierlichen Integration und Bereitstellung integrieren.<\/p>\n\n\n\n<p>Weitere Informationen zu Jenkins k\u00f6nnen unter <a href=\"https:\/\/www.jenkins.io\/\" target=\"_blank\" rel=\"noopener\" title=\"\">https:\/\/www.jenkins.io\/<\/a> gefunden werden<a href=\"https:\/\/www.jenkins.io\/\" target=\"_blank\" rel=\"noopener\" title=\"\">.<\/a><\/p>\n\n\n\n<p>CI\/CD ist eine Praktik in der Softwareentwicklung, um Prozesse zu automatisieren und Software schneller bereitzustellen. Bei Continuous Integration werden Code\u00e4nderungen automatisch und regelm\u00e4\u00dfig in ein gemeinsames Repository integriert. Continuous Delivery hei\u00dft, dass die Anwendung jederzeit bereit f\u00fcr die Bereitstellung ist. Zusammen werden diese Praktiken als CI\/CD-Pipeline bezeichnet. <\/p>\n\n\n\n<p>Wer mehr zum Thema CI\/CD lesen m\u00f6chte, kann sich den Artikel <a href=\"https:\/\/martinfowler.com\/articles\/continuousIntegration.html\" target=\"_blank\" rel=\"noopener\" title=\"\">Continuous Integration<\/a> von Martin Fowler anschauen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"warum-jenkins\">Warum Jenkins?<\/h3>\n\n\n\n<p>Wir haben uns f\u00fcr Jenkins entschieden, da es ein weit verbreitetes Open-Source-Tool und damit kostenlos ist. Ein gro\u00dfer Vorteil ist die ausf\u00fchrliche Dokumentation, die sowohl f\u00fcr uns als Einsteiger als auch f\u00fcr die Integration in GitLab hilfreich ist \u2013 ein essenzielles Kriterium, da unser Projekt auf GitLab liegt. Durch Plugins l\u00e4sst sich Jenkins zudem flexibel erweitern und unterst\u00fctzt die Integration der g\u00e4ngigen DevOps und Cloud-Anbieter, einschlie\u00dflich GitLab.&nbsp;Zudem ist Jenkins plattformunabh\u00e4ngig und l\u00e4uft auf Windows, macOS und Linux.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Installation und Ersteinrichtung<\/h3>\n\n\n\n<p>F\u00fcr den Betrieb von Jenkins wurde eine Virtuelle Maschine (VM) mit dem Betriebssystem Debian gew\u00e4hlt. Die Installation von Jenkins ist \u00fcber ein extra APT Repository m\u00f6glich. Eine Anleitung zur Installation und Ersteinrichtung findet sich in der <a href=\"https:\/\/www.jenkins.io\/doc\/book\/installing\/linux\/#debianubuntu\" target=\"_blank\" rel=\"noopener\" title=\"\">Dokumentation von Jenkins<\/a>. Um einen sicheren Zugriff auf Jenkins \u00fcber das \u00f6ffentliche Internet mittels HTTPS&nbsp;zu erm\u00f6glichen, wurde <a href=\"https:\/\/nginx.org\/\" target=\"_blank\" rel=\"noopener\" title=\"\">nginx<\/a> als Reverse Proxy konfiguriert und ein Zertifikat von Let\u2019s Encrypt verwendet. Das Anfordern der Zertifikate und die Konfiguration von nginx f\u00fcr HTTPS wurde dabei von dem Tool <a href=\"https:\/\/certbot.eff.org\/\" target=\"_blank\" rel=\"noopener\" title=\"\">certbot<\/a> \u00fcbernommen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pipelines in Jenkins<\/h3>\n\n\n\n<p>Jenkins bietet verschiedene Typen und M\u00f6glichkeiten eine Pipeline zu erstellen. Wir haben f\u00fcr unser Projekt den in Jenkins als &#8220;Pipeline&#8221; bezeichneten Typ gew\u00e4hlt. Bei diesem wird die Pipeline durch eine Datei in unserem Git Repository, einer sogenannten &#8220;Jenkinsfile&#8221;, definiert. Hierf\u00fcr werden zwei Syntax Varianten, Declarative und Scripted, von Jenkins angeboten. Wir haben uns f\u00fcr die Variante Declarative entschieden.<\/p>\n\n\n\n<p>Eine solche Pipeline besteht aus Stages, die nacheinander abgearbeitet werden. In diesen Stages k\u00f6nnen sogenannte Steps verwendet werden, um z.B. Kommandozeilenbefehle auszuf\u00fchren. <\/p>\n\n\n\n<p>Das folgende Beispiel gibt den Text &#8220;Hello World&#8221; aus:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\" style=\"padding-right:0;padding-left:0\"><pre><code class=\"language-groovy\" data-line=\"\">pipeline {\n\tagent any\n\n\tstages {\n    \tstage(&#039;Hello&#039;) {\n        \tsteps {\n            \techo &#039;Hello World&#039;\n        \t}\n    \t}\n\t}\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Erstellen einer ersten Pipeline<\/h3>\n\n\n\n<p>Wie bereits erw\u00e4hnt, sollte unsere Pipeline in Form einer Jenkinsfile in unserem Projekt Repository definiert sein. Damit Jenkins von GitLab immer dann benachrichtigt wird, wenn eine Pipeline gestartet werden soll, haben wir in GitLab die Jenkins Integration aktiviert. Damit diese auch in Jenkins korrekt funktioniert, mussten wir das <a href=\"https:\/\/plugins.jenkins.io\/gitlab-plugin\/\">GitLab Plugin<\/a> f\u00fcr Jenkins installieren.\u00a0<\/p>\n\n\n\n<p>Nach der Konfiguration der Integration sowohl in GitLab als auch Jenkins, haben wir eine Jenkinsfile angelegt und eine Pipeline definiert, um die Integration zu testen. Diese gibt nur eine kurze Textnachricht aus und \u00e4ndert am Ende den Status der Pipeline im GitLab UI.\u00a0<\/p>\n\n\n\n<p>Die Jenkinsfile sah zu diesem Zeitpunkt wie folgt aus:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">pipeline {\n    agent any\n\n    stages {\n        stage(&#039;Build&#039;) {\n            steps {\n                echo &#039;Jenkinsfile Test&#039;\n            }\n        }\n\n        \/\/ Needed to update the GitLab Pipeline UI \/ Status\n        stage(&#039;notify-gitlab&#039;) {\n            steps {\n                echo &#039;Notify GitLab&#039;\n                updateGitlabCommitStatus name: &#039;build&#039;, state: &#039;pending&#039;\n                updateGitlabCommitStatus name: &#039;build&#039;, state: &#039;success&#039;\n            }\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Bauen des Projektes<\/h3>\n\n\n\n<p>Nachdem wir die GitLab Integration von Jenkins eingerichtet hatten, war der n\u00e4chste Schritt eine Pipeline zu erstellen, mit welcher unsere ToDo App gebaut und theoretisch deployed werden k\u00f6nnte. Um hier nicht direkt auf der VM die notwendigen SDKs und Tools installieren zu m\u00fcssen, haben wir uns dazu entschieden, dass wir Docker verwenden wollen, um einer Pipeline Stage eine bestimmte Umgebung bereitzustellen.<\/p>\n\n\n\n<p>F\u00fcr Jenkins gibt es ein Plugin, welches es erm\u00f6glicht, Docker zu verwenden. Beim Testen dieses Plugins trat allerdings schon das erste Problem auf. In Kombination mit Rootless Docker funktioniert das Plugin nicht korrekt. Abgesehen davon hatte das Plugin zu diesem Zeitpunkt schon seit Monaten keine Updates mehr erhalten. Wir haben uns daher entschieden, Docker &#8220;manuell&#8221; durch die direkte Nutzung des CLI zu verwenden. Um das Ganze m\u00f6glichst einfach zu machen, haben wir die einzelnen Befehle, die notwendig f\u00fcr den Build des Projektes sind, in ein eigenes Shell-Skript ausgelagert.<\/p>\n\n\n\n<p>Wir haben die Build Stage entsprechend angepasst, sodass diese zun\u00e4chst das Build-Skript ausf\u00fchrt und das Ergebnis in Jenkins als Artefakt speichert. Das verwendete Docker Image wird \u00fcber eine Umgebungsvariable definiert.<\/p>\n\n\n\n<p>Damit sieht unsere neue Build Stage &#8220;Build Web App&#8221; wie folgt aus:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">stage(&#039;Build Web App&#039;) {\n    environment {\n        DOCKER_IMAGE = &#039;ghcr.io\/cirruslabs\/flutter:3.24.5&#039;\n    }\n    steps {\n        \/\/ Build Web App\n        sh &#039;chmod 740 build.sh&#039; \/\/ Make sure the build script is executable\n        sh &#039;docker run --rm -v .:\/build --workdir \/build $DOCKER_IMAGE .\/build.sh web&#039;\n\n        \/\/ Archive build artifact\n        sh &#039;zip -r build\/web.zip build\/web&#039;\n        archiveArtifacts artifacts: &#039;build\/web.zip&#039;, fingerprint: true, onlyIfSuccessful: true\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Deployment der Web App<\/h3>\n\n\n\n<p>Um die Web App zu deployen, haben wir die Pipeline um eine Deployment Stage erweitert. Diese verwendet das in der Build Stage erzeugte Artefakt und deployed dieses \u00fcber SSH auf eine weitere VM mit einem einfachen Web Server. Zur einfacheren Verwendung von SSH in der Pipeline haben wir das Plugin <a href=\"https:\/\/plugins.jenkins.io\/ssh-agent\/\">SSH Agent<\/a> verwendet. Dieses erlaubt es, Credentials f\u00fcr die Verbindung mit der VM via SSH in Jenkins zu hinterlegen und in der Pipeline direkt auf diese Credentials zuzugreifen. Die Befehle f\u00fcr das eigentliche Deployment haben wir ebenfalls in ein Skript ausgelagert. Dieses kopiert lediglich das ZIP-Archiv auf die VM und entpackt dieses dort in den Root Ordner des Web Servers.<\/p>\n\n\n\n<p>Die Deployment Stage sieht zu diesem Zeitpunkt wie folgt aus:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">stage(&#039;Deploy Web App to DEV&#039;) {\n    steps {\n        \/\/ Get build artifact for deployment\n        unarchive mapping: [&#039;build\/web.zip&#039;: &#039;build\/web.zip&#039;]\n\n        \/\/ Deploy Web App via SSH\n        sshagent([&#039;todo-list-cicd-dev&#039;]) {\n            sh &#039;chmod 740 deploy.sh&#039; \/\/ Make sure the deploy script is executable\n            sh &#039;.\/deploy.sh dev&#039;\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pipeline-status-problem\">Das Pipeline Status Problem<\/h3>\n\n\n\n<p>Mit den zwei obenstehenden Stages haben wir eine grundlegende Version unserer Pipeline erfolgreich aufgesetzt. Allerdings mussten wir hier feststellen, dass mit dieser Konfiguration in der Jenkinsfile der Status der Pipeline im GitLab UI nicht korrekt aktualisiert wurde. Dabei konnte es z.B. vorkommen, dass GitLab gar keinen Status erhalten hat, wenn es zu einem Fehler w\u00e4hrend der Ausf\u00fchrung einer Pipeline Stage kam. Dies hat zur Folge, dass GitLab Merge Requests den Status nicht kennen und dieser nicht mehr als Anforderung f\u00fcr einen Merge verwendet werden kann.&nbsp;<\/p>\n\n\n\n<p>Ein Blick in die Dokumentation des GitLab Plugins hat uns dabei gezeigt, dass sich dieses Problem relativ einfach l\u00f6sen l\u00e4sst. Das GitLab Plugin sendet zwar nicht ohne weiteres Statusupdates, es ist aber m\u00f6glich, durch bereitgestellte Pipeline Steps, die Pipeline so zu konfigurieren, dass ein automatisches Update gesendet wird. Um f\u00fcr ein automatisches Statusupdate zu sorgen, k\u00f6nnen die Steps einer Stage innerhalb des Steps \u201cgitlabCommitStatus\u201d platziert werden. Au\u00dferdem kann mit dem Step \u201cupdateGitlabCommitStatus\u201d der Status manuell angepasst werden.<\/p>\n\n\n\n<p>Neben dem eigentlichen Problem konnten wir auch die Darstellung unserer Pipeline im GitLab UI verbessern. Urspr\u00fcnglich wurde diese in GitLab nur als ein einziger CI\/CD Job dargestellt. Mit der Option \u201cgitlabBuilds\u201d lassen sich aber beliebig viele Stages\/Jobs im GitLab UI anzeigen.<\/p>\n\n\n\n<p>Mit diesen neuen Erkenntnissen haben wir die bisherige Pipeline angepasst. Wie unten zu sehen ist, haben wir jeweils einen Job f\u00fcr unsere beiden Stages definiert sowie in den beiden Stages den Step \u201cgitlabCommitStatus\u201d f\u00fcr die automatische Aktualisierung eingebaut.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">\/\/ Define pipeline stages to ensure name consistency\ndef buildWebApp = &#039;Build Web App&#039;\ndef deployWebAppDev = &#039;Deploy Web App to DEV&#039;\ndef builds = [buildWebApp, deployWebAppDev]\n\n\/\/ Actual pipeline definition\npipeline {\n    agent any\n\n    options {\n        gitlabBuilds(builds: builds)\n    }\n\n    stages {\n        \/\/ Build Web App\n        stage(buildWebApp) {\n            environment {\n                DOCKER_IMAGE = &#039;ghcr.io\/cirruslabs\/flutter:3.24.5&#039;\n            }\n            steps {\n                gitlabCommitStatus(buildWebApp) {\n                    - snip -\n                }\n            }\n        }\n\n        \/\/ Deploy Web App\n        stage(deployWebAppDev) {\n            steps {\n                gitlabCommitStatus(deployWebAppDev) {\n                    - snip -\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Konflikte beim Deployment<\/h3>\n\n\n\n<p>Neben dem obenstehenden Problem traten auch Fehler beim Deployment auf. Bei diesen sind sich zwei oder mehr parallel laufende Pipelines beim Deployment in die Quere gekommen. Dazu kam es, da unsere Deployment Stage immer ausgef\u00fchrt wurde, unabh\u00e4ngig von dem Git Branch, auf den gepusht wurde. F\u00fcr dieses Problem hatten wir zwei m\u00f6gliche L\u00f6sungsans\u00e4tze. Wir konnten<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>die Deployment Stage so konfigurieren, dass diese manuell gestartet werden muss <strong><em>oder<\/em><\/strong><\/li>\n\n\n\n<li>die Deployment Stage nur f\u00fcr einen bestimmten Branch (dem main Branch) ausf\u00fchren.<\/li>\n<\/ol>\n\n\n\n<p>Wir haben uns f\u00fcr die zweite Option entschieden, da die VM, auf die wir deployen, vom Staging Konzept her eine DEV Umgebung sein soll. Wir haben daf\u00fcr eine entsprechende Bedingung f\u00fcr die Ausf\u00fchrung in der Jenkinsfile hinterlegt.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">\/\/ Deploy Web App\nstage(deployWebAppDev) {\n    when {\n        expression { env.gitlabSourceBranch == &#039;main&#039; }\n    }\n    steps {\n        gitlabCommitStatus(deployWebAppDev) {\n            - snip -\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Mit dieser Anpassung der Deployment Stage kam es allerdings zu einem neuen Problem mit dem GitLab Pipeline Status. Im Fall, dass die Stage \u00fcbersprungen wird, wird der Status in GitLab nicht auf \u201cskipped\u201d gesetzt. Um das zu verhindern, haben wir einen Workaround eingebaut. Dieser besteht darin, f\u00fcr alle Stages, die \u00fcbersprungen werden k\u00f6nnten, zu Beginn der Pipeline den Status dieser Stages in GitLab auf \u201cskipped\u201d zu setzen. Wird eine der Stages doch ausgef\u00fchrt, wird der Status automatisch durch den Step \u201cgitlabCommitStatus\u201d aktualisiert.<\/p>\n\n\n\n<p>Diese Stage f\u00fcr den Workaround sieht wie folgt aus:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">\/\/ Mark manually triggered stages as skipped\nstage(&#039;Mark manual stages as skipped&#039;) {\n    steps {\n        updateGitlabCommitStatus name: deployWebAppDev, state: &#039;skipped&#039;\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ungel\u00f6stes-problem\">Ein ungel\u00f6stes Problem<\/h3>\n\n\n\n<p>Abgesehen von den beiden obigen Problemen trat ein weiteres auf, als wir versucht haben, eine der Pipelines bzw. eine Stage \u00fcber das Jenkins UI neu zu starten. Dabei mussten wir feststellen, dass in der Pipeline nur wenige Sekunden nach dem Neustart ein Fehler auftritt. Zu dem Fehler kommt es, da Jenkins die Information, welchen Branch oder Commit es verwenden muss, nicht abspeichert. Diese Informationen liefert GitLab an Jenkins, wenn eine Pipeline \u00fcber GitLab gestartet wird.<\/p>\n\n\n\n<p>Wir haben uns nur relativ kurz mit dem Problem auseinandergesetzt und auch keine schnelle L\u00f6sung gefunden. Da uns dieser Fehler nicht wirklich bei unserem Ziel, eine Pipeline aufzubauen, behindert hat, haben wir uns auch nicht weiter damit befasst. F\u00fcr den Einsatz in einem realen Projekt m\u00fcsste dieses Problem wahrscheinlich gel\u00f6st werden, da es durchaus dazu kommen kann, dass man eine Pipeline aufgrund eines tempor\u00e4ren Fehlers neu starten m\u00f6chte.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Testen der App<\/h3>\n\n\n\n<p>Um Tests zu automatisieren, haben wir eine Test Stage eingebaut. Daf\u00fcr wurden die Befehle wieder in ein Shell-Skript ausgelagert. In der Pipeline wird das Test-Skript innerhalb eines Docker-Containers ausgef\u00fchrt, indem die Tests mit &#8211;coverage f\u00fcr die Testabdeckung ausgef\u00fchrt werden. Die JSON-Testdaten werden mithilfe des Tools junitreport in das JUnit XML-Format konvertiert, damit diese sp\u00e4ter bei der Status \u00dcbersicht der Pipeline in Jenkins angezeigt werden k\u00f6nnen.&nbsp;Um die Code Coverage der Tests visuell darstellen zu k\u00f6nnen, haben wir das <a href=\"https:\/\/plugins.jenkins.io\/coverage\/\">Coverage Plugin<\/a> von Jenkins verwendet. Die Coverage-Daten der Tests (LCOV-Coveragedatei) werden ins XML-Format umgewandelt, damit diese in Jenkins dargestellt werden k\u00f6nnen. Die Testresultate werden archiviert und der Codeabdeckungsbericht visuell auf Jenkins dargestellt.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">\/\/ Test\nstage(&#039;Test Web App&#039;) {\n    steps {\n        gitlabCommitStatus(testWebApp) {\n            sh &#039;chmod 740 test.sh&#039;\n            sh &#039;docker run --rm -v .:\/build --workdir \/build $FLUTTER_IMAGE .\/test.sh&#039;\n        }\n    }\n    post {\n        always {\n            \/\/ Archive test results for Jenkins\n            archiveArtifacts artifacts: &#039;target\/test-results.xml&#039;, fingerprint: true\n\n            \/\/ Publish coverage report in Jenkins\n            recordCoverage(tools: [[parser: &#039;COBERTURA&#039;, pattern: &#039;target\/coverage.xml&#039;]])\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Statische Code Analyse&nbsp;<\/h3>\n\n\n\n<p>Als n\u00e4chste Stage haben wir eine Linting Stage zur statischen Code Analyse implementiert, welche jedoch an den Anfang der Pipeline gesetzt wird, damit kein Build Prozess stattfindet, wenn das Linting fehlschl\u00e4gt. Die eigentliche Codeanalyse ist in das Shell-Skript lint.sh ausgelagert, welches das Dart-Code-Metrics-Tool aktiviert. Dieses \u00fcberpr\u00fcft den Code auf Verst\u00f6\u00dfe gegen Linting-Regeln und Best Practices. W\u00e4hrend des Linting-Prozesses werden Warnungen und Fehler gespeichert. Das Ergebnis kann in Jenkins eingesehen werden. Die Stage schl\u00e4gt nur dann fehl, wenn Fehler auftreten \u2013 bei Warnungen wird der Prozess fortgesetzt.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-groovy\" data-line=\"\">\/\/ Linting Stage\nstage(&#039;Dart Analyze&#039;) {\n    steps {\n        gitlabCommitStatus(lintWebApp) {\n            sh &#039;chmod 740 lint.sh&#039;\n            sh &#039;docker run --rm -v .:\/build --workdir \/build $FLUTTER_IMAGE .\/lint.sh&#039;\n        }\n    }\n    post {\n        always {\n            archiveArtifacts artifacts: &#039;lint-results.txt&#039;, fingerprint: true\n        }\n    }\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Alternativen zu Jenkins<\/h2>\n\n\n\n<p>Wie oben im Abschnitt <a href=\"#warum-jenkins\" title=\"Warum Jenkins?\">Warum Jenkins?<\/a> beschrieben, hatten wir uns zwar f\u00fcr Jenkins entschieden, waren aber im Verlauf des Projektes bei der Integration von Jenkins in GitLab doch auf ein paar Schwierigkeiten gesto\u00dfen. Gibt es eine Alternative, die hier besser gewesen w\u00e4re? Das l\u00e4sst sich pauschal zwar nicht sagen, aber es gibt definitiv sehr viele CI\/CD-Tools, sowohl kommerzielle als auch kostenfreie Tools. Zu diesen geh\u00f6ren unter anderem Travis CI, CircleCI, ArgoCD sowie GitHub Actions und GitLab CI\/CD.&nbsp;<\/p>\n\n\n\n<p>Bei letzterem l\u00e4sst sich zumindest vermuten, dass die Integration in GitLab wohl am besten sein wird, da es sich um einen festen Bestandteil von GitLab handelt. Um herauszufinden, ob wir mit GitLab CI\/CD besser unterwegs gewesen w\u00e4ren, m\u00fcsste man unsere Pipeline zu GitLab CI\/CD migrieren. Das w\u00e4re vermutlich relativ einfach, da die eigentliche Logik aller Stages der Jenkins Pipeline in Shell-Skripte ausgelagert ist.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Migration zu GitLab CI\/CD<\/h3>\n\n\n\n<p>Und daher haben wir auch genau das getan und dieselbe Pipeline mit GitLab CI\/CD aufgesetzt. Wie zuvor vermutet, war dies auch tats\u00e4chlich relativ einfach. Abgesehen davon, dass wir die Pipeline in eine andere Syntax (YAML) \u00fcbertragen mussten, waren fast keine Anpassungen notwendig. Selbst die Verwendung von JUnit und Cobertura wird von GitLab CI\/CD von Haus aus unterst\u00fctzt. Wir mussten also nicht einmal das Shell-Skript f\u00fcr das Testing anpassen. Wir konnten daher alle unsere Shell-Skripte ohne Ver\u00e4nderung wiederverwenden.<\/p>\n\n\n\n<p>Lediglich bei der Migration der Deployment Stage zu GitLab CI\/CD mussten wir uns \u00fcberlegen, wie wir den f\u00fcr den Zugang zu der VM notwendigen SSH Key bereitstellen k\u00f6nnen. Dazu konnten wir allerdings ganz einfach GitLab CI\/CD Variablen verwenden und vor der Ausf\u00fchrung des Deployments den SSH Agent manuell vorbereiten. W\u00e4hrend diese Variablen zwar nicht das sicherste sind, um einen Private Key in GitLab zu hinterlegen, war es f\u00fcr unsere Zwecke aber ausreichend. <\/p>\n\n\n\n<p>Wie im Abschnitt <a href=\"#ungel\u00f6stes-problem\" title=\"\">Ein ungel\u00f6stes Problem<\/a> beschrieben, konnten wir die Pipeline oder einzelne Stages einer Pipeline in Jenkins nicht neu starten und damit auch nicht als nur manuell startbar hinterlegen. Dies ist bei GitLab CI\/CD nicht der Fall, weshalb wir den Deployment Job hier so konfiguriert haben, dass dieser zus\u00e4tzlich zur Beschr\u00e4nkung auf den &#8220;main&#8221; Branch noch manuell gestartet werden muss.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><p>Folgender Ausschnitt der <code class=\"\" data-line=\"\">.gitlab-ci.yml<\/code> Datei zeigt den Test und den Deployment Job:<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-yaml\" data-line=\"\">image: ghcr.io\/cirruslabs\/flutter:3.24.5\n\nstages:\n  - lint\n  - build\n  - test\n  - deploy\n\nvariables:\n  GIT_DEPTH: 3\n\n# Linting Stage\nDart Analyze (GitLab):\n- snip -\n\n# Build Web App\nBuild Web App (GitLab):\n- snip -\n\n# Test Web App\nTest Web App (GitLab):\n  stage: test\n  script:\n    - chmod 740 test.sh\n    - .\/test.sh\n  artifacts:\n    paths:\n      - target\/test-results.xml\n      - target\/coverage.xml\n      - coverage\/lcov.info\n    reports:\n      junit: target\/test-results.xml\n      coverage_report:\n        coverage_format: cobertura\n        path: target\/coverage.xml\n  needs:\n    - job: Build Web App (GitLab)\n      artifacts: true\n  when: on_success\n\n# Deploy Web App to DEV\nDeploy Web App to DEV (GitLab):\n  stage: deploy\n  image: alpine:latest\n  before_script:\n    # Install SSH and necessary tools\n    - apk add --no-cache openssh-client bash\n    # Setup SSH agent with private key\n    - eval $(ssh-agent -s)\n    - echo &quot;$SSH_PRIVATE_KEY&quot; | tr -d &#039;\\r&#039; | ssh-add -\n    # Create SSH directory and add known hosts\n    - mkdir -p ~\/.ssh\n    - chmod 700 ~\/.ssh\n    - echo &quot;$SSH_KNOWN_HOSTS&quot; &amp;gt; ~\/.ssh\/known_hosts\n    - chmod 644 ~\/.ssh\/known_hosts\n  script:\n    - chmod 740 deploy.sh\n    - .\/deploy.sh dev\n  needs:\n    - job: Build Web App (GitLab)\n      artifacts: true\n    - job: Test Web App (GitLab)\n      artifacts: false\n  when: manual\n  rules:\n    - if: $CI_COMMIT_BRANCH == &quot;main&quot;\n<\/code><\/pre>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Ausblick<\/h2>\n\n\n\n<p>Die Pipeline kann je nach Bedarf durch weitere Stages erweitert werden. Sowohl bei Jenkins als auch bei GitLab CI\/CD muss dazu nicht die gesamte Konfigurationsdatei ge\u00e4ndert werden. Es muss lediglich eine neue Stage bzw. Job und gegebenenfalls ein dazugeh\u00f6riges Shell-Skript hinzugef\u00fcgt werden.<\/p>\n\n\n\n<p>Die wohl offensichtlichste Erweiterungsm\u00f6glichkeit besteht wahrscheinlich darin, Stages f\u00fcr das Bauen des Projektes f\u00fcr die anderen von Flutter unterst\u00fctzten Plattformen Windows, Linux, MacOS, iOS und Android zu erstellen. Zu diesen kann dann gegebenenfalls auch eine Delivery Stage eingebaut werden, die die Software auf irgendeine Weise verf\u00fcgbar macht.<\/p>\n\n\n\n<p>Weitere m\u00f6gliche Stages w\u00e4ren zum Beispiel der OWASP Dependency-Check zur \u00dcberpr\u00fcfung der Dependencies des Projektes auf Schwachstellen. Auch w\u00e4re es denkbar, einen Vulnerability Scanner einzusetzen, um automatisiert Schwachstellen im Projekt entdecken zu k\u00f6nnen. F\u00fcr ein leichteres Deployment k\u00f6nnte au\u00dferdem ein Docker Image f\u00fcr die Web App gebaut werden. Falls notwendig, kann zudem in einer Pipeline eine Software Bill of Material (SBOM) generiert werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned und Fazit<\/h2>\n\n\n\n<p>Im Laufe des Projekts haben wir gelernt, wie ein Jenkins Server konfiguriert und eine Pipeline in Jenkins aufgesetzt wird. Zudem konnten wir Erfahrungen mit der Integration von Jenkins in GitLab sowie der Erstellung von Pipeline-Skripten sammeln. W\u00e4hrend der Entwicklung der Flutter-App haben wir zudem die Vorteile einer Pipeline kennengelernt. Durch die Automatisierung von Tests und anderen Prozessen erm\u00f6glicht eine Pipeline eine effizientere Arbeitsweise. Dar\u00fcber hinaus sorgt beispielsweise die Linting-Stage f\u00fcr sauberen und konsistenten Code. <\/p>\n\n\n\n<p>Eine CI\/CD Pipeline kann, wenn sie richtig konfiguriert und eingesetzt wird, zur fr\u00fchzeitigen Fehlererkennung und -behebung beitragen und Fehler k\u00f6nnen verhindert werden. Zum Beispiel durch das Einbinden der Pipeline in Merge Requests, um die Pipeline als Kriterium f\u00fcr einen Merge zu verwenden. Somit ist f\u00fcr eine h\u00f6here Codequalit\u00e4t gesorgt. Au\u00dferdem ist Konsistenz garantiert, indem Deployments immer in derselben Umgebung erfolgen. Es ist jedoch entscheidend, die passende Pipeline-Umgebung auszuw\u00e4hlen sowie die Pipeline sauber zu bauen und strukturiert einzusetzen.&nbsp;<\/p>\n\n\n\n<p>Jenkins ist ein m\u00e4chtiges Tool, mit dem wir unsere CI\/CD Pipeline erfolgreich umsetzen konnten \u2013 allerdings nicht ohne Herausforderungen. Insbesondere die Integration in GitLab war sehr (zeit-)aufwendig. F\u00fcr ein kleines Projekt wie unsere Flutter App w\u00e4re GitLab CI\/CD eine gute L\u00f6sung und v\u00f6llig ausreichend gewesen \u2013 <a href=\"#pipeline-status-problem\" title=\"\">das Pipeline Status Problem<\/a> h\u00e4tte uns so beispielsweise keine Zeit gekostet.&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Im Rahmen der Vorlesung &#8220;System Engineering und Management (143101a)&#8221; haben wir es uns zum Ziel gesetzt, mehr \u00fcber CI\/CD Pipelines zu lernen und eine eigene Pipeline f\u00fcr ein kleines Projekt aufzusetzen. Wir haben uns dabei entschieden, Jenkins f\u00fcr die CI\/CD Pipeline einzusetzen und eine kleine ToDo App mit dem Framework Flutter zu entwickeln. Im Verlauf des Projektes sind wir dabei auf unterschiedliche Probleme gesto\u00dfen, die es zu beseitigen galt.<\/p>\n","protected":false},"author":1247,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[2],"tags":[144,150,9,145,78,1019,424],"ppma_author":[1083,1070],"class_list":["post-26965","post","type-post","status-publish","format-standard","hentry","category-system-engineering","tag-ci-pipeline","tag-ci-cd","tag-devops","tag-gitlab-ci","tag-jenkins","tag-system-engineering","tag-system-engineering-and-management"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":382,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/02\/21\/wrap-up-seeing-the-bigger-picture-part-5\/","url_meta":{"origin":26965,"position":0},"title":"Jenkbird &#8211; Wrap up &#038; seeing the bigger picture &#8211; Part 5","author":"J\u00f6rg Einfeldt","date":"21. February 2016","format":false,"excerpt":"Kchhhhhh, Kchhhhhh, Kchhhhhhh... - Ernie on companies without CI Hello internet and welcome to the last part of our\u00a0tutorial series about\u00a0Continuous Integration, Code Deployment and Automated Testing with Jenkins. If you arrived at this post and have read all the others we are very proud of you, hope you enjoyed\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"bert","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/02\/bert-186x300.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":79,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2015\/12\/29\/sesame-deployment-street-continuous-integration-with-jenkins-tutorial-part-1\/","url_meta":{"origin":26965,"position":1},"title":"Jenkbird \u2013 Continuous Integration with Jenkins Tutorial \u2013 Part 1","author":"Marc Stauffer","date":"29. December 2015","format":false,"excerpt":"Bad days happen to everyone, but when one happens to you, just keep doing your best and never let a bad day make you feel bad about yourself.\u201d \u2014 Big Bird about\u00a0a day with deployment problems \"Continuous Integration\" (CI) and \"Continuous Delivery\" (CD) are topics every modern software developer should\u2026","rel":"","context":"In &quot;DevOps&quot;","block_context":{"text":"DevOps","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/devops\/"},"img":{"alt_text":"Jenkbird","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2015\/12\/Yhfrwka16hgJOdDTrwFaVPWfBhzA72MWv9qwzj-tvdA-217x300.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":3496,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/03\/30\/ci-cd-with-gitlab-ci-for-a-web-application-part-1\/","url_meta":{"origin":26965,"position":2},"title":"CI\/CD with GitLab CI for a web application &#8211; Part 1","author":"Nina Schaaf","date":"30. March 2018","format":false,"excerpt":"Introduction When it comes to software development, chances are high that you're not doing this on your own. The main reason for this is often that implementing components like UI, frontend, backend, servers and more is just too much to handle for a single person leading to a slow development\u2026","rel":"","context":"In &quot;DevOps&quot;","block_context":{"text":"DevOps","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/devops\/"},"img":{"alt_text":"Shaky architecture","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/01_shaky-architecture-300x106.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":12032,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2020\/09\/30\/admin-panel-web-app-in-der-aws-cloud\/","url_meta":{"origin":26965,"position":3},"title":"Admin Panel (Web App) in der AWS Cloud","author":"ss447","date":"30. September 2020","format":false,"excerpt":"1. Einleitung Im Rahmen der Vorlesung \u201eSoftware Development for Cloud Computing\u201c haben wir uns als Gruppe dazu entschieden aufbauend auf teilweise bereits vorhandener Codebasis an einem Startup-Projekt weiterzuarbeiten. Der Hauptfokus lag bei uns auf dem Ausbau von DevOps-Aspekten und auf dem eines stabilen und sicheren Systems, welches auch in der\u2026","rel":"","context":"In &quot;Cloud Technologies&quot;","block_context":{"text":"Cloud Technologies","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/cloud-technologies\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/img.youtube.com\/vi\/qw9ZkWnvR4M\/0.jpg?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":21683,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/09\/18\/ynstagram-cloud-computing-mit-aws-serverless\/","url_meta":{"origin":26965,"position":4},"title":"Ynstagram &#8211; Cloud Computing mit AWS &amp; Serverless","author":"ns144","date":"18. September 2021","format":false,"excerpt":"Im Rahmen der Vorlesung \u201cSoftware Development for Cloud Computing\u201d haben wir uns hinsichtlich des dortigen Semesterprojektes zum Ziel gesetzt einen einfachen Instagram Klon zu entwerfen um uns die Grundkenntnisse des Cloud Computings anzueignen. Grundkonzeption \/ Ziele des Projektes Da wir bereits einige Erfahrung mit React aufgrund anderer studentischer Projekte sammeln\u2026","rel":"","context":"In &quot;Cloud Technologies&quot;","block_context":{"text":"Cloud Technologies","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/cloud-technologies\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Prasentation_CC_01.png?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":3421,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/03\/28\/take-me-home-project-overview\/","url_meta":{"origin":26965,"position":5},"title":"Take Me Home &#8211; Project Overview","author":"cp054","date":"28. March 2018","format":false,"excerpt":"Related articles:\u00a0\u25baCI\/CD infrastructure: Choosing and setting up a server with Jenkins as Docker image\u00a0\u25baDockerizing Android SDK and Emulator for testing\u00a0 \u25baAutomated Unit- and GUI-Testing for Android in Jenkins\u00a0 \u25baTesting a MongoDB with NodeJS, Mocha and Mongoose During the winter term 2017\/2018, we created an app called Take Me Home. The\u2026","rel":"","context":"In &quot;Mobile Apps&quot;","block_context":{"text":"Mobile Apps","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/interactive-media\/mobile-apps\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/03\/tmh_admin_usermanagement_bearbeitet.png?resize=1400%2C800&ssl=1 4x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":1083,"user_id":1247,"is_guest":0,"slug":"cedric_gottschalk","display_name":"Cedric Gottschalk","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/472d26ded7606fb8380c1f434241072814d874082d94097668be7def3acbce3b?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""},{"term_id":1070,"user_id":1108,"is_guest":0,"slug":"ronja_brauchle","display_name":"Ronja Brauchle","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/794f5c7f2e46a96e9b18c1fd47e7b9b15e4b6fbaccdd46b4bebd0d90bd827bdd?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26965","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1247"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=26965"}],"version-history":[{"count":50,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26965\/revisions"}],"predecessor-version":[{"id":27491,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26965\/revisions\/27491"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=26965"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=26965"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=26965"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=26965"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}