Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Docker Jenkins installation #1071

Merged
merged 2 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/jenkins.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

docker pull jenkins/jenkins:lts-alpine
docker pull jenkins/jenkins:lts-jdk21

docker compose build --pull

Expand Down
60 changes: 18 additions & 42 deletions doc/Continuous-Integration.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
# Continuous Integration des Coding Style

Gemäß dem Grundsatz **eat your own dogfood** ist dieser Coding Style bereits für die Continuous Integration
in [GitHub Actions](https://github.com/features/actions) und [Jenkins](https://jenkins.io) vorbereitet.
Gemäß dem Grundsatz **eat your own dogfood** ist dieser Coding Style bereits für die Continuous Integration in [GitHub Actions](https://github.com/features/actions), [GitLab CI](https://docs.gitlab.com/ee/ci/) und [Jenkins](https://jenkins.io) vorbereitet.

## Maven Konfiguration

Sowohl für GitHub Actions als auch für Jenkins erfolgt die Automatisierung des Builds über Maven. Im zugehörigen
[POM](../pom.xml) sind alle Versionen der benutzten Maven Plugins und der benötigten Abhängigkeiten über Properties
definiert, d.h. eine Aktualisierung lässt sich im entsprechenden Abschnitt leicht selbst durchführen bzw. wird
über den [Dependabot](https://dependabot.com) Roboter von GitHub automatisch über einen Pull Request aktualisiert.
Sowohl für GitHub Actions als auch für Jenkins erfolgt die Automatisierung des Builds über Maven. Im zugehörigen [POM](../pom.xml) sind alle Versionen der benutzten Maven Plugins und der benötigten Abhängigkeiten über Properties definiert, d.h. eine Aktualisierung lässt sich im entsprechenden Abschnitt leicht selbst durchführen bzw. wird über den [Dependabot](https://dependabot.com) Roboter von GitHub automatisch über einen Pull Request aktualisiert.
U.a. sind die folgenden Plugins vorkonfiguriert:
- maven-compiler-plugin: konfiguriert die Java Version auf Java 8 und legt alle Error Prone Regeln fest. Die Java
Version kann beliebig aktualisiert werden.
- maven-compiler-plugin: konfiguriert die Java Version auf Java 11 und legt alle Error Prone Regeln fest. Die Java Version kann beliebig aktualisiert werden.
- maven-javadoc-plugin: aktiviert die strikte Prüfung von JavaDoc Kommentaren
- maven-jar-plugin: legt einen Modulnamen fest, falls das Projekt in Java 9 oder höher verwendet wird. Außerdem wird
ein test-jar konfiguriert, sodass alle Tests (und abstrakte Testklassen) auch als Dependencies genutzt werden können.
- maven-pmd-plugin: prüft das Projekt mit PMD, die Regeln liegen in den Dateien [pmd-java-configuration.xml](../etc/pmd-java-configuration.xml), [pmd-tests-configuration.xml](../etc/pmd-tests-configuration.xml) und [pmd-javascript-configuration.xml](../etc/pmd-javascript-configuration.xml).
- maven-checkstyle-plugin: prüft das Projekt mit CheckStyle, die Regeln liegen in den Dateien [checkstyle-java-configuration.xml](../etc/checkstyle-java-configuration.xml) und [checkstyle-tests-configuration.xml](../etc/checkstyle-tests-configuration.xml).
- spotbugs-maven-plugin: prüft das Projekt mit SpotBugs, alle Regeln werden verwendet mit den Ausnahmen definiert in der Datei [spotbugs-exclusion-filter.xml](../etc/spotbugs-exclusion-filter.xml).
- org.revapi: prüft, ob die aktuelle Versionsnummer die [semantische Versionierung](https://semver.org) berücksichtigt (source and binary). D.h. es gilt:
- maven-jar-plugin: legt einen Modulnamen fest. Außerdem wird ein test-jar konfiguriert, sodass alle Tests (und abstrakte Testklassen) auch als Dependencies genutzt werden können.
- maven-pmd-plugin: prüft das Projekt mit [PMD](https://pmd.github.io/), die Regeln liegen in den Dateien [pmd-java-configuration.xml](../etc/pmd-java-configuration.xml), [pmd-tests-configuration.xml](../etc/pmd-tests-configuration.xml) und [pmd-javascript-configuration.xml](../etc/pmd-javascript-configuration.xml).
- maven-checkstyle-plugin: prüft das Projekt mit [CheckStyle](https://checkstyle.sourceforge.io/), die Regeln liegen in den Dateien [checkstyle-java-configuration.xml](../etc/checkstyle-java-configuration.xml) und [checkstyle-tests-configuration.xml](../etc/checkstyle-tests-configuration.xml).
- spotbugs-maven-plugin: prüft das Projekt mit [SpotBugs](https://spotbugs.github.io/), alle Regeln werden verwendet mit den Ausnahmen definiert in der Datei [spotbugs-exclusion-filter.xml](../etc/spotbugs-exclusion-filter.xml).
- revapi-maven-plugin: prüft, ob die aktuelle Versionsnummer die [semantische Versionierung](https://semver.org) berücksichtigt (source and binary). D.h. es gilt:
1. Eine neue **Major** Version wurde definiert, wenn das API nicht mehr abwärtskompatibel ist.
2. Eine neue **Minor** Version wurde definiert, wenn eine neue Funktionalität abwärtskompatibel hinzugefügt wurde.
3. Eine neue **Patch** Version wurde definiert, wenn Fehler abwärtskompatibel behoben wurden.
Expand All @@ -30,41 +24,23 @@ ein test-jar konfiguriert, sodass alle Tests (und abstrakte Testklassen) auch al

[![GitHub Actions](https://github.com/uhafner/codingstyle/workflows/GitHub%20CI/badge.svg)](https://github.com/uhafner/codingstyle/actions)

Die Konfiguration der Continuous Integration in GitHub Actions is sehr [einfach](../.github/workflows/ci.yml).
Da der gesamte Build über Maven automatisiert ist, besteht die Konfiguration eigentlich nur aus einem Maven Aufruf,
der das Projekt baut, alle Tests (Unit und Integrationstests) ausgeführt, die statische Code Analyse durchführt
und schließlich werden nochmals alle Test mit dem Code Coverage Tool JaCoCo analysiert. Bei einem erfolgreichen
Build werden die Code Coverage Ergebnisse in die Platform [CodeCov](https://app.codecov.io/gh/uhafner/codingstyle) hochgeladen.
GitHub Actions bietet die Möglichkeit, Matrix Builds durchzuführen: d.h., der Build wird auf den Plattformen Linux,
Windows und macOS parallel durchgeführt.
Die Konfiguration der Continuous Integration in GitHub Actions is sehr [einfach](../.github/workflows/ci.yml) über eine Pipeline möglich. Da der gesamte Build über Maven automatisiert ist, besteht die Pipeline eigentlich nur aus einem Maven Aufruf, der das Projekt baut, alle Tests (Unit und Integrationstests) ausgeführt, die statische Code Analyse durchführt und schließlich die Coverage misst. GitHub Actions bietet auch die Möglichkeit, Matrix Builds durchzuführen: d.h., der Build wird z.B. auf den Plattformen Linux, Windows und macOS oder mit den Java Versionen 17 und 21 parallel durchgeführt. Ein Beispiel für die Konfiguration eines Matrix Builds ist in der Datei [ci.yml](../.github/workflows/ci.yml) zu finden.

Wenn gewünscht, können die Ergebnisse der statischen Code Analyse und der Code Coverage Tools auch direkt im Commit oder Pull-Request angezeigt werden. Dazu muss in der Pipeline meine [Quality Monitor Action](https://github.com/uhafner/quality-monitor) aktiviert werden. Eine Beispielkonfiguration ist in der Datei [quality-monitor.yml](../.github/workflows/quality-monitor.yml) zu finden, das Ergebnis in der nachfolgenden Abbildung:

![Quality Monitor](images/quality-monitor.png)

## Jenkins

Eine Beispielintegration in Jenkins ist auch bereits vorhanden. Diese ist im [Jenkinsfile](../Jenkinsfile) hinterlegt
und startet die Integration in mehreren Schritten (Stages). Zunächst werden auch hier alle Schritte wie in GitHub Actions
aufgerufen. Anschließend erfolgt noch ein Start der Mutation Coverage mit [PIT](http://pitest.org). Insgesamt ist
die CI Konfiguration für Jenkins umfangreicher, da nicht nur der eigentliche Build konfiguriert wird, sondern
auch die Darstellung der Ergebnisse im Jenkins UI über die entsprechenden Jenkins Plugins konfiguriert wird.
Eine solche Visualisierung ist unter GitHub Actions nicht verfügbar.
Eine Beispielintegration mit Jenkins ist auch bereits vorhanden. Diese ist im [Jenkinsfile](../Jenkinsfile) hinterlegt und startet die Integration in mehreren Schritten (Stages). Zunächst werden auch hier alle Schritte wie in GitHub Actions aufgerufen. Anschließend erfolgt noch ein Start der Mutation Coverage mit [PIT](http://pitest.org). Insgesamt ist die CI Konfiguration für Jenkins umfangreicher, da nicht nur der eigentliche Build konfiguriert wird, sondern auch die Darstellung der Ergebnisse im Jenkins UI über die entsprechenden Jenkins Plugins konfiguriert wird.

### Lokale CI in Jenkins (über Docker Compose)

Da es für Jenkins keinen öffentlichen Service wie bei GitHub Actions gibt, um eigene Projekte zu bauen, muss die Jenkins
Integration lokal durchgeführt werden. Zur Vereinfachung des Jenkins Setup ist in diesem Coding Style eine
lauffähige Jenkins Installation enthalten (im Sinne von *Infrastructure as Code*).
Diese kann über `bin/jenkins.sh` gestartet werden. Anschließend wird die
aktuelle Jenkins LTS Version mit allen benötigten Plugins in einem Docker Container gebaut und gestartet (das dauert
beim ersten Aufruf etwas). Dazu wird ebenso ein als Docker Container initialisierter Java Agent (**Achtung**: Java 8)
verbunden, der die Builds ausführt.
Da es für Jenkins keinen öffentlichen Service wie bei GitHub Actions gibt, um eigene Projekte zu bauen, muss die Jenkins Integration lokal auf einem Team-Server durchgeführt werden. Zur Vereinfachung des Jenkins Setup ist in diesem Coding Style eine lauffähige Jenkins Installation enthalten (im Sinne von *Infrastructure as Code*). Diese kann über `bin/jenkins.sh` gestartet werden. Anschließend wird die aktuelle Jenkins LTS Version mit allen benötigten Plugins in einem Docker Container gebaut und gestartet (das dauert beim ersten Aufruf etwas). Dazu wird ebenso ein als Docker Container initialisierter Java Agent verbunden, der die Builds ausführt.

<!-- markdown-link-check-disable-next-line -->
Nach einem erfolgreichen Start von Jenkins ist dann unter [http://localhost:8080/job/Codingstyle/](http://localhost:8080/job/Codingstyle/) der entsprechende Jenkins Job sichtbar.
Der Zugang auf diesen lokalen Rechner erfolgt zur Vereinfachung
mit Benutzer `admin` und Passwort `admin`, anschließend hat man volle Jenkins Administrationsrechte.
Der Job `Codingstyle` muss danach manuell gestartet werden,
die Ergebnisse der Tests, Code und Mutation Coverage sowie statischen Analyse werden dann automatisch
visualisiert. Das Jenkins Home Verzeichnis ist im Docker Container als externes Volume angelegt: d.h. der Zugriff kann
auf dem Host direkt im Verzeichnis `docker/volumes/jenkins-home` erfolgen.
Nach einem erfolgreichen Start von Jenkins sind dann unter [http://localhost:8081](http://localhost:8080/job/Codingstyle/) mehrere Jenkins Jobs sichtbar. Einer dieser Jobs baut das vorliegende Coding Style Projekt. Der Zugang auf diesen lokalen Rechner erfolgt zur Vereinfachung mit Benutzer `admin` und Passwort `admin`, anschließend hat man volle Jenkins Administrationsrechte. Die jeweiligen Jobs müssen danach manuell gestartet werden, die Ergebnisse der Tests, Code und Mutation Coverage sowie statischen Analyse werden dann automatisch visualisiert. Das Jenkins Home Verzeichnis ist im Docker Container als externes Volume angelegt: d.h. der Zugriff kann auf dem Host direkt im Verzeichnis `docker/volumes/jenkins-home` erfolgen.

Nach einem ersten Build in Jenkins sollte sich dann folgendes Bild ergeben:
Nach einem ersten Build in Jenkins sollte sich dann in etwa folgendes Bild ergeben:

![Jenkins Build Summary](images/build-result.png)
Binary file added doc/images/quality-monitor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 58 additions & 8 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
version: "3"
name: codingstyle

services:
jenkins-controller:
jenkins:
container_name: jenkins
build:
context: docker/images/jenkins-controller
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./docker/volumes/jenkins-home:/var/jenkins_home:cached
- ./docker/volumes/jenkins-home:/var/jenkins_home # Mounts the local jenkins_home volume to the /var/jenkins_home path inside the container
- agent-ssh-dir:/ssh-dir # Mounts the shared volume agent-ssh-dir to a path inside the container
ports:
- 8081:8080 # Jenkins UI - HOST:CONTAINER
environment:
- TRY_UPGRADE_IF_NO_MARKER=true
- JAVA_OPTS= -Dstapler.jelly.noCache=true -Dhudson.remoting.ClassFilter=com.google.common.collect.ImmutableListMultimap -DexecutableWar.jetty.disableCustomSessionIdCookieName=true -DexecutableWar.jetty.sessionIdCookieName=warnings-ng-devenv
- JAVA_OPTS= -Dstapler.jelly.noCache=true -Dhudson.remoting.ClassFilter=com.google.common.collect.ImmutableListMultimap -DexecutableWar.jetty.disableCustomSessionIdCookieName=true -DexecutableWar.jetty.sessionIdCookieName=codingstyle
user: ${CURRENT_UID}
restart: unless-stopped
java11-agent:
build: ./docker/images/java11-agent
depends_on:
- jenkins-controller
key-generator:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: ["CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1"]
# Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5

key-generator:
container_name: key-generator
build:
context: docker/images/key-generator
stdin_open: true
tty: true
# The entrypoint script generates the SSH keys and outputs them to the /ssh-dir directory.
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
# The healthcheck command checks if the conductor_ok file exists in the /ssh-dir directory.
healthcheck:
test: ["CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1"]
# Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5

java-agent:
container_name: java-agent
build: docker/images/java-agent
depends_on:
key-generator:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1"]
# Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
- ${HOME}/.m2/repository:/home/jenkins/.m2/repository # Mounts the local Maven repository to the /home/jenkins/.m2 path inside the container

volumes:
agent-ssh-dir:
name: agent-ssh-dir # Creates a named volume called agent-ssh-dir
jenkins_home:
name: jenkins_home # Creates a named volume called jenkins_home
external: true
27 changes: 27 additions & 0 deletions docker/images/java-agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM jenkins/ssh-agent:latest-jdk21

# Install prerequisites for Java and Maven
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install Maven
ARG MAVEN_VERSION=3.9.8

SHELL ["/bin/bash", "-eo", "pipefail", "-c"]
RUN curl -sS -L -O --output-dir /tmp/ --create-dirs https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
&& printf "%s" "$(sha512sum /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz)" | sha512sum -c - \
&& curl -sS -L -O --output-dir /tmp/ --create-dirs https://downloads.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512 \
&& printf "%s /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" "$(cat /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512)" | sha512sum --check --status - \
&& tar xzf "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -C /opt/ \
&& rm "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
&& ln -s /opt/apache-maven-${MAVEN_VERSION} /opt/maven \
&& ln -s /opt/maven/bin/mvn /usr/bin/mvn \
&& mkdir -p /etc/profile.d \
&& echo "export JAVA_HOME=$JAVA_HOME \n \
export M2_HOME=/opt/maven \n \
export PATH=${M2_HOME}/bin:${PATH}" > /etc/profile.d/maven.sh
ENV M2_HOME="/opt/maven"
ENV PATH="${M2_HOME}/bin/:${PATH}"
RUN echo "PATH=${PATH}" >> /etc/environment && chown -R jenkins:jenkins "${JENKINS_AGENT_HOME}"
65 changes: 0 additions & 65 deletions docker/images/java11-agent/Dockerfile

This file was deleted.

27 changes: 0 additions & 27 deletions docker/images/java11-agent/docker-entrypoint.sh

This file was deleted.

Loading