用一个JavaScript函数来实现ChatOps

前言

本文的目的是简单介绍一个下我自己如何借助 fx,通过一个 JavaScript 函数就可以实现一个简单的 ChatOps 方案的.

由于公司 Ops 团队人手不够,维护基础设施(jenkins,gitlab,redis,数据库等等)的稳定和持续发展已经占据了他们大部分的精力,没有更多的资源来做更好的自动化,趁着公司内部开展Hackathon,我们开发团队几个同事决定自己来做一会 DevOps 的角色,从开发团队的需求出发搭建一个完整的CI/CD自动化环境. 而我当然是选择其中最好玩的一个环节喽:ChatOps 的 Bot.

ChatOps 就不用多说了,就是 ChatOps = Chat + Ops 嘛,就是通过 Chat 的方式来完成 Ops 相关的操作。当然业界已经有很成熟实现 ChatOps 的框架 GitHub - hubotio/hubot ,或者 botkit , 都在很多大公司广泛的使用。

分析需求

不过我想另辟蹊径 (好吧,就是想装逼个X),用一个函数来实现一个 ChatOps,显然它可能不是一个成熟的产品,不过是一种很好玩的思路。使用 Function as a Service 的思路来构建一个 ChatOps, 说到 Function as a Service, 当然就要使用到我自己开发的fx了 ,它可以把你写的函数(Function) 直接变成一个服务(Service), 而且 fx 几乎支持了所有的主流语言 (Golang, JavaScript/Node, Ruby, Python, Java, PHP Julia).

简单的分析 ChatOps 要做的事情: 接收即时消息工具(Slack, HipChat)发送来的消息,解析消息目的,然后调用相应的Ops工具 (Jenkins ) 完成特定的操作. 所以可以简单系统架构如下:

              message                   call API
IM (Slack)  ----------> ChatBot --------------> Ops System
            <----------           <--------------
                message                    result

整个过程,我们可以认为是一个无状态的处理和调用的过程,所以我们也可以用一个函数来表示这整个过程:

ChatBot = f(message)

而这个 f (message) 就是一个简单(或者复杂)的函数,它接受从我们在即时消息发过来的消息,然后做分析转化成特定的Ops任务,去调用相应的Ops工具的 API去完成相应的工作。

代码实现

好吧,那我们就按照我们上面的分析来写这个函数就可以了。假设我们需要去实现下面的几个需求:

  1. 能够在 Slack 中接收一个 build 的指令
    根据消息传过来的project name 和 branch 去调用 Jenkins 做相应的 build.

  2. 能够在 Slack 中接收一个 query 的指令
    根据消息传过来的project name 和 branch 去调用 Jenkins 查询 Job 的状态.

所以我们的代码其实就非常的简单,一个函数的整体实现如下.

const { exec } = require('child_process');

module.exports = async (req) => {
    const { text } = req;

    // 从接收的消息中做简单的分析
    const analyze = (text) => {
        const [cmd, projectName, branch] = text.split(/\s+/);
        return {
            cmd,
            projectName,
            branch,
        }
    };

    // 调用Jenkins的API做build
    const build = (projectName, branch) => {
        const { projectName, branch } = options;
        const URL = `http://<your jenkins server>/job/${projectName}/job/${branch}/build`;
        const cmd = `curl -X POST  ${URL} --user admin:admin`;
        return new Promise((res, rej) => {
            exec(`${cmd}`, (err, stdout, stderr) => {
                if (err) {
                    rej(err);
                } else {
                    res(stdout, stderr);
                }
            });
        });
    };

    // 调用Jenkins的API做Job状态的查询
    const query = (projectName, branch) => {
        const URL = `http://<your jenkins server>/job/${projectName}/job/${branch}/lastBuild/api/json`;

        const cmd = `curl -X POST  ${URL} --user admin:admin`;
        return new Promise((res, rej) => {
            exec(`${cmd}`, (err, stdout, stderr) => {
                if (err) {
                    rej(err);
                } else {
                    const ret = JSON.parse(stdout);
                    res(ret);
                }
            });
        });
    };

    const message = (msg) => {
        const SEND_URL = "https://hooks.slack.com/services/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j";
        const cmd = `curl -X POST --data-urlencode "payload={\\"channel\\": \\"#hack-team-1\\", \\"username\\": \\"chatops\\", \\"text\\": \\"${msg}\\"}" ${SEND_URL}`;
        return new Promise((res, rej) => {
            exec(`${cmd}`, (err, stdout, stderr) => {
                if (err) {
                    rej(err);
                } else {
                    res(stdout, stderr);
                }
            });
        });
    };

    const { cmd, projectName, branch } = analyze(req.text);
    if (cmd === 'build') {
        await build(projectName, branch);
        message(`${projectName} ${branch} build started`);
    } else if(cmd === 'query') {
        const res = await query(projectName, branch);
        message(`${projectName} ${branch} built ${res.result} ${res.culprits[0].fullName}`);
    } else {
        message ('Command not supportet yet');
    }
}

实现了这个函数之后,我们可以通过 fx立即部署成一个服务.

fx up chatops.js

如果你想我们一样使用的 Slack 的话,你需要在 Slack 的 Outgoing hook 中把你刚刚部署好的服务的 url 配置过去,然后设置相应的触发条件,我们的一个简单但是五脏俱全的 ChatBot 就算完成了.

Bonus

如果你需要一个内网穿透工具,也许 ngrok 是你最好的选择。