Escrevendo um plug-in Jenkins
1. Visão geral
O Jenkins é um servidor de integração contínua de código aberto, que permite criar uma criação de plug-in personalizado para uma tarefa / ambiente específico.
Neste artigo, passaremos por todo o processo de criação de uma extensão que adiciona estatísticas à saída de construção, ou seja, número de classes e linhas de código.
2. Configuração
A primeira coisa a fazer é configurar o projeto. Luckily, Jenkins provides convenient Maven archetypes para isso.
Basta executar o comando abaixo em um shell:
mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin
Obteremos o seguinte resultado:
[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.)
Agora, escolha a primeira opção e defina grupo / artefato / pacote no modo interativo. Depois disso, é necessário fazer refinamentos empom.xml - pois contém entradas como<name>TODO Plugin</name>.
3. Jenkins Plugin Design
3.1. Pontos de extensão
Jenkins provides a number of extension points. Essas são interfaces ou classes abstratas que definem contratos para casos de uso específicos e permitem que outros plug-ins os implementem.
Por exemplo, toda compilação consiste em várias etapas, por exemplo “Checkout from VCS”,“Compile”,“Test”,“Assemble”, etc. Jenkins define o ponto de extensãohudson.tasks.BuildStep, para que possamos implementá-lo para fornecer uma etapa personalizada que pode ser configurada.
Outro exemplo éhudson.tasks.BuildWrapper - isso nos permite definir ações pré / pós.
Também temos um pluginEmail Extension não principal que define o ponto de extensãohudson.plugins.emailext.plugins.RecipientProvider, que permite fornecer destinatários de email. Um exemplo de implementação está disponível aqui:hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider.
Nota: há uma abordagem legada em que a classe de plug-in precisa estenderhudson.Plugin. No entanto,it’s now recommended to use extension points instead.
3.2. Inicialização do Plugin
É necessário dizer ao Jenkins sobre nossa extensão e como ela deve ser instanciada.
Primeiro, definimos uma classe interna estática dentro do plugin e a marcamos usando a anotaçãohudson.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";
}
}
}
Em segundo lugar, precisamos definir um construtor a ser usado para a instanciação do objeto do plugin e marcá-lo pela anotaçãoorg.kohsuke.stapler.DataBoundConstructor.
É possível usar parâmetros para isso. Eles são exibidos na IU e fornecidos automaticamente pelo Jenkins.
E.g. considere oMaven plugin:
@DataBoundConstructor
public Maven(
String targets,
String name,
String pom,
String properties,
String jvmOptions,
boolean usePrivateRepository,
SettingsProvider settings,
GlobalSettingsProvider globalSettings,
boolean injectBuildVariables) { ... }
Ele é mapeado para a seguinte IU:
Também é possível usar a anotaçãoorg.kohsuke.stapler.DataBoundSetter com setters.
4. Implementação de Plugin
Pretendemos coletar estatísticas básicas do projeto durante uma construção, então,hudson.tasks.BuildWrapper é o caminho certo aqui.
Vamos implementar:
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, agora precisamos implementar a funcionalidade real.
Vamos definir uma classe de domínio para as estatísticas do projeto:
class ProjectStats {
private int classesNumber;
private int linesNumber;
// standard constructors/getters
}
E escreva o código que constrói os dados:
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);
}
Por fim, precisamos mostrar as estatísticas para os usuários finais. Vamos criar um modelo HTML para isso:
$PROJECT_NAME$
Project $PROJECT_NAME$:
Classes number
Lines number
$CLASSES_NUMBER$
$LINES_NUMBER$
E preencha-o durante a compilação:
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. Uso
É hora de combinar tudo o que criamos até agora - e ver em ação.
Presume-se que o Jenkins esteja instalado e funcionando no ambiente local. Caso contrário, consulteinstallation details.
5.1. Adicione o plug-in ao Jenkins
Agora, vamos construir nosso plugin:
mvn install
Isso criará um arquivo*.hpi no diretóriotarget. Precisamos copiá-lo para o diretório de plug-ins do Jenkins (~/.jenkins/plugin por padrão):
cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/
Por fim, vamos reiniciar o servidor e garantir que o plug-in seja aplicado:
-
Abra o painel de CI emhttp://localhost:8080
-
Navegue atéManage Jenkins | Manage Plugins | Installed
-
Encontre nosso plugin
5.2. Configurar trabalho do Jenkins
Vamos criar um novo trabalho para um projeto de código aberto Apache commons-lang e configurar o caminho para seu repositório Git lá:
Também precisamos ativar nosso plugin para isso:
6. Conclusão
Neste tutorial, criamos um pluginJenkins do zero e garantimos que ele funcione.
Naturalmente, não cobrimos todos os aspectos do desenvolvimento de extensões de CI, apenas fornecemos uma visão geral básica, ideias de design e uma configuração inicial.
E, como sempre, o código-fonte pode ser encontradoover on GitHub.