一.前言
在企业项目发版本过程中,以Jenkins为例,个别项目构建时间较长,不想时刻关注构建情况,或构建成功后移交其他人继续处理,需要配置企业微信通知功能,本篇主要是针对pipeline流水线项目,如何实现Jenkins构建成功与否通知到消息至企业微信指定人
二.实现过程
2.1 企业微信创建机器人
这里采用比较简单的方法实现,主要是通过Pipeline+Shell, Pipeline对shell还是比较有好的,这里首先必须要选择企业微信,创建2~3人以上的群聊,随后添加一个群机器人
创建机器人主要是为了接收我们传递的消息和提供一个控制它的API,这里我们只需要记住机器人的webhook,下面的shell脚本将会通过webhook的方式调用它
2.2 Jenkins安装插件
关于企业微信构建任务通知,Jenkins需要依赖以下插件
获取构建用户变量 需安装插件: build user vars plugin
获取构建时间变量 需安装插件:Build Timestamp Plugin
企业微信插件 Qy Wechat Notification
2.3 编写Shell脚本
Shell脚本主要用于请求企业微信API、发送数据等
#vim send_message-export.sh
#!/bin/sh
#替换成自己的群机器人key值
CHAT_WEBHOOK_KEY=xxxxxx-xxxxxx-xxxxxx-xxxxxxxx
CHAT_CONTENT_TYPE='Content-Type: application/json'
#-o代表或的意思,成功或失败 CHAT_WEBHOOK_URL都是一样的WEBHOOK url
if [ _"${TYPE}" = _"success" -o _"${TYPE}" = _"failure" ]; then
CHAT_WEBHOOK_URL='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key'
fi
if [ _"${CHAT_WEBHOOK_KEY}" = _"" ]; then
echo "please make sure CHAT_WEBHOOK_KEY has been exported as environment variable"
fi
echo "## send message for : ${TYPE}"
if [ _"${TYPE}" = _"success" ]; then
curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
-H "${CHAT_CONTENT_TYPE}" \
-d '
{
"msgtype": "markdown",
"markdown": {
"content": "<font color=\"warning\">**Jenkins任务通知**</font> \n
>构建人:<font color=\"comment\">'"${BUILD_USER}"'</font>
>构建时间:<font color=\"comment\">'"${BUILD_TIME}"'</font>
>构建分支:<font color=\"comment\">'"${BRANCH_NAME}"'</font>
>任务名称:<font color=\"comment\">'"${JOB_NAME}"'</font>
>构建次数:<font color=\"comment\">'"${BUILD_NUM}"'</font>
>任务地址:<font color=\"comment\">[点击查看]('"${URL_JOB}"')</font>
>构建日志:<font color=\"comment\">[点击查看]('"${URL_LOG}"')</font>
>构建状态:<font color=\"info\">**Success**</font> \n
>任务已构建完成请确认:<@'"${BUILD_USER}"'>"
}
}
'
elif [ _"${TYPE}" = _"failure" ]; then
curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
-H "${CHAT_CONTENT_TYPE}" \
-d '
{
"msgtype": "markdown",
"markdown": {
"content": "<font color=\"warning\">**Jenkins任务通知**</font> \n
>构建人:<font color=\"comment\">'"${BUILD_USER}"'</font>
>构建时间:<font color=\"comment\">'"${BUILD_TIME}"'</font>
>构建分支:<font color=\"comment\">'"${BRANCH_NAME}"'</font>
>任务名称:<font color=\"comment\">'"${JOB_NAME}"'</font>
>构建次数:<font color=\"comment\">'"${BUILD_NUM}"'</font>
>任务地址:<font color=\"comment\">[点击查看]('"${URL_JOB}"')</font>
>构建日志:<font color=\"comment\">[点击查看]('"${URL_LOG}"')</font>
>构建状态:<font color=\"comment\">**Failure**</font>
>任务已构建完成请确认:<@'"${BUILD_USER}"'>"
}
}
'
fi
2.4 Pipeline脚本
在这里我这边给出整个pipeline企业微信消息通知流水线案例
#vim Jenkinsfile
pipeline {
agent {
kubernetes {
cloud 'Test-kubernetes'
slaveConnectTimeout 1200
workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/devops-tols/jnlp-slave:latest'
name: jnlp
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/devops-tols/maven:3.5.3"
imagePullPolicy: "IfNotPresent"
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
- mountPath: "/root/.m2/"
name: "cachedir"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/devops-tols/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/devops-tols/docker-git:1.19-git"
imagePullPolicy: "IfNotPresent"
name: "docker"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- mountPath: "/var/run/docker.sock"
name: "dockersock"
readOnly: false
restartPolicy: "Never"
nodeSelector:
jenkins-build: "true"
securityContext: {}
volumes:
- hostPath:
path: "/var/run/docker.sock"
name: "dockersock"
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
- name: "cachedir"
hostPath:
path: "/opt/m2"
'''
}
}
stages {
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
when {
expression {
env.gitlabBranch == null
}
}
steps {
git(changelog: true, poll: true, url: 'https://gitee.com/bjcaszyx/test-boot-projext.git', branch: "${BRANCH}", credentialsId: 'GITEE_ACCOUNT')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
stage('Pulling Code by trigger') {
when {
expression {
env.gitlabBranch != null
}
}
steps {
git(url: 'https://gitee.com/bjcaszyx/test-boot-projext.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'GITEE_ACCOUNT')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
}
}
stage('Building') {
steps {
//配置构建人变量,在编译阶段定义
wrap([$class: 'BuildUser']) {
script {
BUILD_USER = "${env.BUILD_USER}"
BUILD_USER_ID ="${BUILD_USER_ID}"
}
}
script {
env.BUILD_USERNAME = "${BUILD_USER}"
env.BUILD_USERNAMEID = "${BUILD_USER_ID}"
}
//END结束
container(name: 'build') {
sh """
mvn clean package -Dmaven.test.skip=true -Ptest
ls target/*
"""
}
}
}
stage('Docker build for creating image') {
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
//配置构建人变量,在打包镜像定义
wrap([$class: 'BuildUser']) {
script {
BUILD_USER = "${env.BUILD_USER}"
BUILD_USER_ID ="${BUILD_USER_ID}"
}
}
script {
env.BUILD_USERNAME = "${BUILD_USER}"
env.BUILD_USERNAMEID = "${BUILD_USER_ID}"
}
//END
container(name: 'docker') {
sh """
echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
pwd
ls *
docker version
docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
"""
}
}
}
stage('Deploying to K8s') {
environment {
MY_KUBECONFIG = credentials('k8s-kubeconfig')
}
steps {
//配置构建人变量,在部署环节定义
wrap([$class: 'BuildUser']) {
script {
BUILD_USER = "${env.BUILD_USER}"
BUILD_USER_ID ="${BUILD_USER_ID}"
}
}
script {
env.BUILD_USERNAME = "${BUILD_USER}"
env.BUILD_USERNAMEID = "${BUILD_USER_ID}"
}
//END
container(name: 'kubectl'){
sh """
/usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
"""
}
}
}
}
//下面是整个流水线脚本构建完成之后的动作,这里添加到了pipeline的最下面
post {
success {
container('jnlp'){
script {
//多分支流水线项目使用${JOB_NAME}可显示 项目名称+分支名称
sh 'export TYPE=success;export BRANCH_NAME="${BRANCH}";export JOB_NAME="${JOB_BASE_NAME}";export BUILD_NUM="$BUILD_NUMBER";export BUILD_TIME="$BUILD_TIMESTAMP"; export BUILD_USER="${BUILD_USERNAME}"; export URL_JOB="${BUILD_URL}";export URL_LOG="${BUILD_URL}console";export JOB_TIPS1="${BUILD_USERNAMEID}" ;sh send_message-export.sh'
}
}
}
failure {
container('jnlp'){
script {
sh 'export TYPE=failure;export BRANCH_NAME="${BRANCH}";export JOB_NAME="${JOB_BASE_NAME}";export BUILD_NUM="$BUILD_NUMBER";export BUILD_TIME="$BUILD_TIMESTAMP"; export BUILD_USER="${BUILD_USERNAME}"; export URL_JOB="${BUILD_URL}";export URL_LOG="${BUILD_URL}console";export JOB_TIPS1="${BUILD_USERNAMEID}" ;sh send_message-export.sh'
}
}
}
}
//END结束
environment {
COMMIT_ID = ""
HARBOR_ADDRESS = "harbor.xxx.xxx" #这里指定自己的镜像仓库地址
REGISTRY_DIR = "trt-test"
IMAGE_NAME = "test-boot-project"
NAMESPACE = "tongrentang"
TAG = ""
}
parameters {
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
//string(defaultValue: 'master', description: 'Branch for build and deploy', name: 'BRANCH')
}
}
Ps: 上述pipeline脚本、Shell脚本都是需要放在git上,保证jenkinsfile能够调用shell脚本
2.5 原理剖析
- Jenkins通过Jenkinsfike文件构建任务
- Jenkins文件最后使用了post参数,从而来判断该任务状态(是否正常success/failure)然后调用事先准备好的shell脚本,然后将Jenkins构建过程中获取的参数传递至shell脚本里面
- 当pipeline流程结束之后,会触发post执行shell脚本,将请求企业微信机器人webhook,发送信息到企业微信群聊中
三、验证测试
Ps:记住一定要在jenkins中确认下时间戳,避免出现构建完成之后企业微信消息通知时间不对
系统管理-->系统配置--Build Timestamp
整个流水线走下来,发布完成之后就会发送企业微信消息
四、问题小思考?
最后虽然整个流程下来都没有问题,细心的小伙伴可能会发现,在Pipeline脚本中,每一个阶段我都定义了构建人环境变量,如果只定义某一处的话,那么在另外某一阶段失败的情况下虽然消息通知了,但是该阶段获取不了构建人变量,企业微信通知消息就会缺失变量(如下图所示)
所以请问对pipeline非常熟悉的小伙伴没有办法解决这个问题,就是说wrap只需要在pipeline一个地方定义即可,此后无论哪阶段构建失败,构建人变量都正常识别,也就能正常的发送完整的消息通知
标签:构建,name,NAME,手把手,export,BUILD,微信,Jenkins,USER From: https://blog.51cto.com/u_11880730/7254507