Escrevendo um plug-in Jenkins

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:

image

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:

  1. Abra o painel de CI emhttp://localhost:8080

  2. Navegue atéManage Jenkins | Manage Plugins | Installed

  3. Encontre nosso plugin

image

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á:

image

Também precisamos ativar nosso plugin para isso:

image

5.3. Verifique os resultados

Está tudo pronto agora, vamos verificar como funciona.

Podemos construir o projeto e navegar até os resultados. Podemos ver que um arquivostats.html está disponível aqui:

image

Vamos abrir:

image

Isso é o que esperávamos - uma única classe com três linhas de código.

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.