jenkins本身提供了一套插件的管理机制,这些插件允许可插拨形式存在。jenkins插件虽然能提供很多种插件,但还是不能满足我们持续集成的需要,所以需要定制一些插件来支撑整个持续集成平台的运行。
Jenkins运行周期:
1.checkout -check out出源码
2.Pre-build - 预编译
3.Build wrapper-准备构建的环境,设置环境变量等
4.Builder runs -执行构建,比如调用calling Ant, Make 等等
5.Recording - 记录输出,如测试结果
6.Notification - 通知成员
Jenkins提供的扩展点:
为了支撑插件能在各个生命周期中运行,jenkins提供了各种扩展点,我们主类必须要extends一个扩展点;针对现状,基本上只需要使用Notifier,与builder这两个扩展点;详细如下:
1.Builder:这个扩展点支撑的是构建这个阶段需要做的事情,包括prebuild postbuid、构建环境等步骤,例如我们更换slave机器的hosts插件
2.Notifier:包括通知、消息通知等情况,我们的sendnapolimessage是基于这种扩展点来开发的
具体扩展点说明请参考:Extension+points
插件开始手册:Plugin+tutorial
下面说一下开发流程:
开发环境需要安装maven和jdk环境。

1.maven配置

配置maven的settings.xml配置文件

<settings>
  <pluginGroups>
    <pluginGroup>org.jenkins-ci.tools</pluginGroup>
  </pluginGroups>
   <mirrors>  
    <mirror>  
      <id>repo.jenkins-ci.org</id>  
     <url>http://repo.jenkins-ci.org/public/</url>  
     <mirrorOf>m.g.o-public</mirrorOf>  
    </mirror>  
  </mirrors> 
  <profiles>
<!-- Give access to Jenkins plugins -->
    <profile>
      <id>jenkins</id>
      <activation>
        <activeByDefault>true</activeByDefault> <!-- change this to false, if you don't like to have it on per default -->
      </activation>
      <repositories>
        <repository>
          <id>repo.jenkins-ci.org</id>
          <url>http://repo.jenkins-ci.org/public/</url>
        </repository>
      </repositories>  
      <pluginRepositories>
        <pluginRepository>
          <id>repo.jenkins-ci.org</id>
          <url>http://repo.jenkins-ci.org/public/</url>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
</settings>

2.创建插件maven项目

运行命令:mvn -cpu hpi:create
该操作需要你输入一些参数,比如说groupid=cn.slimsmart.jenkins.plugin,artifactid=helloword。之后会创建一个新的插件模板便于开发者之后的开发工作。
创建完成使用mvn clean package试试是否可以打包完成。
注:我遇见javadoc: 错误 - java.lang.IllegalArgumentException错误,原因是在解析环境变量classpath时使用了%JAVA_HOME%导致,修改classpath不适用变量就解决了。
创建的项目结构如下:

pom.xml:Maven的构建配置文件
src/main/java:Java源文件目录
src/main/resources:插件Jelly/Grovy视图
src/main/webapps:插件的静态资源如images和html文件

