Menulis Plugin Jenkins

1. Ikhtisar

Jenkins adalah server Integrasi Berkelanjutan sumber terbuka, yang memungkinkan pembuatan plugin kustom untuk tugas / lingkungan tertentu.

Di artikel ini, kita akan membahas seluruh proses pembuatan ekstensi yang menambahkan statistik ke output build, yaitu jumlah kelas dan baris kode.

2. Penyiapan

Hal pertama yang harus dilakukan adalah menyiapkan proyek. Untungnya, Jenkins menyediakan arketipe Maven yang nyaman untuk itu.

Jalankan saja perintah di bawah ini dari shell:

mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin

Kami akan mendapatkan output berikut:

[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.)

Sekarang, pilih opsi pertama dan tentukan grup / artefak / paket dalam mode interaktif. Setelah itu, perlu dilakukan perbaikan pada pom.xml - karena mengandung entri seperti TODO Plugin .

3. Desain Plugin Jenkins

3.1. Poin Ekstensi

Jenkins memberikan sejumlah poin ekstensi. Ini adalah antarmuka atau kelas abstrak yang menentukan kontrak untuk kasus penggunaan tertentu dan memungkinkan plugin lain untuk mengimplementasikannya.

Misalnya, setiap build terdiri dari sejumlah langkah, misalnya "Checkout from VCS" , "Compile" , "Test", "Assemble", dll. Jenkins menentukan titik ekstensi hudson.tasks.BuildStep , sehingga kita dapat mengimplementasikannya untuk menyediakan langkah kustom yang dapat dikonfigurasi.

Contoh lainnya adalah hudson.tasks.BuildWrapper - ini memungkinkan kita untuk menentukan tindakan pra / posting.

Kami juga memiliki plugin Ekstensi Email non-inti yang menentukan titik ekstensi hudson.plugins.emailext.plugins.RecipientProvider , yang memungkinkan menyediakan penerima email. Contoh implementasi tersedia di sini: hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider .

Catatan: ada pendekatan lama di mana kelas plugin perlu memperluas hudson.Plugin . Namun, sekarang disarankan untuk menggunakan titik ekstensi sebagai gantinya.

3.2. Inisialisasi Plugin

Penting untuk memberi tahu Jenkins tentang ekstensi kami dan bagaimana itu harus dibuat.

Pertama, kita mendefinisikan kelas dalam statis di dalam plugin dan menandainya menggunakan hudson.Extension annotation:

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"; } } }

Kedua, kita perlu mendefinisikan konstruktor yang akan digunakan untuk pembuatan instance objek plugin dan menandainya dengan anotasi org.kohsuke.stapler.DataBoundConstructor .

Mungkin menggunakan parameter untuk itu. Mereka ditampilkan di UI dan dikirim secara otomatis oleh Jenkins.

Misalnya pertimbangkan plugin Maven:

@DataBoundConstructor public Maven( String targets, String name, String pom, String properties, String jvmOptions, boolean usePrivateRepository, SettingsProvider settings, GlobalSettingsProvider globalSettings, boolean injectBuildVariables) { ... }

Itu dipetakan ke UI berikut:

Ini juga memungkinkan untuk menggunakan anotasi org.kohsuke.stapler.DataBoundSetter dengan penyetel.

4. Implementasi Plugin

Kami bermaksud untuk mengumpulkan statistik proyek dasar selama pembuatan, jadi, hudson.tasks.BuildWrapper adalah cara yang tepat untuk pergi ke sini.

Mari terapkan:

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, sekarang kita perlu mengimplementasikan fungsionalitas sebenarnya.

Mari tentukan kelas domain untuk statistik proyek:

class ProjectStats { private int classesNumber; private int linesNumber; // standard constructors/getters }

Dan tulis kode yang membangun data:

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); }

Akhirnya, kami perlu menunjukkan statistik kepada pengguna akhir. Mari buat template HTML untuk itu:

    $PROJECT_NAME$   Project $PROJECT_NAME$: 
    
Classes number Lines number
$CLASSES_NUMBER$ $LINES_NUMBER$

Dan mengisinya selama build:

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. Penggunaan

Saatnya menggabungkan semua yang telah kita buat sejauh ini - dan melihatnya beraksi.

Diasumsikan bahwa Jenkins aktif dan berjalan di lingkungan lokal. Silakan lihat detail instalasi jika tidak.

5.1. Tambahkan Plugin ke Jenkins

Sekarang, mari buat plugin kita:

mvn install

Ini akan membuat file * .hpi di direktori target . Kita perlu menyalinnya ke direktori plugin Jenkins ( ~ / .jenkins / plugin secara default):

cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/

Terakhir, mari mulai ulang server dan pastikan plugin diterapkan:

  1. Open CI dashboard at //localhost:8080
  2. Navigate to Manage Jenkins | Manage Plugins | Installed
  3. Find our plugin

5.2. Configure Jenkins Job

Let's create a new job for an open-source Apache commons-lang project and configure the path to its Git repo there:

We also need to enable our plugin for that:

5.3. Check the Results

We're all set now, let's check how it works.

We can build the project and navigate to the results. We can see that a stats.html file is available here:

Let's open it:

That's what we expected – a single class which has three lines of code.

6. Conclusion

In this tutorial, we created a Jenkins plugin from scratch and ensured that it works.

Biasanya, kami tidak mencakup semua aspek pengembangan ekstensi CI, kami hanya memberikan gambaran umum dasar, ide desain, dan penyiapan awal.

Dan, seperti biasa, kode sumber dapat ditemukan di GitHub.