Nodejs调用Jenkins的API进行自动化部署

1,024 阅读3分钟

本文主要讲解如何使用nodejs调用Jenkins的API进行自动化部署。

应用场景

目前团队中前端项目的部署都基于Jenkins,每次发布时候前端需要登录Jenkins的管理后台,找到对应的JOB然后手动执行任务。

在测试期间,会频繁的做合并分支和手动执行部署Jenkins的任务的操作。

本着重复的低价值的工作应该交给工具的理念,我们期望简化这一系列的操作,最好能自动化。

我们有两个选择:

  1. 通过设置Jenkins任务的定期运行解决;
  2. 将部署前的所有操作都集成到命令行工具中,实现本地一行命令完成部署的所有操作;

我们选择了第2种方案,因为本地命令行工具基于Nodejs开发,所以需要在完成分支合并后调用Jenkins API进行项目的打包部署。

思路

  1. 通过API触发打包任务;
  2. 获取日志流输出到当前控制台;
  3. 打包任务完成通过API获取是否执行成功;

实施

我们找到了Jenkins的NPM包 www.npmjs.com/package/jen…

我们主要使用以下几个API:

jenkins.job.build发起打包任务;

jenkins.build.logStream 获取日志流;

jenkins.build.get 任务结束时候获取任务执行结果;

但是有个问题,jenkins.job.build执行后只能获取到 queueId,但是获取日志流需要用到 buildId,因此我们需要用 queueIdbuildId,所以我们找到了另外一个API:

jenkins.queue.item 通过 queueId查询当前任务的详情;

但是我们又要面临另外一个问题,jenkins.job.build发起打包任务是一个异步操作,什么时候创建任务成功并加入队列,这个时间是不确定的。但是要通过 queueId查询当前任务的详情必须是在任务创建完成才可以查下到。

我们的处理办法,做一个轮询去查询任务的详情,查询到任务详情之后再去获取日志流。

核心脚本如下:

const jenkinsApi = require('jenkins');
...
async function publish(options) {
  const { jenkinsHost, jenkinsUser, jenkinsToken, jenkinsJob, jenkinsParameters, buildBranch, globalConfig, config, prod } = options;
  if (!config.jenkinsUser || !config.jenkinsToken) {
    log.error('Jenkins账号和密码输入异常,打包终止!');
    return
  }
  const baseUrl = `http://${config.jenkinsUser}:${config.jenkinsToken}@${config.jenkinsHost}`;
  // crumbIssuer 默认false, true 启用CSRF保护支持
  const jenkins = jenkinsApi({ baseUrl, crumbIssuer: true });
  const jobName = config.jenkinsJob
  let count = 1;

  return new Promise((resolve, reject) => {
    const params = {
      name: jobName
    };
    // 启动Jenkins打包任务
    jenkins.job.build(
      params,
      function (err, queueId) {
        if (err) {
          reject(err);
          log.error(err.message);
          return;
        };
        log.info(`============ Jenkins任务[${jobName}]开始 ============`)
        log.notice('当前任务的队列ID:' + queueId);
        // 2秒之后再进行log的获取,防止buildId还未生成的情况
        setTimeout(() => { exceLog(queueId) }, 2000);
      });
    // 轮询 通过queueId获取任务详情
    function exceLog(queueId) {
      jenkins.queue.item(queueId, function (err, data) {
        if (err) {
          reject(err);
          throw err
        };
        // 获取任务ID次数超过10次则终止
        if (count > 10) {
          log.error(`Jenkins任务[${jobName}]获取buildId请求超时!`)
          reject(`Jenkins任务[${jobName}]获取buildId请求超时!`);
          return
        }
        if (!data.executable) {
          count++;
          // 2秒之后再进行log的获取,防止buildId还未生成的情况
          setTimeout(() => { exceLog(queueId) }, 2000);
          return;
        }
        const lastBuildNumber = data.executable.number
        log.notice('打包任务ID:' + lastBuildNumber);
        //获取到 lastBuildNumber 则进行下一步的日志输出
        logStream(lastBuildNumber)
      });
    }
    // 日志流处理
    function logStream(buildNumber) {
      var logAction = jenkins.build.logStream({
        name: jobName,
        number: buildNumber
      });

      logAction.on('data', function (text) {
        process.stdout.write(text);
      });

      logAction.on('error', function (err) {
        log.error(err);
        reject(err);
      });

      logAction.on('end', async () => {
        log.info('============ Jenkins任务结束 ============')
        // 任务结束获取任务是否执行成功
        jenkins.build.get(jobName, buildNumber, async (err, data) => {
          if (err) {
            reject(err);
            throw err
          };
          log.verbose("build result data", data);
          const result = data.result || '';
          resolve(result.toUpperCase() == 'SUCCESS' ? true : false);
        });
      });
    }
  });
}

以上,即可完成Nodejs调用Jenkins任务完成自动化部署。