diff --git a/mlc-config.json b/mlc-config.json index cdb6b80..88fd9a2 100644 --- a/mlc-config.json +++ b/mlc-config.json @@ -8,6 +8,9 @@ }, { "pattern": ".*.sh" + }, + { + "pattern": "^https://bitbucket.org/supercomputingsystems.*" } ], "aliveStatusCodes": [200, 206, 500] diff --git a/topics/build_systems/CMakeLists.txt b/topics/build_systems/CMakeLists.txt index 4450e9f..47d42c9 100644 --- a/topics/build_systems/CMakeLists.txt +++ b/topics/build_systems/CMakeLists.txt @@ -4,6 +4,7 @@ file(GLOB_RECURSE slides_files slides/*.md) js_slides(build_systems_slides build_systems_slides.md ${slides_files}) js_exercise(build_systems_exercise build_systems_exercise.md) +js_script(bitbucket_pipelines_exercise bitbucket_pipelines_exercise.md) file(GLOB_RECURSE code RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "code/*") list(FILTER code EXCLUDE REGEX "code/CMakeLists.txt") diff --git a/topics/build_systems/bitbucket_pipelines_exercise.md b/topics/build_systems/bitbucket_pipelines_exercise.md new file mode 100644 index 0000000..934ff74 --- /dev/null +++ b/topics/build_systems/bitbucket_pipelines_exercise.md @@ -0,0 +1,343 @@ +<#include meta/exercise.md> + +--- +title: "Arbeitsblatt: Bitbucket Pipelines" +--- + + + +Einleitung +========== + +In dieser Übung werden wir eine einfache Bitbucket Pipeline aufsetzten, +welche automatisch z.B. CI/CD Aufgaben ausführen kann. +Solche Pipelines laufen per Default in der Cloud bei Atlassian, +können aber auch auf selbst-gehosteten Servern laufen. +Wir verwenden in dieser Übung nur Cloud-Funktionen. + + + +Vorbereitung +============ + +Als Erstes wird ein privater Atlassian Account für Bitbucket benötigt. +Diesen kann man z.B. unter [bitbucket.org](https://bitbucket.org/) erstellen. +Falls im Browser bereits ein anderer Atlassian Account eingeloggt ist, +kann ein "Inkognito" oder "privates" Fenster verwendet werden, +um sich beim bestehenden Account nicht abmelden zu müssen. + +Danach kann über den Button `Create` ein neues Repository erstellt werden. +Dazu muss auch ein Workspace und ein Projekt definiert werden. +Als Workspace kann z.B. `-privat` und als Projekt `Jumpstart` verwendet werden. +Das Repository kann z.B. `bitbucket-pipelines-exercise` genannt werden. + +Damit einfach auf die Repos zugegriffen werden kann, +sollte man unter [bitbucket.org/account/settings/ssh-keys](https://bitbucket.org/account/settings/ssh-keys/) +seinen public Key registrieren. + +Danach kann das neue Repository in der `jumpstart-vm` oder lokal geklont werden: + +~~~ +git clone https://@bitbucket.org/-privat/bitbucket-pipelines-exercise.git +# oder +git clone git@bitbucket.org:-privat/bitbucket-pipelines-exercise.git +~~~ + +Wenn folgender Fehler beim Versuch mittels SSH auftritt: + +~~~ +Cloning into 'bitbucket-pipelines-exercise'... +The requested repository either does not exist or you do not have access. +~~~ + +kann es sein, dass der Authentication Agent (z.B. `pageant`) mehrere Keys verwaltet, +welche bei Bitbucket registriert sind. +In diesem Fall muss sichergestellt werden, +dass der gewünschte private Key der einzige oder der erste verwaltete Key ist. + + + +Erste Automation mit Pipelines +============================== + + +Mittels CI/CD-Automatismen kann man diverse Probleme lösen. +Ein üblicher Task, welcher vor jedem Merge eines Pull-Requests durchgeführt werden soll, +ist üblicherweise das komplette Kompilieren und Ausführen der Tests des Projektes. + +Da solche Automatismen stark mit dem eigentlichen Projekt-Code verbunden sind, +werden diese Pipelines ebenfalls im Projekt-Repo definiert und sind somit konsistent versionierbar. +In Bitbucket werden die Pipelines in der Datei `bitbucket-pipelines.yml` im Top-Level-Ordner des Repositories definiert. + + +Aufgabe +------- + +* Erstelle im neuen Repo die Datei `bitbucket-pipelines.yml`. +* Definiere einen einzelnen "Step" der `Hello World` im Log ausgibt und bei jedem Pushen auf jedem Branch getriggert wird. +* Verwende das Ubuntu 22.04 image von Atlassian. +* Commite und pushe den neuen Stand und beobachte die Ausführung. + +Die Pipeline kann links im Menu `Pipelines` ausgewählt werden. +Initial muss evtl. noch 2FA für den Atlassian-Account aktiviert werden. +Danach kann man mittels `Run initial pipeline` die Ausführung der Pipeline starten. + + +Lösung +------ + +Die `bitbucket-pipelines.yml` sollte etwa folgendermassen aussehen: + +~~~{.yaml} +pipelines: + default: # no filtering for branch or PR-only + - step: # first in a list of steps + name: 'Welcome' + image: atlassian/default-image:4 # docker image to use: ubuntu 22.04 LTS + script: # list of shell commands to execute + - echo "Hello World" +~~~ + +![pipelines_welcome_step](images/pipelines_welcome_step.png) + +Siehe auch: + +* [use-docker-images-as-build-environments/#Default-build-environment](https://support.atlassian.com/bitbucket-cloud/docs/use-docker-images-as-build-environments/#Default-build-environment) +* [view-your-pipeline](https://support.atlassian.com/bitbucket-cloud/docs/view-your-pipeline/) + + + +Pipeline mit mehreren "Steps" +============================= + + +In unserer ersten Pipeline haben wir einen einzelnen "Step" definiert, +welcher einen einzelnen Befehl ausgeführt hat. + +"Pipelines" haben "Steps". Und "Steps" haben "Scripts" (Befehle). + +Jeder Step ist in der Pipeline komplett unabhängig von anderen Steps. +Dazu läuft jeder Step in einem eigenen, neuen Container, welcher durch das `image` definiert wird. +Innerhalb des Step können aber beliebig viele Befehle ausgeführt werden, +welche auf den gemeinsamen Stand des aktuellen Containers zugreifen können. +So könnte durchaus in einem ersten Befehl eine Source-Datei erzeugt werden, +welche in einem zweiten Befehl als Input für den Kompiler dienen würde. + + +Aufgabe +------- + +Als Nächstes wollen wir mehrere Steps aufeinander aufbauen lassen. +Dazu soll im ersten Step eine Datei generiert werden, +welche als sogenanntes "Artifact" an den zweiten Step übergeben wird. +Und dies, obwohl der zweite Step in einem völlig neuen Container läuft. + + +Lösung +------ + +Wir erzeugen in einem ersten Step die Datei `input.txt`. +Diese soll in einem weiteren Step mittels `zip` komprimiert werden. +Die resultierende `output.zip` Datei soll dann zusätzlich über des Web-UI herunterladbar sein. + +Dazu ersetzten wir die Steps im `bitbucket-pipelines.yml` durch: + +~~~{.yaml} +pipelines: + default: + - step: + name: 'Generate' + image: debian:bookworm + script: + - echo "My specific text" >> input.txt + artifacts: + - input.txt + - step: + name: 'Compress' + image: ubuntu:jammy + script: + - apt update && apt install -y zip + - zip output.zip input.txt + artifacts: + - output.zip +~~~ + +Diese nutzt nun unterschiedliche Container-Images. +Grundsätzlich können alle öffentlichen Container-Registries verwendet werden. +Mittels entsprechenden Credentials könnten auch Images von privaten Registries verwendet werden. + +Das `artifacts` Keyword sorgt einerseits dafür, dass Dateien in folgenden Steps verfügbar sind, +andererseits, dass diese im Web-UI unter "Artifacts" herunterladbar sind. + +![pipelines_artifacts_download](images/pipelines_artifacts_download.png) + +Siehe auch: + +* [step-options](https://support.atlassian.com/bitbucket-cloud/docs/step-options/) +* [step-options/#Artifacts](https://support.atlassian.com/bitbucket-cloud/docs/step-options/#Artifacts) + + + +Python Projekt mit Tests +======================== + +Nun wollen wir ein etwas praxis-näheres Beispiel betrachten. +In diesem Fall möchten wir Python-Code mittels `pytest` testen und das Resultat in Bitbucket Pipelines anzeigen lassen. + +Dies ist ein Basis-Feature, welches automatisch Test-Resultate in bestimmten Ordner sucht. +Die expliziten Resultate der einzelnen Tests werden in einem Fehlerfall angezeigt. +Wenn alle Tests erfolgreich waren, werden keine Details angezeigt. + + +Aufgabe +------- + +Der neue Step soll ein geeignetes Container-Image mit Python verwenden +und darin ein Python-Environment inkl. `pytest` vorbereiten. +Danach kann der Test ausgeführt werden. +Es muss sichergestellt werden, +dass die Ausführung eine "Report"-Datei in geeignetem Format z.B. unter `test-reports/` ablegt. + + +Lösung +------ + +Wir fügen folgenden Python-Code im Repo unter `pytest_project/test_capitalize.py` hinzu: + +~~~{.python} +def capital_case(x): + return x.capitalize() + +def test_capital_case(): + assert capital_case('semaphore') == 'SemaphoreWrong' +~~~ + +Nach dem Ersetzen der Steps im `bitbucket-pipelines.yml`, +wird die Pipeline aufgrund eines fehlgeschlagenen Tests nicht durchlaufen. + +~~~{.yaml} +pipelines: + default: + - step: + name: 'Test Results' + image: python:3.13.0a3 + script: + - cd pytest_project + - python3 -m venv pytest-env + - source pytest-env/bin/activate + - pip install pytest + - pytest --junitxml=test-reports/report.xml +~~~ + +![pipelines_failed_test](images/pipelines_failed_test.png) + +Nach diesem ersten Fehlschlag kann die `assert`-Zeile folgendermassen korrigiert werden: + +~~~{.python} + assert capital_case('semaphore') == 'Semaphore' +~~~ + +Siehe auch: + +* [test-reporting-in-pipelines](https://support.atlassian.com/bitbucket-cloud/docs/test-reporting-in-pipelines/) + + + +Pipeline mit Docker Registry +============================ + +Häufig beinhalten Projekte eigene Definitionen für Container-Images. +Diese werden entweder für das Endprodukt oder für einen Teil des Build-Prozesses benötigt. +Solche Images ändern meist relativ selten im Projekt, +weshalb sie wiederverwendbar sind und nicht für jeden Durchlauf des CI/CD-Prozesses erneut gebaut werden müssen. + +Um dies zu ermöglichen, +kann eine externe Container-Image Registry, wie z.B. DockerHub oder Artifactory, verwendet werden. + + +Aufgabe +------- + +Im nächsten Step soll nun ein Dockerfile zu einem Image kompiliert werden, +welches dann im nächsten Step als Basis verwendet wird. +Damit dieser erste Step nicht jedes mal das gleiche Image bauen muss, +deployen wir den Cache von BuildKit auf DockerHub. +Beim nächsten Durchlauf kann BuildKit beim Bauen des Images auf diesen Cache zugreifen +und muss im besten Fall gar keinen Layer des Image mehr bauen, +sondern kann alles bestehende herunterladen und direkt verwenden. + + +Lösung +------ + +In unserem Beispiel verwenden wir DockerHub als Registry, weshalb ein DockerHub-Account existieren muss. + +Damit in der CI nicht das echte Passwort hinterlegt werden muss, +kann unter `My Account -> Security` ein Access-Token für DockerHub generiert werden, +welches später als Passwort dient. + +![pipelines_access_token](images/pipelines_access_token.png) + +Die Login-Daten können nun als verschlüsselte Repository-Variablen über das Web-UI gespeichert werden. +Für die Variable `DOCKER_HUB_PASSWORD` wird das generierte Token verwendet. +In meinem Fall ist `$DOCKER_HUB_USER` mit `langchr86` definiert. + +![pipelines_variables](images/pipelines_variables.png) + +Jetzt können wir die Pipeline durch folgende ersetzten. +In der Zeile wo das selbst-gebaute Image verwendet wird, +muss `langchr86` durch den eigenen Usernamen ersetzt werden. + +~~~{.yaml} +pipelines: + default: + - step: + name: 'Build Image' + image: atlassian/default-image:4 + script: + - export DOCKER_BUILDKIT=1 + - export IMAGE=docker.io/$DOCKER_HUB_USER/bitbucket-pipelines-exercise + - docker login --username $DOCKER_HUB_USER --password $DOCKER_HUB_PASSWORD + - >- + docker build + --build-arg BUILDKIT_INLINE_CACHE=1 + --cache-from $IMAGE:cache + --tag $IMAGE:v0.1 --tag $IMAGE:cache . + - docker push $IMAGE:v0.1 + - docker push $IMAGE:cache + services: + - docker + - step: + name: 'Use Image' + image: docker.io/langchr86/bitbucket-pipelines-exercise:v0.1 + script: + - cat /custom.txt +~~~ + +Hier verwenden wir `docker build` in Kombination mit der `--cache-from` Option. +In einem anderen Kontext, wo `docker buildx` unterstützt würde, wäre dies die modernere Variante. +Leider ist dies auf den Cloud-Runner von Bitbucket nicht möglich. +In der SCS könnte man dieses Feature auf self-hosted Runners verwenden. + + +Bemerkungen zu den Credentials +------------------------------ + +Der `docker login` Befehl wird eine Warnung in dieser Form auslösen: + +~~~ +WARNING! Using --password via the CLI is insecure. Use --password-stdin. +WARNING! Your password will be stored unencrypted in /root/.docker/config.json. +Configure a credential helper to remove this warning. See +https://docs.docker.com/engine/reference/commandline/login/#credentials-store +~~~ + +Dies ist normalerweise ein Problem. +Da wir hier aber in einem Container sind, +welcher direkt nach dem Ausführen des Steps wieder komplett gelöscht wird, +ist dies kein Problem. + +Durch die Verwendung des Tokens anstatt des echten Passworts +würden sich auch im Leak-Fall aber weniger Probleme ergeben. +Ein solches Token kann zwar ebenfalls Images pushen oder auch löschen, +aber z.B. nicht den Account löschen oder das Passwort ändern. diff --git a/topics/build_systems/images/pipelines_access_token.png b/topics/build_systems/images/pipelines_access_token.png new file mode 100644 index 0000000..f3afd18 Binary files /dev/null and b/topics/build_systems/images/pipelines_access_token.png differ diff --git a/topics/build_systems/images/pipelines_artifacts_download.png b/topics/build_systems/images/pipelines_artifacts_download.png new file mode 100644 index 0000000..1e3c8f5 Binary files /dev/null and b/topics/build_systems/images/pipelines_artifacts_download.png differ diff --git a/topics/build_systems/images/pipelines_failed_test.png b/topics/build_systems/images/pipelines_failed_test.png new file mode 100644 index 0000000..0429b86 Binary files /dev/null and b/topics/build_systems/images/pipelines_failed_test.png differ diff --git a/topics/build_systems/images/pipelines_variables.png b/topics/build_systems/images/pipelines_variables.png new file mode 100644 index 0000000..40575a2 Binary files /dev/null and b/topics/build_systems/images/pipelines_variables.png differ diff --git a/topics/build_systems/images/pipelines_welcome_step.png b/topics/build_systems/images/pipelines_welcome_step.png new file mode 100644 index 0000000..ac94fee Binary files /dev/null and b/topics/build_systems/images/pipelines_welcome_step.png differ diff --git a/topics/build_systems/slides/links.md b/topics/build_systems/slides/links.md index 4fa544e..09e1989 100644 --- a/topics/build_systems/slides/links.md +++ b/topics/build_systems/slides/links.md @@ -81,6 +81,7 @@ Links [github_awesome_actions]: https://github.com/sdras/awesome-actions [github_actions_hello_world]: https://github.com/skills/hello-github-actions [github_actions_ci]: https://github.com/skills/continuous-integration +[bitbucket-pipeline-template-docker]: https://bitbucket.org/supercomputingsystems/bitbucket-pipeline-template-docker/src/main/ * [ci_vs_cd] * [future_of_ci_cd] @@ -90,5 +91,6 @@ Links * [github_awesome_actions] * [github_actions_hello_world] * [github_actions_ci] +* [bitbucket-pipeline-template-docker] \colEnd diff --git a/topics/build_systems/slides/practice_2.md b/topics/build_systems/slides/practice_2.md index 7b13da4..e81c75a 100644 --- a/topics/build_systems/slides/practice_2.md +++ b/topics/build_systems/slides/practice_2.md @@ -2,6 +2,14 @@ Praxis Block 2 ============== +Bitbucket Pipelines +------------------- + +Es soll die Aufgabe `bitbucket_pipelines_exercise.pdf` gelöst werden. + +Weitere SCS spezifische Vorlagen finden sich hier: [bitbucket-pipeline-template-docker] + + GitHub Actions --------------