自动化工具 Jenkins RESTful API 定制化

胡刚 · 2016年10月06日 · 最后由 刘锦润 回复于 2018年07月18日 · 5055 次阅读
本帖已被设为精华帖!

1.背景

Jenkins 具有丰富的插件生态,足以满足我们日常工作的需求;如果我们想通过具体的 Jenkins 任务直接对外提供服务,而我们不想将内部的具体实现对外暴露 (否则,需添加对应的用户权限,通过页面执行 job);可以对外直接提供接口,第三方直接调用接口(比如提供给开发,提测前回归冒烟用例集),执行相应的 Jenkins 任务并获取任务结果。

2.Jenkins 原生 Remote access API

Jenkins 提供了原生的 Remote accsee API, 主要用来做:

1.检索Jenkins中信息
2.触发新构建
3.创建/复制任务

实例:

获取项目FastTest信息:
curl -u user:pwd -X POST http://ip:port/jenkins/job/FastTest/api/xml
并支持Xpath进行信息提取,详见 https://issues.jenkins-ci.org/browse/JENKINS-626

触发新的构建(带参数构建):
curl -u user:pwd -X POST http://ip:port/jenkins/job/FastTest/buildWithParameters -d "environment=运行环境&module=模块&mailer=收件人"

该接口响应头信息:
* upload completely sent off: 94 out of 94 bytes
< HTTP/1.1 201 Created
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< Location: http://ip:port/jenkins/queue/item/111/
< Content-Length: 0
< Date: Wed, 05 Oct 2016 07:39:39 GMT
< 
* Connection #0 to host 10.210.228.50 left intact

但是该接口无任何返回信息。

完整的 Remote access API wiki 可以通过如下获取:

curl  -u user:pwd -X POST http://ip:port/jenkins/job/FastTest/api/ > /Users/hugang/Desktop/jenkins_api_wiki.html

原生的接口,功能较为简单、不友好,需封装 Jenkins 的 api,提供有价值的接口返回信息。

3.Jenkins api wrappers

Jenkins api wrappers 有多种编程语言实现, 可以很方便的操作 Jenkins,具体如下:

Node.js: https://www.npmjs.com/package/jenkins-api

ruby: https://rubygems.org/gems/jenkins_api_client

python: https://pypi.python.org/pypi/python-jenkins/

java: https://github.com/jenkinsci/java-client-api

本文将使用 java 的 Jenkins api wrappers:java-client-api 作为介绍,定制适合自己的 Jenkins RESTful API.

4.定制 Jenkins RESTful API

新建一个 Maven webapp 工程,工程 pom.xml 中添加 java-client-api 依赖:

<dependency>
  <groupId>com.offbytwo.jenkins</groupId>
  <artifactId>jenkins-client</artifactId>
  <version>0.3.6</version>
</dependency>

项目使用 Spring RESTful web service

Controller 层

执行用例接口:

package com.weibo.qa.fasttestapi.controller;


import com.weibo.qa.fasttestapi.model.RunInfo;
import com.weibo.qa.fasttestapi.service.EmailSuffixService;
import com.weibo.qa.fasttestapi.service.FastTestJenkinsService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * 接口/fasttest/run:
 *
 * params:
 *
 * env 测试环境
 * module 模块
 * email 收件人
 *
 *
 * result:
 *
 * {"result":true,"jobId":int值}
 *
 * Created by hugang on 16/9/19.
 */
@RestController
public class RunController {

    @RequestMapping(value = "/fasttest/run.json", method = RequestMethod.POST)
    public RunInfo runFasttest(@RequestParam(value = "env", required = true) String env,
                        @RequestParam(value = "module", required = true, defaultValue = "BVT") String module,
                        @RequestParam(value = "email", required = true, defaultValue = "hugang") String email){

        FastTestJenkinsService fastTestJenkinsService = new FastTestJenkinsService();
        // 增加邮箱后缀
        EmailSuffixService emailSuffix = new EmailSuffixService();

        String receiver = emailSuffix.addEmailSuffix(email);

        int jobId = fastTestJenkinsService.buildJob(env, module, receiver);

        RunInfo runInfo = new RunInfo();

        runInfo.setJobId(jobId);
        runInfo.setResult(true);

        return runInfo;

    }

}

根据 jobId 判断是否执行结束接口:

package com.weibo.qa.fasttestapi.controller;

import com.weibo.qa.fasttestapi.model.FinishInfo;
import com.weibo.qa.fasttestapi.service.FastTestJenkinsService;
import org.springframework.web.bind.annotation.*;

/**
 * Created by hugang on 16/9/20.
 */

@RestController
public class FinishController {

    @RequestMapping(value = "/fasttest/finish.json",method = RequestMethod.GET)
    public @ResponseBody
    FinishInfo isFinish(@RequestParam(value = "job_id", required = true)int job_id) {

        FastTestJenkinsService fastTestJenkinsService = new FastTestJenkinsService();

        boolean isFinish = fastTestJenkinsService.isJobFinish(job_id);

        FinishInfo finishInfo = new FinishInfo();

        finishInfo.setFinished(isFinish);

        return finishInfo;

    }
}


