Automatisches Deployment von TYPO3 Extensions via GitHub Actions
Diejenigen unter euch, welche bereits TYPO3 Extensions auf GitHub hosten, haben vermutlich schonmal von GitHub Actions gehört. Es ist die GitHub Variante von GitLab CI und ziemlich cool. Mit GitHub Actions kann man Workflows für seine Repositories erstellen, das heißt auf gewisse Events reagieren und zum Beispiel Builds generieren. Genau das habe ich mir zunutze gemacht, um die nervigen TYPO3 Extension Repository Uploads zu automatisieren und mir somit Arbeit zu ersparen.
Im Folgenden erläutere ich wie ich die GitHub Action erstellt habe und sie für meine Extensions benutze.
Ausgangssituation
Ursprünglich musste ich bei jedem Release einer neuen Version meiner Extensions zuerst den Code in der entsprechenden Version auschecken, das Kommando zum Erstellen des zip-Archivs heraussuchen, es erstellen und dieses dann manuell auf https://extensions.typo3.org hochladen. Da dies ziemlich aufwändig ist, habe ich mir schließlich eine Lösung über GitHub Actions ausgedacht, welche den praktischen TER Client von Helmut Hummel nutzt, um diesen Upload bei einem neuen Release automatisiert ins TER zu machen.
TER Client vorbereiten
Der Client ist ein selbstständiges PHP Programm, welches über CLI mit einigen Parametern direkt aufgerufen werden kann. Damit dieses in den GitHub Actions funktioniert und damit ich eine immer gleiche Platform zum Ausführen des Clients habe, erstellte ich mir zunächst einen Docker Container, welcher den Client in einer PHP Umgebung enthält.
Dazu erstellen wir uns zunächst ein Dockerfile, welches von php:7.4-cli ableitet. Der Client arbeitet intern mittels SOAP, daher müssen wir noch einige PHP Extensions laden:
Dockerfile
FROM php:7.4-cli
RUN apt-get update && apt-get install -y libxml2-dev wget git zip libzip-dev
RUN docker-php-ext-install -j "$(nproc)" soap
Da der Client als composer Paket daher kommt, brauchen wir auch composer. Bei der composer Installation beachten wir den Hinweis zur programmatischen Installation von composer und erstellen uns ein Install-Script, welches wir beim Bauen des Docker Containers mit ausführen.
composer-installer.sh
#!/bin/sh
EXPECTED_CHECKSUM="$(wget -q -O - composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
>&2 echo 'ERROR: Invalid installer checksum'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
Dockerfile
FROM php:7.4-cli
RUN apt-get update && apt-get install -y libxml2-dev wget git zip libzip-dev
RUN docker-php-ext-install -j "$(nproc)" soap
COPY composer-installer.sh /composer-installer.sh
RUN chmod +x /composer-installer.sh
RUN /composer-installer.sh
Zum Abschluss clonen wir uns den Client von GitHub und führen ein composer install aus.
Dockerfile
FROM php:7.4-cli
RUN apt-get update && apt-get install -y libxml2-dev wget git zip libzip-dev
RUN docker-php-ext-install -j "$(nproc)" soap
COPY composer-installer.sh /composer-installer.sh
RUN chmod +x /composer-installer.sh
RUN /composer-installer.sh
RUN git clone github.com/helhum/ter-client.git app; cd app; git checkout v0.1.1; /composer.phar install
Nun da wir den Client in unserem Docker Container haben, machen wir ihn noch der Außenwelt bekannt. Hierfür definieren wir einen Entrypoint, in dem wir den Client aufrufen.
Dockerfile
FROM php:7.4-cli
RUN apt-get update && apt-get install -y libxml2-dev wget git zip libzip-dev
RUN docker-php-ext-install -j "$(nproc)" soap
COPY composer-installer.sh /composer-installer.sh
RUN chmod +x /composer-installer.sh
RUN /composer-installer.sh
RUN git clone github.com/helhum/ter-client.git app; cd app; git checkout v0.1.1; /composer.phar install
ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh
/app/ter-client upload -u $1 -p $2 -m "$3" $4 $5
The Github Action
Jetzt, da wir den TER-Client in einem Docker Container ausführen können, können wir uns an die Erstellung unserer Github Action machen. Dies kann sowohl direkt in dem Repository geschehen, in welchem der Extension Code liegt, oder aber generell als alleinstehendes Github Repository, welches dann in einen Workflow integriert werden kann.
Die eine Github Action braucht zunächst eine YAML Datei, weche diese beschreibt. Ein paar Basisangaben in dieser YAML sind:
- name - Der Name der Action
- description - Eine Beschreibung, was die Action tut
- author - Die Angabe eines Autors
- runs - Angaben darüber, wie die Action ausgeführt wird. Es gibt neben der Möglichtkeit, Docker Container zu starten auch noch andere Varianten. Mehr dazu vielleicht in einem späteren Beitrag. Es lohnt sich hier die Dokumentation von Github anzuschauen.
Wir werden in diesem Beispiel in der runs Konfiguration einen Docker Container eintragen, welchen wir erstellen werden, um den ter-client zu laden und auszuführen.
The YAML could look like this:
name: 'TYPO3 Extension Repository Uploader'
description: 'This action can pack and upload an extension to the TYPO3 Extension Repository (TER)'
author: 'Kevin Ditscheid <kevin@the-coding-owl.de>'
runs:
using: 'docker'
image: 'Dockerfile'
Secrets
Da man für den Upload der Extensions ins TER einen Username und ein Passwort benötigt, kümmern wir uns als nächstes um die Angabe der Credentials in Form von Secrets.
Diese Secrets stellt man in den Einstellungen des jeweiligen Repositories ein, welches die Action am Ende im Workflow aufruft.
Um diese Secrets nutzen zu könnnen, müssen wir diese zunächst in der entrypoint.sh abfragen. Dafür habe ich einige Environment Variablen benutzt.
entrypoint.sh
/app/ter-client upload -u ${SECRET_USERNAME} -p ${SECRET_PASSWORD} -m "${UPLOAD_MESSAGE}" ${EXTENSION_KEY} ${GITHUB_WORKSPACE}
Die Secrets mit dem Namen SECRET_USERNAME und SECRET_PASSWORD werden vom Workflow übergeben.
Eine Workflow YAML einer TYPO3 Extension auf Github könnnte am Ende wie folgt aussehen:
.github/workflows/publish.yaml
name: Publish on TER
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: the-coding-owl/action-typo3-extension-repository-upload@0.0.1
env:
SECRET_USERNAME: ${{ secrets.USERNAME }}
SECRET_PASSWORD: ${{ secrets.PASSWORD }}
EXTENSION_KEY: 'my_ext'
UPLOAD_MESSAGE: ${{ github.event.release.body }}
Über die Einstellung env können wir der aufgerufenen Action diese Environment Variablen übergeben. Die Secrets aus den Repository Einstellungen befinden sich in der Variable ${{ secrets }}.
Die anderen beiden Environment Variablen beschreiben den Extension Key, welcher beim TER Upload dazu dient die Extension im TER zu identifizieren, und die UPLOAD_MESSAGE, welche in der Versionshistorie als Release Notes angezeigt werden. Hier habe ich den Beschreibungstext des Github Releases eingefügt, weil dieser in meinem Fall den selben Inhalt haben sollte, wie die entsprechenden Informationen im TER selbst.
Ergebnis
Das Ergebnis unserer Bemühungen haben wir nun eine Action, welche einen Docker Container mit dem ter-client baut und ihn in einer entrypoint.sh dem Workflow zur Verfügung stellt und einen Workflow, welcher beim Erstellen eines Releases die Action triggert, um mit dem neuen Extension Code ein Paket zu bauen und es im TER hochzuladen.
Den Workflow und die entsprechenden Actions habe ich im folgenden für meine Extension oclock benutzt:
https://github.com/the-coding-owl/oclock
Die Action habe ich in ein separates Repository ausgelagert, zur einfacheren Wiederverwendung:
https://github.com/the-coding-owl/action-typo3-extension-repository-upload
Und den ter-client habe ich ebenfalls in ein Repository ausgelagert und im Docker Hub hochgeladen: