Ein Jenkins Plugin schreiben
1. Überblick
Jenkins ist ein Open Source Continuous Integration-Server, mit dem Sie ein benutzerdefiniertes Plugin für bestimmte Aufgaben / Umgebungen erstellen können.
In diesem Artikel werden wir den gesamten Prozess des Erstellens einer Erweiterung durchlaufen, die der Build-Ausgabe Statistiken hinzufügt, nämlich die Anzahl der Klassen und Codezeilen.
2. Konfiguration
Das erste, was zu tun ist, ist das Projekt einzurichten. Luckily, Jenkins provides convenient Maven archetypes dafür.
Führen Sie einfach den folgenden Befehl aus einer Shell aus:
mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin
Wir erhalten die folgende Ausgabe:
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart
(org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> io.jenkins.archetypes:empty-plugin (Skeleton of
a Jenkins plugin with a POM and an empty source tree.)
2: remote -> io.jenkins.archetypes:global-configuration-plugin
(Skeleton of a Jenkins plugin with a POM and an example piece
of global configuration.)
3: remote -> io.jenkins.archetypes:hello-world-plugin
(Skeleton of a Jenkins plugin with a POM and an example build step.)
Wählen Sie nun die erste Option und definieren Sie Gruppe / Artefakt / Paket im interaktiven Modus. Danach müssen diepom.xml verfeinert werden, da sie Einträge wie<name>TODO Plugin</name> enthalten.
3. Jenkins Plugin Design
3.1. Erweiterungspunkte
Jenkins provides a number of extension points. Dies sind Schnittstellen oder abstrakte Klassen, die Verträge für bestimmte Anwendungsfälle definieren und es anderen Plugins ermöglichen, diese zu implementieren.
Beispielsweise besteht jeder Build aus einer Anzahl von Schritten, z. “Checkout from VCS”,“Compile”,“Test”,“Assemble”, usw. Jenkins definiert den Erweiterungspunkt vonhudson.tasks.BuildStep, sodass wir ihn implementieren können, um einen benutzerdefinierten Schritt bereitzustellen, der konfiguriert werden kann.
Ein weiteres Beispiel isthudson.tasks.BuildWrapper - dies ermöglicht es uns, Vor- / Nachaktionen zu definieren.
Wir haben auch ein Nicht-Core-PluginEmail Extension, das den Erweiterungspunkthudson.plugins.emailext.plugins.RecipientProviderdefiniert und das Bereitstellen von E-Mail-Empfängern ermöglicht. Eine Beispielimplementierung finden Sie hier:hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider.
Hinweis: Es gibt einen Legacy-Ansatz, bei dem die Plugin-Klassehudson.Plugin erweitern muss. it’s now recommended to use extension points instead.
3.2. Plugin-Initialisierung
Es ist notwendig, Jenkins über unsere Erweiterung zu informieren und darüber, wie sie instanziiert werden sollte.
Zuerst definieren wir eine statische innere Klasse innerhalb des Plugins und markieren sie mit der Annotationhudson.Extension:
class MyPlugin extends BuildWrapper {
@Extension
public static class DescriptorImpl
extends BuildWrapperDescriptor {
@Override
public boolean isApplicable(AbstractProject, ?> item) {
return true;
}
@Override
public String getDisplayName() {
return "name to show in UI";
}
}
}
Zweitens müssen wir einen Konstruktor definieren, der für die Objektinstanziierung des Plugins verwendet werden soll, und ihn durch die Annotationorg.kohsuke.stapler.DataBoundConstructormarkieren.
Es ist möglich, Parameter dafür zu verwenden. Sie werden in der Benutzeroberfläche angezeigt und automatisch von Jenkins bereitgestellt.
E.g. Betrachten Sie dieMaven plugin:
@DataBoundConstructor
public Maven(
String targets,
String name,
String pom,
String properties,
String jvmOptions,
boolean usePrivateRepository,
SettingsProvider settings,
GlobalSettingsProvider globalSettings,
boolean injectBuildVariables) { ... }
Es ist der folgenden Benutzeroberfläche zugeordnet:
Es ist auch möglich, die Annotation vonorg.kohsuke.stapler.DataBoundSettermit Setzern zu verwenden.
4. Plugin-Implementierung
Wir beabsichtigen, grundlegende Projektstatistiken während eines Builds zu sammeln, daher isthudson.tasks.BuildWrapper der richtige Weg, um hierher zu gelangen.
Implementieren wir es:
class ProjectStatsBuildWrapper extends BuildWrapper {
@DataBoundConstructor
public ProjectStatsBuildWrapper() {}
@Override
public Environment setUp(
AbstractBuild build,
Launcher launcher,
BuildListener listener) {}
@Extension
public static class DescriptorImpl extends BuildWrapperDescriptor {
@Override
public boolean isApplicable(AbstractProject, ?> item) {
return true;
}
@Nonnull
@Override
public String getDisplayName() {
return "Construct project stats during build";
}
}
}
Ok, jetzt müssen wir die eigentliche Funktionalität implementieren.
Definieren wir eine Domänenklasse für die Projektstatistiken:
class ProjectStats {
private int classesNumber;
private int linesNumber;
// standard constructors/getters
}
Und schreiben Sie den Code, der die Daten erstellt:
private ProjectStats buildStats(FilePath root)
throws IOException, InterruptedException {
int classesNumber = 0;
int linesNumber = 0;
Stack toProcess = new Stack<>();
toProcess.push(root);
while (!toProcess.isEmpty()) {
FilePath path = toProcess.pop();
if (path.isDirectory()) {
toProcess.addAll(path.list());
} else if (path.getName().endsWith(".java")) {
classesNumber++;
linesNumber += countLines(path);
}
}
return new ProjectStats(classesNumber, linesNumber);
}
Schließlich müssen wir den Endbenutzern die Statistiken anzeigen. Erstellen wir dafür eine HTML-Vorlage:
$PROJECT_NAME$
Project $PROJECT_NAME$:
Classes number
Lines number
$CLASSES_NUMBER$
$LINES_NUMBER$
Und füllen Sie es während des Builds:
public class ProjectStatsBuildWrapper extends BuildWrapper {
@Override
public Environment setUp(
AbstractBuild build,
Launcher launcher,
BuildListener listener) {
return new Environment() {
@Override
public boolean tearDown(
AbstractBuild build, BuildListener listener)
throws IOException, InterruptedException {
ProjectStats stats = buildStats(build.getWorkspace());
String report = generateReport(
build.getProject().getDisplayName(),
stats);
File artifactsDir = build.getArtifactsDir();
String path = artifactsDir.getCanonicalPath() + REPORT_TEMPLATE_PATH;
File reportFile = new File("path");
// write report's text to the report's file
}
};
}
}
5. Verwendungszweck
Es ist Zeit, alles, was wir bisher geschaffen haben, zu kombinieren - und in Aktion zu sehen.
Es wird davon ausgegangen, dass Jenkins in der lokalen Umgebung aktiv ist. Bitte beziehen Sie sich ansonsten aufinstallation details.
5.1. Fügen Sie das Plugin zu Jenkins hinzu
Jetzt erstellen wir unser Plugin:
mvn install
Dadurch wird eine*.hpi-Datei imtarget-Verzeichnis erstellt. Wir müssen es in das Jenkins-Plugins-Verzeichnis kopieren (~/.jenkins/plugin standardmäßig):
cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/
Zuletzt starten wir den Server neu und stellen sicher, dass das Plugin angewendet wird:
-
Öffnen Sie das CI-Dashboard beihttp://localhost:8080
-
Navigieren Sie zuManage Jenkins | Manage Plugins | Installed
-
Finden Sie unser Plugin
5.2. Konfigurieren Sie Jenkins Job
Erstellen wir einen neuen Job für ein Open-Source-Projekt von Apache commons-lang und konfigurieren Sie dort den Pfad zu seinem Git-Repo:
Dafür müssen wir auch unser Plugin aktivieren:
5.3. Überprüfen Sie die Ergebnisse
Wir sind jetzt fertig. Lassen Sie uns überprüfen, wie es funktioniert.
Wir können das Projekt erstellen und zu den Ergebnissen navigieren. Wir können sehen, dass einestats.html-Datei hier verfügbar ist:
Lass es uns öffnen:
Das haben wir erwartet - eine einzelne Klasse mit drei Codezeilen.
6. Fazit
In diesem Tutorial haben wir einJenkins-Plugin von Grund auf neu erstellt und sichergestellt, dass es funktioniert.
Natürlich haben wir nicht alle Aspekte der Entwicklung von CI-Erweiterungen behandelt, sondern lediglich einen grundlegenden Überblick, Entwurfsideen und eine erste Einrichtung bereitgestellt.
Und wie immer kann der Quellcodeover on GitHub gefunden werden.