Jenkins通知脚本-飞书卡片通知

Jenkins通知脚本-飞书卡片通知

目的

使用飞书卡片消息类型将Jenkins任务构建开始和结束时状态进行飞书群通知

关键点

  1. Jenkins构建任务中增加2个过程
    • Build Steps -> Execute shell (添加在主构建脚本(npm/mvn相关脚本)之前,目的是:编译等主要构建过程之前进行通知)
    • 构建后操作-> Post build task(添加在构建后操作,目的是:编译等主要构建过程之后进行通知)
  1. 飞书卡片的制作,必须先在飞书后台自建应用内新建自己的卡片,并且卡片的字段和卡片ID要和脚本内容匹配;

效果截图

  1. 构建开始
    在这里插入图片描述
  2. 构建结束
    在这里插入图片描述

源码

飞书卡片源码

  • 请使用“消息卡片制作工具”进行制作,下述源码会根据卡片组件自动生成;
{
  "config": {
    "wide_screen_mode": true
  },
  "elements": [
    {
      "tag": "hr"
    },
    {
      "tag": "markdown",
      "content": "**任务名称:** **<font >${JOB_NAME}</font>**"
    },
    {
      "tag": "markdown",
      "content": "此次构建启动时间:${BUILD_START_TIME}"
    },
    {
      "tag": "markdown",
      "content": "构建状态:**<font color=${font_color}>${build_status}</font>**"
    },
    {
      "tag": "hr"
    },
    {
      "tag": "markdown",
      "content": "**仓库分支信息如下:**"
    },
    {
      "tag": "column_set",
      "flex_mode": "none",
      "background_style": "grey",
      "columns": [
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "column_set",
              "flex_mode": "none",
              "background_style": "grey",
              "columns": [
                {
                  "tag": "column",
                  "width": "weighted",
                  "weight": 1,
                  "vertical_align": "top",
                  "elements": [
                    {
                      "tag": "markdown",
                      "content": "**地址**"
                    }
                  ]
                },
                {
                  "tag": "column",
                  "width": "weighted",
                  "weight": 1,
                  "vertical_align": "top",
                  "elements": [
                    {
                      "tag": "markdown",
                      "content": "**分支**"
                    }
                  ]
                },
                {
                  "tag": "column",
                  "width": "weighted",
                  "weight": 1,
                  "vertical_align": "top",
                  "elements": [
                    {
                      "tag": "markdown",
                      "content": "**commitId**"
                    }
                  ]
                }
              ]
            }
          ]
        }
      ],
      "horizontal_spacing": "default"
    },
    {
      "tag": "column_set",
      "flex_mode": "none",
      "background_style": "default",
      "columns": [
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${repo_url}"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${repo_branch_name}"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${repo_branch_commit_id}"
            }
          ]
        }
      ],
      "_varloop": "${last_build_info}",
      "horizontal_spacing": "default"
    },
    {
      "tag": "hr"
    },
    {
      "tag": "markdown",
      "content": "**${last_changes_info_text}**"
    },
    {
      "tag": "column_set",
      "flex_mode": "none",
      "background_style": "grey",
      "columns": [
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "**提交ID**"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "**提交人**"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "**提交内容**"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "**提交时间**"
            }
          ]
        }
      ]
    },
    {
      "tag": "column_set",
      "flex_mode": "none",
      "background_style": "default",
      "columns": [
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${commitId}"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${authorEmail}"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${comment}"
            }
          ]
        },
        {
          "tag": "column",
          "width": "weighted",
          "weight": 1,
          "vertical_align": "top",
          "elements": [
            {
              "tag": "markdown",
              "content": "${date}"
            }
          ]
        }
      ],
      "_varloop": "${last_changes_info}"
    },
    {
      "tag": "hr"
    },
    {
      "tag": "markdown",
      "content": "**😁构建人:**<at email=${user_id}></at>"
    },
    {
      "tag": "markdown",
      "content": "**😁知情人:**${insider_users_email}"
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "任务链接"
          },
          "type": "primary",
          "multi_url": {
            "url": "${JOB_URL}",
            "pc_url": "",
            "android_url": "",
            "ios_url": ""
          }
        },
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "构建链接"
          },
          "type": "primary",
          "multi_url": {
            "url": "${THIS_BUILD_URL}",
            "pc_url": "",
            "android_url": "",
            "ios_url": ""
          }
        },
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "Jenkins变更记录"
          },
          "type": "primary",
          "multi_url": {
            "url": "${THIS_BUILD_CHANGES}",
            "pc_url": "",
            "android_url": "",
            "ios_url": ""
          }
        }
      ]
    },
    {
      "tag": "hr"
    },
    {
      "tag": "note",
      "elements": [
        {
          "tag": "img",
          "img_key": "img_v2_xxx",
          "alt": {
            "tag": "plain_text",
            "content": ""
          }
        },
        {
          "tag": "plain_text",
          "content": "${short_description}"
        }
      ]
    }
  ],
  "card_link": {
    "url": "",
    "pc_url": "",
    "android_url": "",
    "ios_url": ""
  },
  "header": {
    "title": {
      "content": "Jenkins任务构建通知:",
      "tag": "plain_text"
    },
    "template": "wathet"
  }
}

