@agile-team/wl-skills-gate 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,137 @@
1
1
  // Package-owned Scripted Pipeline; no separately published shared library.
2
+ def wlOpsRun(Map cfg) {
3
+ def publicRoot = "${pwd()}/wl-ops-public"
4
+ dir('wl-ops-public') { deleteDir(); dir('projects') { writeFile file: '.keep', text: '' } }
5
+ def inventory = null
6
+ def outcome = 2
7
+ withEnv(["WL_QUALITY_CLI=${cfg.gateRoot}/bin/wl-quality.js", "WL_OPS_CLUSTER=${cfg.clusterFile}", "WL_OPS_PUBLIC=${publicRoot}"]) {
8
+ try {
9
+ stage('发现项目与流水线') {
10
+ withCredentials([usernamePassword(credentialsId: 'wl-jenkins-ci-readonly', usernameVariable: 'WL_JENKINS_READ_USER', passwordVariable: 'WL_JENKINS_READ_TOKEN')]) {
11
+ sh '''#!/bin/sh
12
+ set -eu
13
+ set +x
14
+ node "$WL_QUALITY_CLI" jenkins ops inventory --cluster "$WL_OPS_CLUSTER" --out "$WL_OPS_PUBLIC/inventory.json"
15
+ '''
16
+ }
17
+ inventory = new groovy.json.JsonSlurperClassic().parseText(readFile('wl-ops-public/inventory.json'))
18
+ echo "巡检发现 ${inventory.jobsDiscovered} 个任务;目标 ${inventory.targets.size()} 项。"
19
+ }
20
+ stage('检查项目接入条件') {
21
+ for (def target in inventory.targets) {
22
+ withEnv(["WL_OPS_KEY=${target.key}", "WL_OPS_RESULT=${publicRoot}/projects/${target.key}.json"]) {
23
+ def checkoutFailed = false
24
+ if (target.checkoutAllowed == true) {
25
+ dir("wl-ops-checkouts/${target.key}") {
26
+ deleteDir()
27
+ try {
28
+ try {
29
+ timeout(time: 5, unit: 'MINUTES') {
30
+ checkout(changelog: false, poll: false, scm: [
31
+ $class: 'GitSCM', branches: [[name: "origin/${target.branch}"]],
32
+ userRemoteConfigs: [[name: 'origin', url: target.repository, credentialsId: target.credentialId,
33
+ refspec: "+refs/heads/${target.branch}:refs/remotes/origin/${target.branch}"]],
34
+ extensions: [[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, honorRefspec: true]]
35
+ ])
36
+ }
37
+ } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException interrupted) { throw interrupted }
38
+ catch (Exception checkoutFailure) { checkoutFailed = true; echo '源码检出未完成;仅记录缺项,继续检查其余项目。' }
39
+ if (!checkoutFailed) {
40
+ sh(returnStatus: true, script: '''#!/bin/sh
41
+ set -eu
42
+ node "$WL_QUALITY_CLI" jenkins ops inspect --cluster "$WL_OPS_CLUSTER" --inventory "$WL_OPS_PUBLIC/inventory.json" --target-key "$WL_OPS_KEY" --root "$PWD" --out "$WL_OPS_RESULT"
43
+ ''')
44
+ }
45
+ } finally { deleteDir() }
46
+ }
47
+ }
48
+ if (checkoutFailed || target.checkoutAllowed != true) {
49
+ sh(returnStatus: true, script: '''#!/bin/sh
50
+ set -eu
51
+ node "$WL_QUALITY_CLI" jenkins ops inspect --cluster "$WL_OPS_CLUSTER" --inventory "$WL_OPS_PUBLIC/inventory.json" --target-key "$WL_OPS_KEY" --failure checkout-not-completed --out "$WL_OPS_RESULT"
52
+ ''')
53
+ }
54
+ }
55
+ }
56
+ }
57
+ } finally {
58
+ try {
59
+ if (fileExists('wl-ops-public/inventory.json')) {
60
+ stage('记录缺项与飞书提醒') {
61
+ try { withEnv(["WL_OPS_RETRY_NOTICE=${params.RETRY_NOTIFICATION == true ? '1' : '0'}"]) { withCredentials([
62
+ string(credentialsId: 'wl-feishu-ci-webhook', variable: 'WL_FEISHU_CI_WEBHOOK'),
63
+ string(credentialsId: 'wl-feishu-ci-signing-secret', variable: 'WL_FEISHU_CI_SECRET')
64
+ ]) {
65
+ outcome = sh(returnStatus: true, script: '''#!/bin/sh
66
+ set -eu
67
+ set +x
68
+ set -- jenkins ops finish --cluster "$WL_OPS_CLUSTER" --inventory "$WL_OPS_PUBLIC/inventory.json" --results "$WL_OPS_PUBLIC/projects" --out "$WL_OPS_PUBLIC/summary.json" --send
69
+ if [ "$WL_OPS_RETRY_NOTICE" = '1' ]; then set -- "$@" --retry-notification; fi
70
+ node "$WL_QUALITY_CLI" "$@"
71
+ ''')
72
+ } } } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException interrupted) { throw interrupted }
73
+ catch (Exception noticeFailure) {
74
+ echo '通知阶段未完成;请核对流水线机器人凭据。本次仍生成巡检原件,不登记消息送达。'
75
+ if (!fileExists('wl-ops-public/summary.json')) sh(returnStatus: true, script: '''#!/bin/sh
76
+ set -eu
77
+ node "$WL_QUALITY_CLI" jenkins ops finish --cluster "$WL_OPS_CLUSTER" --inventory "$WL_OPS_PUBLIC/inventory.json" --results "$WL_OPS_PUBLIC/projects" --out "$WL_OPS_PUBLIC/summary.json"
78
+ ''')
79
+ outcome = 2
80
+ }
81
+ }
82
+ }
83
+ } finally { archiveArtifacts artifacts: 'wl-ops-public/*.json,wl-ops-public/*.html,wl-ops-public/projects/*.json', allowEmptyArchive: true }
84
+ }
85
+ }
86
+ if (fileExists('wl-ops-public/summary.json')) {
87
+ def summary = new groovy.json.JsonSlurperClassic().parseText(readFile('wl-ops-public/summary.json'))
88
+ if (!summary.globalGaps) wlOpsContinue(cfg, summary)
89
+ }
90
+ if (outcome != 0) { currentBuild.result = 'UNSTABLE'; echo '运维巡检有待处理项或消息未确认送达;见本次归档,修复后下一轮自动复查。本结果不授予构建准入。' }
91
+ }
92
+
93
+ // Only package-verified CHECK jobs can be resumed; never arbitrary legacy deploy jobs.
94
+ def wlOpsContinue(Map cfg, Map summary) {
95
+ try {
96
+ stage('承接已就绪项目的九维检查') {
97
+ for (def project in summary.projects.findAll { it.checkJob && (it.status == 'ready' || it.gaps.every { g -> g.id == 'CHECK-DISPATCH-UNKNOWN' }) }) {
98
+ withEnv(["WL_QUALITY_CLI=${cfg.gateRoot}/bin/wl-quality.js", "WL_OPS_CLUSTER=${cfg.clusterFile}", "WL_OPS_RUN=${summary.runId}", "WL_OPS_KEY=${project.key}", "WL_OPS_CLAIM=wl-ops-public/claim-${project.key}.json", "WL_OPS_ACK=wl-ops-public/ack-${project.key}.json", "WL_OPS_RETRY_CHECK=${params.RETRY_UNCONFIRMED_CHECK == true ? '1' : '0'}"]) {
99
+ def claimCode = 2
100
+ withCredentials([usernamePassword(credentialsId: 'wl-jenkins-ci-readonly', usernameVariable: 'WL_JENKINS_READ_USER', passwordVariable: 'WL_JENKINS_READ_TOKEN')]) {
101
+ claimCode = sh(returnStatus: true, script: '''#!/bin/sh
102
+ set -eu
103
+ set +x
104
+ set -- jenkins ops claim-check --cluster "$WL_OPS_CLUSTER" --run-id "$WL_OPS_RUN" --target-key "$WL_OPS_KEY" --out "$WL_OPS_CLAIM"
105
+ if [ "$WL_OPS_RETRY_CHECK" = '1' ]; then set -- "$@" --retry-unknown; fi
106
+ node "$WL_QUALITY_CLI" "$@"
107
+ ''')
108
+ }
109
+ if (claimCode == 0 && fileExists("wl-ops-public/claim-${project.key}.json")) {
110
+ def claim = new groovy.json.JsonSlurperClassic().parseText(readFile("wl-ops-public/claim-${project.key}.json"))
111
+ if (claim.status == 'claimed') {
112
+ try {
113
+ def started = build(job: claim.job, wait: false, waitForStart: true, propagate: false,
114
+ parameters: [string(name: 'WL_REVIEW_SHA', value: claim.sha), string(name: 'WL_GATE_DISPATCH_ID', value: claim.dispatchId)])
115
+ withEnv(["WL_OPS_BUILD=${started.number}", "WL_OPS_DISPATCH=${claim.dispatchId}"]) {
116
+ sh '''#!/bin/sh
117
+ set -eu
118
+ node "$WL_QUALITY_CLI" jenkins ops ack-check --cluster "$WL_OPS_CLUSTER" --run-id "$WL_OPS_RUN" --target-key "$WL_OPS_KEY" --build-number "$WL_OPS_BUILD" --dispatch-id "$WL_OPS_DISPATCH" --out "$WL_OPS_ACK"
119
+ '''
120
+ }
121
+ echo "CHECK已启动:${claim.job} #${started.number};正式检查及Review结论以该运行原件为准。"
122
+ } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException interrupted) { throw interrupted }
123
+ catch (Exception dispatchFailure) { currentBuild.result = 'UNSTABLE'; echo 'CHECK启动未确认,已保留本轮记录;下轮提醒核对,不自动重复启动。' }
124
+ } else {
125
+ if (claim.status != 'already-started') currentBuild.result = 'UNSTABLE'
126
+ echo "CHECK承接:${claim.status};${claim.reason ?: '已有实际启动记录,未重复启动'}"
127
+ }
128
+ } else { currentBuild.result = 'UNSTABLE'; echo '本项目CHECK承接核验未通过,未启动;按本轮控制台缺项修复,其余项目继续。' }
129
+ }
130
+ }
131
+ }
132
+ } finally { archiveArtifacts artifacts: 'wl-ops-public/claim-*.json,wl-ops-public/ack-*.json', allowEmptyArchive: true }
133
+ }
134
+
2
135
  def wlFleetRun(Map cfg, String action, String batch = '', String planHash = '') {
3
136
  def code = 2
4
137
  withEnv(["WL_APPROVED_GATE_CLI=${cfg.gateRoot}/bin/wl-skills-gate.js",
@@ -58,7 +191,11 @@ def call(Map cfg, def bootstrap) {
58
191
  try {
59
192
  if (cfg.role != 'job') {
60
193
  def options = [disableConcurrentBuilds()]
61
- if (cfg.role == 'node') options.add(parameters([booleanParam(name: 'TEST_NOTIFICATION', defaultValue: false, description: '仅测试流水线机器人签名推送')]))
194
+ if (cfg.role == 'node') {
195
+ def enabled = params.ENABLE_MONITOR == true
196
+ options.add(parameters([booleanParam(name: 'TEST_NOTIFICATION', defaultValue: false, description: '仅测试流水线机器人签名推送'), booleanParam(name: 'ENABLE_MONITOR', defaultValue: enabled, description: '启用每5分钟发现项目、检查接入缺项并提醒;不代替业务九维检查'), booleanParam(name: 'RETRY_NOTIFICATION', defaultValue: false, description: '核对群记录后补发本轮缺项通知,可能重复;仅本次'), booleanParam(name: 'RETRY_UNCONFIRMED_CHECK', defaultValue: false, description: '核对队列和运行记录后重试未确认CHECK,可能重复;仅本次')]))
197
+ options.add(pipelineTriggers(enabled ? [cron('H/5 * * * *')] : []))
198
+ }
62
199
  if (cfg.role == 'fleet') options.add(parameters([
63
200
  choice(name: 'ACTION', choices: ['准备公共任务', '盘点', '预览', '接入', '续跑', '核验', '回退'], description: '首轮准备公共任务,之后按批准登记批量接入'),
64
201
  string(name: 'BATCH', defaultValue: '', description: '续跑、核验、回退填写原批次ID')]))
@@ -75,6 +212,7 @@ def call(Map cfg, def bootstrap) {
75
212
  load("${cfg.gateRoot}/templates/ci/jenkins-gate.groovy").call(cfg)
76
213
  } else if (cfg.role == 'node') {
77
214
  if (params.TEST_NOTIFICATION) stage('飞书连通测试') { bootstrap.wlGateNotificationTest() }
215
+ else wlOpsRun(cfg)
78
216
  } else if (cfg.role == 'verify') {
79
217
  stage('核验Gate接线') {
80
218
  withCredentials([usernamePassword(credentialsId: 'wl-jenkins-ci-readonly', usernameVariable: 'WL_JENKINS_READ_USER', passwordVariable: 'WL_JENKINS_READ_TOKEN')]) {
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "engines": { "node": "^22.13.0 || >=24.0.0" },
5
5
  "devDependencies": {
6
- "@agile-team/wl-skills-gate": "0.13.1",
6
+ "@agile-team/wl-skills-gate": "0.13.2",
7
7
  "@agile-team/wl-skills-bd": "0.41.1"
8
8
  }
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "lockVersion": 1,
3
- "wl-skills-gate": "0.13.1",
3
+ "wl-skills-gate": "0.13.2",
4
4
  "tools": {
5
5
  "TS-TYPECHECK": "<填写 vue-tsc --version 的完整输出>",
6
6
  "FE-ESLINT": "<填写 eslint --version 的完整输出>"