生成的代码中包含一个HelloWorldBuilder模板。当用户配置项目并启用此构建器时{@link DescriptorImpl#newInstance(StaplerRequest)}会被调用创建一个{@link HelloWorldBuilder} 实例创建实例通过使用持久化到项目配置XML XStream,所以这样可以使用实例字段(如{@link #name})记住配置。执行构建时,{@link #perform}方法将被调用。

3.调试运行

增加远程调试

#window
set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
#linux
#export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
mvn hpi:run -Djetty.port=8090 -Dhpi.prefix=/jenkins
#hpi.prefix设置context path

可以在一运行的jenkins的web界面中由Manage Jenkins>Manage Plugins>Advanced上传插件。
设置及构建运行
这里写图片描述
这里写图片描述
这里写图片描述

4.实例代码

package cn.slimsmart.jenkins.plugin.helloword;
import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.util.FormValidation;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;

import javax.servlet.ServletException;
import java.io.IOException;

/**
 * <p>
 * 当用户配置项目并启用此构建器时{@link DescriptorImpl#newInstance(StaplerRequest)}会被调用创建一个{@link HelloWorldBuilder} 实例
 * 创建实例通过使用持久化到项目配置XML XStream,所以这样可以使用实例字段(如{@link #name})记住配置。
 * 执行构建时,{@link #perform}方法将被调用。
 * </p>
 */
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {

    private final String name;

    // config.jelly中的textbox字段必须与“DataBoundConstructor”中的参数名匹配
    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
    }

    /**
     * 我们将在{@code config.jelly}中使用它。
     */
    public String getName() {
        return name;
    }

    /**
     * 执行构建时,perform方法将被调用
     * Build参数是描述了当前任务的一次构建,通过它可以访问到一些比较重要的模型对象如:project当前项目的对象、workspace构建的工作空间、Result当前构建步骤的结果。
     * Launcher参数用于启动构建。
     * BuildListener该接口用于检查构建过程的状态(开始、失败、成功..),通过它可以在构建过程中发送一些控制台信息给jenkins。
     * perform方法的返回值告诉jenkins当前步骤是否成功,如果失败了jenkins将放弃后续的步骤。
     */
    @Override
    public void perform(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) {
        // This is where you 'build' the project.
        // Since this is a dummy, we just say 'hello world' and call that a build.

        // This also shows how you can consult the global configuration of the builder
        if (getDescriptor().getUseFrench())
            listener.getLogger().println("Bonjour, "+name+"!");
        else{
            listener.getLogger().println("Hello, "+name+"!");
        }
        listener.getLogger().println("workspace="+workspace);
        listener.getLogger().println("number="+build.getNumber());
        listener.getLogger().println("url="+build.getUrl());
    }

    // Overridden for better type safety.
    // If your plugin doesn't really define any property on Descriptor,
    // you don't have to do this.
    //插件描述类
    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl)super.getDescriptor();
    }

    /**
     * Descriptor for {@link HelloWorldBuilder}. Used as a singleton.
     * The class is marked as public so that it can be accessed from views.
     *
     * <p>
     * See {@code src/main/resources/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly}
     * for the actual HTML fragment for the configuration screen.
     * 用于配置屏幕的实际HTML片段
     */
    @Extension // This indicates to Jenkins that this is an implementation of an extension point.
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        /**
         * 要保留全局配置信息,只需将其存储在一个字段中并调用save
         * 如果您不希望字段持久化,请使用{@code transient}。
         * global.jelly中checkbox的useFrench一至,全局配置
         */
        private boolean useFrench;

        /**
         * In order为了加载持久化的全局配置,你必须在构造函数中调用load() 
         */
        public DescriptorImpl() {
            load();
        }

        /**
         * Performs on-the-fly validation of the form field 'name'.
         * 执行表单字段“名称”的即时验证。
         * @param value
         *      This parameter receives the value that the user has typed.
         * @return
         *      Indicates the outcome of the validation. This is sent to the browser.
         *      <p>
         *      Note that returning {@link FormValidation#error(String)} does not
         *      prevent the form from being saved. It just means that a message
         *      will be displayed to the user. 
         */
        public FormValidation doCheckName(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0)
                return FormValidation.error("Please set a name");
            if (value.length() < 4)
                return FormValidation.warning("Isn't the name too short?");
            return FormValidation.ok();
        }

        // 表示此构建器是否可用于各种项目类型
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // Indicates that this builder can be used with all kinds of project types 
            return true;
        }

        /**
         * This human readable name is used in the configuration screen.
         */
        public String getDisplayName() {
            return "Say hello world";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            // To persist global configuration information,
            // set that to properties and call save().
            useFrench = formData.getBoolean("useFrench");
            // ^Can also use req.bindJSON(this, formData);
            //  (easier when there are many fields; need set* methods for this, like setUseFrench)
            save();
            return super.configure(req,formData);
        }

        /**
         * This method returns true if the global configuration says we should speak French.
         *
         * The method name is bit awkward because global.jelly calls this method to determine
         * the initial state of the checkbox by the naming convention.
         */
        public boolean getUseFrench() {
            return useFrench;
        }
    }
}

该实例使用了jenkins的Builder作为扩展点,实现了BuildStep,通过内部类DescripotorImpl添加@Extension声明,告诉系统该内部类是作为BuildStepDescriptor的扩展出现。
这里基本完成了扩展点的后台代码部分,但是扩展过程中还需要对前端页面进行扩张,这时就需要建立一个pcakage放置该扩展类对应的视图。Jenkins使用了Jelly页面渲染技术,这是一个基于XML的服务端页面渲染引擎,其将基于Jelly的xml标签转换为对应的Html标签并输出到客户端。模型对象的信息通过Jexl表达式被传递到页面上(相当于Jsp的JSTL)。jelly文件以.jelly为后缀,在hudson中使用类全名的形式来查找模型类对应的jelly页面文件,如名为src/main/java/cn/slimsmart/jenkins/plugin/helloword/HelloWorldBuilder.java的类,其对应的页面文件应该存在于src/main/resources/cn/slimsmart/jenkins/plugin/helloword/HelloWorldBuilder目录的下。
视图有三种:
1.全局配置(global.jelly->系统管理-系统设置)
2.Job配置(config.jelly->每个Job而言需要的配置信息)
3.还有就是使用帮助(help-字段名).html
关于jelly学习参考:Basic+guide+to+Jelly+usage+in+Jenkins
参考文章:
1.jenkins插件开发
2.浅析 Jenkins 插件开发
3.Jenkins插件开发入门

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