diff --git a/Jenkinsfile b/Jenkinsfile index 9048477..0ba4899 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,66 +1,67 @@ -// BuildFlow portal CI/CD 企业级流水线 -// Stages: ①拉取代码 → ②编译打包 → 推送Harbor → ③部署 -// 构建成功/失败推送飞书交互卡片(绿/红) -// 飞书通知闭包(内联): 使用 Groovy JsonOutput 构造 JSON, 不依赖插件, 最稳妥 +// BuildFlow buildflow-portal CI/CD +// ①拉取代码 -> ②编译打包 -> 推送Harbor -> ③部署 +// 飞书卡片通知(成功绿/失败红), 内联闭包用 JsonOutput 无插件依赖 -def feishuNotify = { String result, String failedStageName = '' -> - def dur = currentBuild.durationString.replace(' and counting', '') - def commit = sh(script: 'git log -1 --pretty=format:"%s (%an)"', returnStdout: true).trim() - def isOk = (result == 'SUCCESS') - def emoji = isOk ? '✅' : '❌' - def color = isOk ? 'green' : 'red' - def title = "${emoji} CI/CD ${isOk ? '成功' : '失败'} · ${env.JOB_NAME} #${env.BUILD_NUMBER}" +def feishuNotify = { String result, String failedStageName = "" -> + def dur = currentBuild.durationString.replace(" and counting", "") + def commit = "" + try { commit = sh(script: "git log -1 --pretty=format:%s__%an 2>/dev/null | head -1", returnStdout: true).trim() } catch(ignored) {} + def isOk = (result == "SUCCESS") + def emoji = isOk ? "✅" : "❌" + def color = isOk ? "green" : "red" + def title = emoji + " CI/CD " + (isOk ? "成功" : "失败") + " · " + env.JOB_NAME + " #" + env.BUILD_NUMBER + def stMsg = isOk ? "成功" : "失败" def fields = [ - [is_short: true, text: [tag: 'lark_md', content: "**项目**\n${env.JOB_NAME}"]], - [is_short: true, text: [tag: 'lark_md', content: "**耗时**\n${dur}"]], - [is_short: true, text: [tag: 'lark_md', content: "**提交**\n${commit.take(50)}"]], - [is_short: true, text: [tag: 'lark_md', content: "**状态**\n${isOk ? '成功' : '失败'}"]] + [is_short: true, text: [tag: "lark_md", content: "**项目**\n" + env.JOB_NAME]], + [is_short: true, text: [tag: "lark_md", content: "**耗时**\n" + dur]], + [is_short: true, text: [tag: "lark_md", content: "**提交**\n" + commit.take(50)]], + [is_short: true, text: [tag: "lark_md", content: "**状态**\n" + stMsg]] ] - def elements = [[tag: 'div', fields: fields], [tag: 'hr']] + def elements = [[tag: "div", fields: fields], [tag: "hr"]] if (isOk) { - elements.add([tag: 'div', text: [tag: 'lark_md', content: "📦 镜像地址\n`${env.IMAGE}:${env.TAG}`"]]) + elements.add([tag: "div", text: [tag: "lark_md", content: "镜像地址\n" + env.IMAGE + ":" + env.TAG]]) } else if (failedStageName) { - elements.add([tag: 'div', text: [tag: 'lark_md', content: "⚠️ 失败阶段\n${failedStageName}"]]) + elements.add([tag: "div", text: [tag: "lark_md", content: "失败阶段\n" + failedStageName]]) } - elements.add([tag: 'action', actions: [[tag: 'button', text: [tag: 'plain_text', content: '查看构建'], url: env.BUILD_URL ?: 'http://115.191.51.246:8080', type: isOk ? 'primary' : 'danger']]]) - def card = [msg_type: 'interactive', card: [header: [title: [tag: 'plain_text', content: title], template: color], elements: elements]] + def burl = env.BUILD_URL + if (burl == null) { burl = "http://115.191.51.246:8080/job/" + env.JOB_NAME.replace("/", "/job/") + "/" } + elements.add([tag: "action", actions: [[tag: "button", text: [tag: "plain_text", content: "查看构建"], url: burl, type: isOk ? "primary" : "danger"]]]) + def card = [msg_type: "interactive", card: [header: [title: [tag: "plain_text", content: title], template: color], elements: elements]] def jsonStr = groovy.json.JsonOutput.toJson(card) - writeFile file: '/tmp/feishu_msg.json', text: jsonStr - sh "curl -sS -X POST https://open.feishu.cn/open-apis/bot/v2/hook/d215e8ed-edea-4f07-a77c-2a0e5b52d9b9 -H 'Content-Type: application/json' -d @/tmp/feishu_msg.json || true" + writeFile file: "/tmp/feishu_msg.json", text: jsonStr + sh "curl -sS -X POST https://open.feishu.cn/open-apis/bot/v2/hook/d215e8ed-edea-4f07-a77c-2a0e5b52d9b9 -H \x27Content-Type: application/json\x27 -d @/tmp/feishu_msg.json || true" sh "rm -f /tmp/feishu_msg.json" } pipeline { agent any environment { - HARBOR = '43.134.235.58:58080' + HARBOR = "43.134.235.58:58080" IMAGE = "${HARBOR}/buildflow/buildflow-portal" TAG = "${env.BUILD_NUMBER}" } options { - timeout(time: 30, unit: 'MINUTES') - buildDiscarder(logRotator(numToKeepStr: '10')) + timeout(time: 30, unit: "MINUTES") + buildDiscarder(logRotator(numToKeepStr: "10")) } stages { - stage('① 拉取代码') { + stage("① 拉取代码") { steps { checkout scm - sh 'git log --oneline -3' + sh "git log --oneline -3" } } - stage('② 编译打包') { + stage("② 编译打包") { steps { - sh """ - docker build -t ${IMAGE}:${TAG} -t ${IMAGE}:latest . - docker images | grep ${IMAGE} | head - """ + sh "docker build -t ${IMAGE}:${TAG} -t ${IMAGE}:latest ." + sh "docker images | grep ${IMAGE} | head -2" } } - stage('推送 Harbor') { + stage("推送 Harbor") { steps { - withCredentials([usernamePassword(credentialsId: 'harbor-credentials', usernameVariable: 'HUSER', passwordVariable: 'HPASS')]) { + withCredentials([usernamePassword(credentialsId: "harbor-credentials", usernameVariable: "HUSER", passwordVariable: "HPASS")]) { sh """ - echo $HPASS | docker login ${HARBOR} -u $HUSER --password-stdin + echo \$HPASS | docker login ${HARBOR} -u \$HUSER --password-stdin docker push ${IMAGE}:${TAG} docker push ${IMAGE}:latest docker logout ${HARBOR} @@ -68,11 +69,11 @@ pipeline { } } } - stage('③ 部署') { + stage("③ 部署") { steps { script { try { - node('deploy-agent') { + node("deploy-agent") { sh """ docker pull ${IMAGE}:latest docker rm -f buildflow-portal 2>/dev/null || true @@ -81,26 +82,18 @@ pipeline { } echo "部署完成" } catch (e) { - echo "⚠️ 部署节点(deploy-agent)暂不可用,跳过自动部署。镜像已推送 Harbor,可手动拉取部署。" + echo "部署节点(deploy-agent)暂不可用,跳过自动部署。镜像已推送 Harbor。" } } } } - } post { success { - script { feishuNotify('SUCCESS') } + script { feishuNotify("SUCCESS") } } failure { - script { - def stageName = '未知' - try { - def failedNodes = currentBuild.getRawBuild().getAction(org.jenkinsci.plugins.workflow.job.views.FlowGraphAction.class)?.getHeads() - if (failedNodes) { stageName = failedNodes.get(0).getDisplayNodeName() } - } catch (ignored) {} - feishuNotify('FAILURE', stageName) - } + script { feishuNotify("FAILURE", "构建过程中失败,请查看日志") } } always { sh "docker rmi ${IMAGE}:${TAG} ${IMAGE}:latest 2>/dev/null || true"