脚本源码

  • Build Steps -> Execute shell 和 构建后操作-> Post build task 存放一样的shell脚本
#/bin/bash

feishu_chat_id1="oc_xxx"
feishu_chat_list=($feishu_chat_id1)

function send_msg() {
  for ((i = 0; i < ${#feishu_chat_list[@]}; i++)); do
    feishu_chat_id=${feishu_chat_list[$i]}
    /root/anaconda3/envs/py36/bin/python /jenkins_tools/jenkins_builds_info_notify_feishu.py \
    "${feishu_chat_id}" -job_name "${JOB_NAME}" -job_url "${JOB_URL}" -build_name "${BUILD_ID}" -branch "${GIT_BRANCH}"
  done
}

send_msg



  • Python脚本(该脚本Python版本:3.6.8),存放在Jenkins服务器上,让shell脚本调用。
import argparse
import json
import time

import requests
import validators

parser = argparse.ArgumentParser(description='Jenkins 发送消息到飞书',
                                 epilog="执行示例>>> python ${feishu_chat_id} -job_name ${JOB_NAME} -job_url ${JOB_URL} "
                                        "-branch ${GIT_BRANCH} -build_name ${BUILD_NUMBER}  ")
parser.add_argument('feishu_chat_id', help='机器人webhookURL')  # 必填
parser.add_argument('-job_name', '--JOB_NAME', help='作业Name', )  # 选填
parser.add_argument('-job_url', '--JOB_URL', help='作业URL', required=True, )  # 必填
parser.add_argument('-branch', '--GIT_BRANCH', help='git分支', default='')  # 选填
parser.add_argument('-build_name', '--BUILD_DISPLAY_NAME', help='编译Name')  # 选填

feishu_chat_id = parser.parse_args().feishu_chat_id
JOB_NAME = parser.parse_args().JOB_NAME
JOB_URL = parser.parse_args().JOB_URL
GIT_BRANCH = parser.parse_args().GIT_BRANCH
BUILD_DISPLAY_NAME = parser.parse_args().BUILD_DISPLAY_NAME
BUILD_URL = JOB_URL + '/lastBuild'


def feishu_tools(send_data, app_id, app_secret):
    get_tenant_access_token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    payload = json.dumps({
        "app_id": app_id,
        "app_secret": app_secret
    })
    headers = {
        'Content-Type': 'application/json'
    }
    response = requests.request("POST", get_tenant_access_token_url, headers=headers, data=payload)
    tenant_access_token = json.loads(response.text)['tenant_access_token']

    url = "https://open.feishu.cn/open-apis/message/v4/send/"

    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + str(tenant_access_token)
    }

    response = requests.request("POST", url, headers=headers, data=send_data)
    return response.text


def feishu_notify(jenkins_auth, app_id, app_secret, card_id):
    result = requests.get(f'{BUILD_URL}/api/json', auth=jenkins_auth).json()
    print(json.dumps(result))
    last_build_info = []
    last_changes_info = []
    insider_users_email = ""
    for _ in result['actions']:
        if _ != {}:
            if _['_class'] == "hudson.model.CauseAction":
                for c in _['causes']:
                    if c['_class'] == "hudson.model.Cause$UserIdCause":
                        short_description = c['shortDescription']
                        user_id = c['userId']
                        # user_name = c['userName']

            elif _['_class'] == "hudson.plugins.git.util.BuildData":
                cur_build_info = {
                    "repo_url": _.get('remoteUrls')[0],
                    "repo_branch_name": _['lastBuiltRevision'].get('branch')[0].get('name'),
                    "repo_branch_commit_id": _['lastBuiltRevision'].get('branch')[0].get('SHA1'),

                }
                last_build_info.append(cur_build_info)

            elif _['_class'] == "hudson.model.ParametersAction":
                for p in _['parameters']:
                    if "insider_users_email" == p['name']:
                        insider_users_email = list(p['value'].split(" "))

    print(insider_users_email)
    insider_users_email_formats = ""
    for insider_user_email in insider_users_email:
        insider_users_email_format = "<at email={}></at> "
        if validators.email(insider_user_email):
            insider_users_email_formats += insider_users_email_format.format(insider_user_email)
        else:
            print(validators.email(insider_user_email))


    changesItems = result['changeSet']['items']
    if len(changesItems) > 0:
        for c in changesItems:
            last_change_info = dict()
            last_change_info['commitId'] = c['commitId']
            # last_change_info['affectedPaths'] = '\n'.join(c['affectedPaths'])
            last_change_info['authorEmail'] = c['authorEmail']
            last_change_info['comment'] = c['comment']
            last_change_info['date'] = c['date']
            last_changes_info.append(last_change_info)

    last_changes_info = list(reversed(last_changes_info))
    print(last_changes_info)

    BUILD_START_TIME = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(result['timestamp'] / 1000))
    build_status = "START" if result['result'] is None else result['result']
    estimated_duration = result['estimatedDuration']
    estimated_duration = int(estimated_duration) // 1000
    minutes, seconds = divmod(estimated_duration, 60)
    hours, minutes = divmod(minutes, 60)
    build_duration = f'{hours}小时:{minutes}分:{seconds}秒'
    BUILD_ID = result['id']
    THIS_BUILD_URL = result['url']
    THIS_BUILD_CHANGES = THIS_BUILD_URL + '/changes'


    # # ============ 设置文字颜色 ============
    if "SUCCESS" == build_status:  # 成功
        font_color = "green"
    elif "FAILURE" == build_status:  # 失败
        font_color = "red"
    elif "ABORTED" == build_status:  # 终止
        font_color = "grey"
    else:
        font_color = "default"


    card = {
        "type": "template",
        "data": {
            "template_id": card_id,
            "template_variable":
                {
                    "JOB_NAME": JOB_NAME,
                    "JOB_URL": JOB_URL,
                    "BUILD_START_TIME": BUILD_START_TIME,
                    "build_duration": build_duration,
                    "BUILD_ID": BUILD_ID,
                    "THIS_BUILD_URL": THIS_BUILD_URL,
                    "THIS_BUILD_CHANGES": THIS_BUILD_CHANGES,
                    "build_status": build_status,
                    "short_description": short_description,
                    "font_color": font_color,
                    "last_build_info": last_build_info,
                    "user_id": user_id,
                    "insider_users_email": insider_users_email_formats if insider_users_email_formats is not "" else "*<font color='green'>暂未分配知情人,可携带该构建url联系运维永久添加!当然,也可以在构建的时候,insider_users_email参数下自行覆盖填写知情人邮箱!以空格分隔!</font>*",
                    "last_changes_info": last_changes_info,
                    "last_changes_info_text": "变更记录详情如下:" if len(last_changes_info) > 0 else "此次变更无变更信息"

                }
        }
    }

    send_data = json.dumps({
               "chat_id": feishu_chat_id,
               "msg_type": "interactive",
               "card": card
            })

    res = feishu_tools(send_data=send_data, app_id=app_id, app_secret=app_secret)
    print(res)


if __name__ == '__main__':
    jenkins_auth = ('yuhongchao', 'xxx')
    app_id = "cli_xxx"
    app_secret = "xxx"
    feishu_notify(jenkins_auth=jenkins_auth, app_id=app_id,
                  app_secret=app_secret, card_id="ctp_xxx")


后话

  • 如果构建任务使用“This project is parameterized”,那么,可在脚本接收到的parameters字典列表字段,基础上完善其他通知逻辑。
    parameters
  • 除了本文使用的“脚本执行”方式,还有一种可以使用通知插件实现,这种通知插件的方式更加优雅,配合Python drf框架提供的接口即可实现整个逻辑。但是对Jenkins的版本有要求,我司的生产环境Jenkins版本过低,不想升级,所以无法使用这个通知插件+Python Drf框架来实现;有兴趣的朋友可以尝试一下,我这边在测试环境验证过,比较简单也比较优雅。

在这里插入图片描述

  • 飞书后台消息卡片截图
    在这里插入图片描述
  • Jenkins字符参数配合知情人通知逻辑

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

于特洛夫斯基

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值