service 层

执行具体的业务:FastTestJenkinsService.java


package com.weibo.qa.fasttestapi.service;

import com.offbytwo.jenkins.JenkinsServer;
import com.offbytwo.jenkins.model.Job;
import com.offbytwo.jenkins.model.JobWithDetails;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by hugang on 16/9/19.
 */
public class FastTestJenkinsService {



    /**
     *
     * @param env 测试环境
     * @param module 测试模块
     * @param email  收件人
     * @return jobId
     */
    public int buildJob(String env, String module, String email) {

        int buildNumber = 0;
        try{
            JenkinsServer jenkinsServer = new JenkinsServer(new URI("http://ip:port/jenkins"), "test", "pwd");

            Map<String, Job> jobs = jenkinsServer.getJobs();

            JobWithDetails job = jobs.get("FastTest").details();

            // 即将执行任务的jobId
            buildNumber = job.getNextBuildNumber();

            // 参数化构建
            Map<String, String> params = new HashMap<String, String>();
            params.put("environment", env);
            params.put("module", module);
            params.put("mailer", email);
            job.build(params);


        }catch (Exception e){
            e.printStackTrace();
        } finally {

        }
        return buildNumber;
    }


    /**
     *
     * @param jobId
     * @return 是否执行结束
     */
    public boolean isJobFinish(int jobId)  {

        if(jobId <=0){
            throw new IllegalArgumentException("jodId must greater than 0!");
        }


        try{

            JenkinsServer jenkinsServer = new JenkinsServer(new URI("http://ip:port/jenkins"), "user", "pwd");

            Map<String, Job> jobs = jenkinsServer.getJobs();

            JobWithDetails job = jobs.get("FastTest").details();

            boolean isBuilding = job.getBuildByNumber(jobId).details().isBuilding();

            return !isBuilding;

        } catch (Exception e){
            e.printStackTrace();
        } finally{

        }

        return false;
    }

model 层

执行用例返回数据:RunInfo.java

package com.weibo.qa.fasttestapi.model;

/**
 * 执行用例接口返回的数据
 * Created by hugang on 16/9/19.
 */
public class RunInfo {
    private boolean result;
    private int jobId;


    public boolean isResult() {
        return result;
    }

    public void setResult(boolean result) {
        this.result = result;
    }

    public int getJobId() {
        return jobId;
    }

    public void setJobId(int jobId) {
        this.jobId = jobId;
    }
}

查看 jobId 对应的任务是否结束:FinishInfo.java

package com.weibo.qa.fasttestapi.model;

/**
 * 查看执行用例是否结束
 * Created by hugang on 16/9/19.
 */
public class FinishInfo {
    private boolean finished;

    public boolean isFinished() {
        return finished;
    }

    public void setFinished(boolean finished) {
        this.finished = finished;
    }
}

调用接口如下图:

5.项目开源

有兴趣的同学,可以自己动手试试,开源地址:https://github.com/neven7/fasttest-api

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 12 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 10月08日 15:11

加精理由: 工作中实用技能. 方便产品流程中的多系统交互.

非常赞,这样非常灵活,扩展起来很方便!。

很实用

hi,我有两个问题想要请教一下,谢谢

  1. 在 buildJob() 中设置的参数 environment,module 和 mailer 是你在 jenkins 中建立 job 的时候设置的参数构建中的参数吗?
  2. 另外我单独执行一个不需要参数的 job 的时候, JenkinsServer jenkins = new JenkinsServer(new URI("http://***/jenkins"), "admin", ""); Map jobs = jenkins.getJobs(); JobWithDetails job = jobs.get("*MonitorJob").details(); buildNumber = job.getNextBuildNumber(); System.out.println(buildNumber); job.build(); 在最后一步 job.build() 报错了,我查了很久都没有查到原因,你有碰到过吗?

@AriesHuang 英文代码里面调用的是 Jenkins 的 build 入口,并没有触发 build

#5 楼 @AriesHuang 1.对的,你要在 Jenkins 上新建一个项目,是参数化构建的。2.你写了 main 函数来执行?你的 31 行是什么内容?

#6 楼 @huangejuan job.build() 就是触发 Jenkins 去执行 build 了。

帅气帅气,才看到这文章,后续做成测试平台,是非常有意义

—— 来自 TesterHome 官方 安卓客户端

谢谢
请教下楼主,Jenkins api 貌似没有回调,调用构建后要么阻塞要么直接返回构建中
比如我要想调用之后异步返回,当任务构建完时告诉我去拿构建结果,有什么好的方法呢

William Master-Slave 结构的 jenkins 平台维护 中提及了此贴 07月03日 21:14
胡刚 #12 · 2017年08月21日 Author
cctodd 回复

你写个轮询调是否结束的接口,结束了你就去取内容。

胡刚 回复

谢谢
更新下办法,Jenkins 设置有构建后操作,可以发个请求
自己写接口处理这个请求,可以免去轮询

cctodd 回复

刚好也遇到这个问题,老哥方便说一下具体的接口吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册