问题

有时候,在本地提交完代码,接着需要将代码部署到测试坏境。

一般部署过程都需要自己登录到某个部署平台,手动去触发。(不包括有些可能直接push完代码就自动触发部署了)。

虽然这样手动触发操作很简单,但每次都打开网页,找项目,去操作,也不免有些麻烦。

思考

能不能在提交完代码,就接着在命令行,完成部署呢?

简单实现

这里以 jenkins 为例,说说我的处理过程。

1、打开 jenkins 中你要远程部署的项目界面,点击配置。

jenkins java无感发布 jenkins发布代码_远程


2、在配置中找到“构建触发器”栏目,在“身份验证令牌”中填入一段字符串。下面的小字是访问链接的拼写规则,原来远程构建触发器是通过 HTTP 协议访问链接实现的,接下来说说拼写规则。记得先将这步的修改应用,保存。

jenkins java无感发布 jenkins发布代码_jenkins_02


3、完成了这些,我们来说触发链接的拼写规则:

参数说明:
JENKINS_URL: jenkins 的域名或ip + 端口号
PEOJECT_NAME:项目名称
TOKEN_NAME:上一步配置的字符串

  • 不带参数:
JENKINS_URL/job/PROJECT_NAME/build?token=TOKEN_NAME
  • 带参数:
JENKINS_URL/job/PROJECT_NAME/buildWithParameters?token=TOKEN_NAME&KEY=value

4、拿着第三步拼好的链接,直接在浏览器中访问。

如果返回 201 状态码,恭喜已经成功触发部署了。
如果返回 403 状态码,那么可能你的 jenkin 开启了 CSRF 保护。
需要回到主界面 -> 系统管理 -> 全局安全配置 -> 找到 CSRF Protection 关掉就好了。

通过上面的简单操作,已经可以实现远程触发构建了,但是如果需要在命令行访问的话,我们还需要写一个简单的 shell 脚本。

#  你的 jenkins 用户名
user="USERNAME"
# 你的 jenkins 密码 
password="PASSWORD"

curl -u $user:$password 上面拼接好的URL

保存上面的脚本文件到自己的项目里,在 .gitignore 忽略掉,比如我保存到我的项目下并命名为 .publish

接下来测试下好不好用,在项目目录下,直接执行 sh .publish,然后回到 jenkins 项目界面,发现项目已经在部署了。

优化

经过上面的处理,已经完成用命令行远程触发构建。但是有一个小缺憾,就是触发部署后,没有状态返回。这个我怎么知道部署成功了没有?

我们先整理思路。

因为触发部署的时候并没有返回什么可利用的信息,所以要想拿到部署过程中的状态返回,必须轮询 /api/json 接口,这个接口会返回部署的相关信息。

接口格式:

JENKINS_URL/job/PROJECT_NAME/api/json

接下来我们分析这个接口返回的信息,看看有什么可以利用的?

分析后发现,想要做显示进度肯定没戏了。但我们发现有这几个字段可以利用,来实现简单的状态判断。

这几个字段下都会自带一个数字 id。

lastBuild // 最后一次构建
lastCompletedBuild // 最后一次完成的构建
lastSuccessfulBuild // 最后一次成功的构建

在构建开始 lastBuild 会生成一个新 id,这个时候 lastCompletedBuild 的 id 还是上次构建的 id。等 lastCompletedBuild 的 id 变成新 id,标志着构建完成。这个时候再对比 lastBuild 和 lastSuccessfulBuild 是否一样,来判断构建是否成功。

有了思路,我们来用代码实现。

由于部署信息接口返回的都是 json 或者 xml 格式,用 bash 解析可能需要安装其他东西。所以接下来用 node.js 实现。

这里为了简单,我还用 curl 来模拟登录。除此之外,也可以在 jenkins 中设置自己用户的 token (网上有教程)或者使用 node.js 模拟登录。

/**
 * 触发部署脚本
 * 需要在 node 环境下运行
 */
const exec = require('child_process').exec

// 获取命令行最后一个参数
const arg = process.argv.pop()

// jenkins 的登录账号密码
const user = 'USER_NAME'
const password = 'PASSWORD'

// 访问地址
const url = 'JENKINS_URL/job/'

// 环境配置,这里的 key 对应输入的参数
const envArr = {
  test: 'PROJECT_NAME',
  pre: 'PROJECT_NAME'
}

// 默认部署测试环境
let env = envArr[arg] ? envArr[arg] : envArr['test']

// 构建命令
const build = `curl -u ${user}:${password} ${url +
  env}/buildWithParameters?token=XXX&KEY=value`
// 获取部署信息命令
const info = `curl -u ${user}:${password} ${url + env}/api/json`

function sleep() {
  return new Promise(resolve => {
    setTimeout(resolve, 1000)
  })
}

// 开始构建
function start(cmd) {
  return new Promise((resolve, reject) => {
    exec(cmd, (err, stdout, stderr) => {
      if (err) reject(err)
      else resolve(stdout)
    })
  })
}

// 获取部署信息
function getInfo(cmd) {
  return new Promise((resolve, reject) => {
    exec(cmd, (err, stdout, stderr) => {
      if (err) reject(err)
      else resolve(stdout)
    })
  })
}

void (async () => {
  await start(build)

  let data = JSON.parse(await getInfo(info))
  var lastId = data.lastBuild.number
  var comId = data.lastCompletedBuild.number
  var sucID = data.lastSuccessfulBuild.number

  console.log('开始部署')

  // 判断是否部署完成
  while (lastId !== comId) {
    console.log(`#${lastId} ${env} 部署中,请稍等...`)

    // 休息后请求
    await sleep()
    let data = JSON.parse(await getInfo(info))
    lastId = data.lastBuild.number
    comId = data.lastCompletedBuild.number
    sucID = data.lastSuccessfulBuild.number
  }

  // 判断是否部署成功
  if (sucID === lastId) {
    console.log('部署成功!')
  } else {
    console.log('部署失败!')
  }
})()

对于上面的代码进行自己业务参数的更改,命名为 .publish 就可以实现命令行发布了。

node .publish test // 测试环境部署
node .publish pre // 预发布环境部署

最后看下最终的运行效果,部署过程中,每行开头会显示这次部署的id,中间(马赛克)是项目名称:

jenkins java无感发布 jenkins发布代码_jenkins java无感发布_03


好了,上面就是关于 jenkins 远程触发构建的尝试,后面可能还会优化。因为对各方面的知识都是刚接触,可能有些地方思路有局限性。希望有大牛看到可以提出宝贵的意见。谢谢!

end~