Skip to content

Commit

Permalink
+ add specific exercise for bitbucket pipelines
Browse files Browse the repository at this point in the history
  • Loading branch information
langchr86 committed Feb 5, 2024
1 parent fabf5ff commit ea18cd6
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 0 deletions.
3 changes: 3 additions & 0 deletions mlc-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
},
{
"pattern": ".*.sh"
},
{
"pattern": "^https://bitbucket.org/supercomputingsystems.*"
}
],
"aliveStatusCodes": [200, 206, 500]
Expand Down
1 change: 1 addition & 0 deletions topics/build_systems/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
343 changes: 343 additions & 0 deletions topics/build_systems/bitbucket_pipelines_exercise.md
Original file line number Diff line number Diff line change
@@ -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. `<user>-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://<user>@bitbucket.org/<user>-privat/bitbucket-pipelines-exercise.git
# oder
git clone [email protected]:<user>-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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions topics/build_systems/slides/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -90,5 +91,6 @@ Links
* [github_awesome_actions]
* [github_actions_hello_world]
* [github_actions_ci]
* [bitbucket-pipeline-template-docker]

\colEnd
8 changes: 8 additions & 0 deletions topics/build_systems/slides/practice_2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------

Expand Down

0 comments on commit ea18cd6

Please sign in to comment.