From 9a781229adbcdb575c024b779f49740ed974f7c0 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 25 May 2023 09:30:42 +0800 Subject: [PATCH 0001/1143] =?UTF-8?q?kubernetes-manager=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81docker=20inspect=20image=20#8862?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kubernetes-management/Chart.yaml | 4 +- .../templates/deployment.yaml | 30 ++- .../kubernetes-manager-configmap.yaml | 3 + .../kubernetes-management/values.yaml | 15 +- src/backend/dispatch-k8s-manager/Makefile | 5 +- .../cmd/apiserver/apiserver.go | 8 +- src/backend/dispatch-k8s-manager/go.mod | 44 ++-- src/backend/dispatch-k8s-manager/go.sum | 83 ++++--- .../pkg/apiserver/apis/apis.go | 1 + .../pkg/apiserver/apis/docker.go | 44 ++++ .../pkg/apiserver/apis/task.go | 4 +- .../pkg/apiserver/service/docker.go | 106 +++++++++ .../pkg/apiserver/service/docker_type.go | 18 ++ .../pkg/config/config_type.go | 5 + .../pkg/constant/constant.go | 5 + .../dispatch-k8s-manager/pkg/docker/docker.go | 91 ++++++++ .../dispatch-k8s-manager/pkg/docker/type.go | 12 + .../dispatch-k8s-manager/pkg/logs/logs.go | 15 +- .../pkg/task/builder_task.go | 29 +-- .../dispatch-k8s-manager/pkg/task/job_task.go | 23 +- .../dispatch-k8s-manager/pkg/task/task.go | 20 +- .../pkg/types/task_type.go | 6 + .../resources/config.yaml | 11 +- .../swagger/apiserver/docs.go | 212 ++++++++++++++++-- .../swagger/apiserver/swagger.json | 206 +++++++++++++++-- .../swagger/apiserver/swagger.yaml | 134 +++++++++-- .../swagger/init-swager.sh | 0 27 files changed, 1002 insertions(+), 132 deletions(-) create mode 100644 src/backend/dispatch-k8s-manager/pkg/apiserver/apis/docker.go create mode 100644 src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker.go create mode 100644 src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker_type.go create mode 100644 src/backend/dispatch-k8s-manager/pkg/constant/constant.go create mode 100644 src/backend/dispatch-k8s-manager/pkg/docker/docker.go create mode 100644 src/backend/dispatch-k8s-manager/pkg/docker/type.go mode change 100644 => 100755 src/backend/dispatch-k8s-manager/swagger/init-swager.sh diff --git a/helm-charts/core/ci/local_chart/kubernetes-management/Chart.yaml b/helm-charts/core/ci/local_chart/kubernetes-management/Chart.yaml index ccd88943079..fcfbe3d23f5 100644 --- a/helm-charts/core/ci/local_chart/kubernetes-management/Chart.yaml +++ b/helm-charts/core/ci/local_chart/kubernetes-management/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: kubernetes-manager description: A Helm chart for BlueKing CI Kubernetes Manager type: application -version: 0.0.36 -appVersion: 0.0.31 +version: 0.0.37 +appVersion: 0.0.32 home: https://github.com/Tencent/bk-ci dependencies: diff --git a/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml b/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml index ab40cdc0ca6..63110efdd33 100644 --- a/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml +++ b/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml @@ -67,6 +67,14 @@ spec: value: {{ .Values.multiCluster.enabled | quote }} - name: DEFAULT_NAMESPACE value: {{ .Values.multiCluster.defaultNamespace }} + {{- if .Values.kubernetesManager.docker.enable }} + - name: DOCKER_HOST + value: tcp://localhost:2375 + {{- end}} + {{- if .Values.kubernetesManager.debug }} + - name: KUBERNETES_MANAGER_DEBUG_ENABLE + value: true + {{- end}} workingDir: /data/workspace/kubernetes-manager livenessProbe: tcpSocket: @@ -90,8 +98,22 @@ spec: mountPath: /data/workspace/kubernetes-manager/config readOnly: true {{- end}} - {{- if .Values.configmap.enabled}} + {{- if .Values.kubernetesManager.docker.enable }} + - name: kuberentes-manager-docker + image: {{ .Values.kubernetesManager.docker.image }} + command: ["dockerd", "--host", "tcp://localhost:2375"] + {{- if .Values.kubernetesManager.docker.resources }} + resources: {{- toYaml .Values.kubernetesManager.docker.resources | nindent 12 }} + {{- end }} + securityContext: + privileged: true + volumeMounts: + - name: docker-graph-storage + mountPath: /var/lib/docker + {{- end }} + volumes: + {{- if .Values.configmap.enabled}} - name: kubernetes-manager-config configMap: name: kubernetes-manager @@ -101,6 +123,10 @@ spec: {{- if .Values.kubeConfig.useKubeConfig}} - key: kubeConfig.yaml path: kubeConfig.yaml - {{- end}} + {{- end}} + {{- if .Values.kubernetesManager.docker.enable }} + - name: docker-graph-storage + emptyDir: {} + {{- end}} {{- end}} {{- end -}} diff --git a/helm-charts/core/ci/local_chart/kubernetes-management/templates/kubernetes-manager-configmap.yaml b/helm-charts/core/ci/local_chart/kubernetes-management/templates/kubernetes-manager-configmap.yaml index 0b8c359d842..37da7e85e0d 100644 --- a/helm-charts/core/ci/local_chart/kubernetes-management/templates/kubernetes-manager-configmap.yaml +++ b/helm-charts/core/ci/local_chart/kubernetes-management/templates/kubernetes-manager-configmap.yaml @@ -109,6 +109,9 @@ data: rsaPrivateKey: | {{- .Values.kubernetesManager.apiserver.auth.rsaPrivateKey | nindent 10 }} + docker: + enable: {{ .Values.kubernetesManager.docker.enable }} + {{ if .Values.kubeConfig.useKubeConfig -}} kubeConfig.yaml: | {{- .Values.kubeConfig.content | nindent 4 }} diff --git a/helm-charts/core/ci/local_chart/kubernetes-management/values.yaml b/helm-charts/core/ci/local_chart/kubernetes-management/values.yaml index 01782a5852b..d418a1bc864 100644 --- a/helm-charts/core/ci/local_chart/kubernetes-management/values.yaml +++ b/helm-charts/core/ci/local_chart/kubernetes-management/values.yaml @@ -94,6 +94,7 @@ service: # kubernetesManager Deployment kubernetesManager: enabled: true + debug: false replicas: 1 resources: requests: @@ -147,11 +148,23 @@ kubernetesManager: apiToken: key: Devops-Token value: landun - rsaPrivateKey: | + rsaPrivateKey: "" volumeMount: # 流水线构建工作空间和agent日志在容器内的挂载点 dataPath: /data/devops/workspace logPath: /data/devops/logs + # manager使用docker相关配置 + docker: + enable: true + image: docker:24.0.1-dind + resources: + requests: + cpu: 50m + memory: 512Mi + limits: + cpu: 100m + memory: 1024Mi + dockerInit: # 是否使用当前chart的 dockerinit.sh useDockerInit: true diff --git a/src/backend/dispatch-k8s-manager/Makefile b/src/backend/dispatch-k8s-manager/Makefile index e6b0b5e5b93..030f21133e7 100644 --- a/src/backend/dispatch-k8s-manager/Makefile +++ b/src/backend/dispatch-k8s-manager/Makefile @@ -1,5 +1,5 @@ LOCAL_REGISTRY=bkci -LOCAL_IMAGE=kubernetes-manager:0.0.31 +LOCAL_IMAGE=kubernetes-manager:0.0.32 CONFIG_DIR=/data/workspace/kubernetes-manager/config OUT_DIR=/data/workspace/kubernetes-manager/out @@ -13,6 +13,9 @@ GOFLAGS := # gin export GIN_MODE=release +format: + find ./ -name "*.go" | xargs gofmt -w + test: test-unit .PHONY: test-unit diff --git a/src/backend/dispatch-k8s-manager/cmd/apiserver/apiserver.go b/src/backend/dispatch-k8s-manager/cmd/apiserver/apiserver.go index 159a82572b4..37076b1e838 100644 --- a/src/backend/dispatch-k8s-manager/cmd/apiserver/apiserver.go +++ b/src/backend/dispatch-k8s-manager/cmd/apiserver/apiserver.go @@ -3,9 +3,11 @@ package main import ( "disaptch-k8s-manager/pkg/apiserver" "disaptch-k8s-manager/pkg/config" + "disaptch-k8s-manager/pkg/constant" "disaptch-k8s-manager/pkg/cron" "disaptch-k8s-manager/pkg/db/mysql" "disaptch-k8s-manager/pkg/db/redis" + "disaptch-k8s-manager/pkg/docker" "disaptch-k8s-manager/pkg/kubeclient" "disaptch-k8s-manager/pkg/logs" "disaptch-k8s-manager/pkg/task" @@ -60,6 +62,10 @@ func main() { os.Exit(1) } + if config.Config.Docker.Enable { + docker.InitDockerCli() + } + if err := apiserver.InitApiServer(filepath.Join(outDir, "logs", config.AccessLog)); err != nil { fmt.Printf("init api server error %v\n", err) os.Exit(1) @@ -69,7 +75,7 @@ func main() { } func initConfig(configDir string) { - if debug == "true" { + if debug == "true" || os.Getenv(constant.KubernetesManagerDebugEnable) == "true" { config.Envs.IsDebug = true } else { config.Envs.IsDebug = false diff --git a/src/backend/dispatch-k8s-manager/go.mod b/src/backend/dispatch-k8s-manager/go.mod index b9803b9a281..67106179c12 100644 --- a/src/backend/dispatch-k8s-manager/go.mod +++ b/src/backend/dispatch-k8s-manager/go.mod @@ -1,8 +1,9 @@ module disaptch-k8s-manager -go 1.18 +go 1.19 require ( + github.com/docker/docker v24.0.1+incompatible github.com/gin-gonic/gin v1.8.1 github.com/go-playground/locales v0.14.0 github.com/go-playground/universal-translator v0.18.0 @@ -12,12 +13,13 @@ require ( github.com/gorilla/websocket v1.4.2 github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.0 - github.com/sirupsen/logrus v1.8.1 + github.com/sirupsen/logrus v1.9.0 github.com/spf13/viper v1.11.0 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe github.com/swaggo/gin-swagger v1.5.1 - github.com/swaggo/swag v1.8.4 + github.com/swaggo/swag v1.16.1 + golang.org/x/net v0.10.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 k8s.io/api v0.24.0 k8s.io/apimachinery v0.24.0 @@ -25,18 +27,21 @@ require ( ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful v2.16.0+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.2.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/goccy/go-json v0.9.7 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -49,12 +54,16 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -65,23 +74,24 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/tools v0.9.1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.4.0 // indirect k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/src/backend/dispatch-k8s-manager/go.sum b/src/backend/dispatch-k8s-manager/go.sum index 91cd0f1b950..3c7e9993f6d 100644 --- a/src/backend/dispatch-k8s-manager/go.sum +++ b/src/backend/dispatch-k8s-manager/go.sum @@ -39,6 +39,7 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= @@ -46,15 +47,16 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -72,6 +74,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.1+incompatible h1:NxN81beIxDlUaVt46iUQrYHD9/W3u9EGl52r86O/IGw= +github.com/docker/docker v24.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -107,18 +117,23 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -249,20 +264,25 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -279,6 +299,10 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -304,8 +328,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -322,6 +346,7 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -329,8 +354,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= @@ -338,8 +364,8 @@ github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9J github.com/swaggo/gin-swagger v1.5.1 h1:PFmlJU1LPn8DjrR0meVLX5gyFdgcPOkLcoFRRFx7WcY= github.com/swaggo/gin-swagger v1.5.1/go.mod h1:Cbj/MlHApPOjZdf4joWFXLLgmZVPyh54GPvPPyVjVZM= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= -github.com/swaggo/swag v1.8.4 h1:oGB351qH1JqUqK1tsMYEE5qTBbPk394BhsZxmUfebcI= -github.com/swaggo/swag v1.8.4/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= +github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= +github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -404,7 +430,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -448,8 +475,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -475,6 +502,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -488,7 +516,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -528,12 +555,13 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -543,8 +571,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -603,8 +631,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -743,6 +771,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -774,5 +804,6 @@ sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go index d0e71d6ec6c..73f04e2083d 100644 --- a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go @@ -36,6 +36,7 @@ func InitApis(r *gin.Engine, handlers ...gin.HandlerFunc) { initJobsApis(apis) initBuilderApis(apis) initTasksApis(apis) + initDockerApis(apis) } func ok(c *gin.Context, data interface{}) { diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/docker.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/docker.go new file mode 100644 index 00000000000..c065d92cd2c --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/docker.go @@ -0,0 +1,44 @@ +package apis + +import ( + "disaptch-k8s-manager/pkg/apiserver/service" + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + dockerPrefix = "/docker" +) + +func initDockerApis(r *gin.RouterGroup) { + docker := r.Group(dockerPrefix) + { + docker.POST("/inspect", dockerInspect) + } +} + +// @Tags docker +// @Summary docker inspect命令(同时会pull) +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param info body service.DockerInspectInfo true "构建机信息" +// @Success 200 {object} types.Result{data=service.TaskId} "任务ID" +// @Router /docker/inspect [post] +func dockerInspect(c *gin.Context) { + info := &service.DockerInspectInfo{} + + if err := c.BindJSON(info); err != nil { + fail(c, http.StatusBadRequest, err) + return + } + + taskId, err := service.DockerInspect(info) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, service.TaskId{TaskId: taskId}) +} diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/task.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/task.go index e7bcbd6e7cd..216c0ea27ac 100644 --- a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/task.go +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/task.go @@ -8,9 +8,9 @@ import ( ) func initTasksApis(r *gin.RouterGroup) { - jobs := r.Group("/tasks") + tasks := r.Group("/tasks") { - jobs.GET("/:taskId/status", getTaskStatus) + tasks .GET("/:taskId/status", getTaskStatus) } } diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker.go new file mode 100644 index 00000000000..9179adf5e96 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker.go @@ -0,0 +1,106 @@ +package service + +import ( + "context" + "disaptch-k8s-manager/pkg/db/mysql" + "disaptch-k8s-manager/pkg/docker" + "disaptch-k8s-manager/pkg/logs" + "disaptch-k8s-manager/pkg/task" + "disaptch-k8s-manager/pkg/types" + "encoding/json" + "strings" + "time" + + dockerTypes "github.com/docker/docker/api/types" + "github.com/pkg/errors" +) + +func DockerInspect(info *DockerInspectInfo) (string, error) { + taskId := generateTaskId() + + if err := mysql.InsertTask(types.Task{ + TaskId: taskId, + TaskKey: info.Name, + TaskBelong: types.TaskBelongDocker, + Action: types.TaskDockerActionInspect, + Status: types.TaskWaiting, + Message: nil, + ActionTime: time.Now(), + UpdateTime: time.Now(), + }); err != nil { + return "", err + } + + go inspect(taskId, info) + + return taskId, nil +} + +func inspect(taskId string, info *DockerInspectInfo) { + task.UpdateTask(taskId, types.TaskRunning) + + ctx := context.Background() + + // 拉取镜像 + pullMsg, err := docker.ImagePull(ctx, info.Ref, info.Credential.Username, info.Credential.Password) + if err != nil { + logs.Error("inspect ImagePull error", err) + task.FailTask(taskId, err.Error()) + return + } + + // 寻找ID + imageName := strings.TrimSpace(info.Ref) + imageStr := strings.TrimPrefix(strings.TrimPrefix(imageName, "http://"), "https://") + images, err := docker.ImageList(ctx) + if err != nil { + logs.Error("get image list error", err) + task.FailTask(taskId, err.Error()) + return + } + id := "" + for _, image := range images { + for _, tagName := range image.RepoTags { + if tagName == imageStr { + id = image.ID + } + } + } + if id == "" { + err = errors.Errorf("image %s not found", imageName) + logs.Errorf("pullMsg %s error %s", pullMsg, err.Error()) + task.FailTask(taskId, err.Error()) + return + } + + defer func() { + // 完事后删除镜像 + if err = docker.ImageRemove(ctx, id, dockerTypes.ImageRemoveOptions{Force: true}); err != nil { + logs.Errorf("remove image %s id %s error %s", info.Ref, id, err.Error()) + } + }() + + // 分析镜像 + image, err := docker.ImageInspect(ctx, info.Ref) + if err != nil { + logs.Error("inspect ImageInspect error", err) + task.FailTask(taskId, err.Error()) + return + } + + msg := &DockerInspectResp{ + Architecture: image.Architecture, + Os: image.Os, + Size: image.Size, + } + + msgStr, err := json.Marshal(msg) + if err != nil { + logs.Error("inspect jsonMarshal error", err) + task.FailTask(taskId, err.Error()) + return + } + + task.OkTaskWithMessage(taskId, string(msgStr)) + +} diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker_type.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker_type.go new file mode 100644 index 00000000000..4cee25c7c13 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/service/docker_type.go @@ -0,0 +1,18 @@ +package service + +type DockerInspectInfo struct { + Name string `json:"name" binding:"required"` // 任务名称,唯一 + Ref string `json:"ref" binding:"required"` // docker镜像信息 如:docker:latest + Credential Credential `json:"cred"` // 拉取镜像凭据 +} + +type Credential struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type DockerInspectResp struct { + Architecture string `json:"arch"` // 架构 + Os string `json:"os"` // 系统 + Size int64 `json:"size"` // 大小 +} diff --git a/src/backend/dispatch-k8s-manager/pkg/config/config_type.go b/src/backend/dispatch-k8s-manager/pkg/config/config_type.go index 1b8ae7e51e6..7d8d44d3256 100644 --- a/src/backend/dispatch-k8s-manager/pkg/config/config_type.go +++ b/src/backend/dispatch-k8s-manager/pkg/config/config_type.go @@ -9,6 +9,7 @@ type ConfigYaml struct { Dispatch Dispatch `json:"dispatch"` BuildAndPushImage BuildAndPushImage `json:"buildAndPushImage"` ApiServer ApiServer `json:"apiServer"` + Docker Docker `json:"docker"` } type Server struct { @@ -135,3 +136,7 @@ type ApiToken struct { Key string `json:"key"` Value string `json:"value"` } + +type Docker struct { + Enable bool `json:"enable"` +} diff --git a/src/backend/dispatch-k8s-manager/pkg/constant/constant.go b/src/backend/dispatch-k8s-manager/pkg/constant/constant.go new file mode 100644 index 00000000000..444b4a13be6 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/constant/constant.go @@ -0,0 +1,5 @@ +package constant + +const ( + KubernetesManagerDebugEnable = "KUBERNETES_MANAGER_DEBUG_ENABLE" +) diff --git a/src/backend/dispatch-k8s-manager/pkg/docker/docker.go b/src/backend/dispatch-k8s-manager/pkg/docker/docker.go new file mode 100644 index 00000000000..ca4991f3a5a --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/docker/docker.go @@ -0,0 +1,91 @@ +package docker + +import ( + "encoding/base64" + "encoding/json" + "io" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/pkg/errors" + "golang.org/x/net/context" +) + +var cli *client.Client + +func InitDockerCli() error { + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return err + } + cli = c + + return nil +} + +func ImageList(ctx context.Context) ([]types.ImageSummary, error) { + images, err := cli.ImageList(ctx, types.ImageListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list image error") + } + + return images, nil +} + +func ImagePull( + ctx context.Context, + ref string, + username string, + password string, +) (string, error) { + imageName := strings.TrimSpace(ref) + + reader, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{ + RegistryAuth: generateDockerAuth(username, password), + }) + if err != nil { + return "", errors.Wrap(err, "pull new image error") + } + defer reader.Close() + buf := new(strings.Builder) + _, _ = io.Copy(buf, reader) + + return buf.String(), nil +} + +func ImageInspect(ctx context.Context, imageId string) (*types.ImageInspect, error) { + image, _, err := cli.ImageInspectWithRaw(ctx, imageId) + if err != nil { + return nil, errors.Wrap(err, "image inspect error") + } + + return &image, nil +} + +func ImageRemove(ctx context.Context, imageId string, opts types.ImageRemoveOptions) error { + _, err := cli.ImageRemove(ctx, imageId, opts) + if err != nil { + return err + } + + return nil +} + +// generateDockerAuth 创建拉取docker凭据 +func generateDockerAuth(user, password string) string { + if user == "" || password == "" { + return "" + } + + authConfig := types.AuthConfig{ + Username: user, + Password: password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + panic(err) + } + + return base64.URLEncoding.EncodeToString(encodedJSON) +} diff --git a/src/backend/dispatch-k8s-manager/pkg/docker/type.go b/src/backend/dispatch-k8s-manager/pkg/docker/type.go new file mode 100644 index 00000000000..c89a0d84d55 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/docker/type.go @@ -0,0 +1,12 @@ +package docker + +type ImagePullPolicyEnum string + +const ( + ImagePullPolicyAlways ImagePullPolicyEnum = "always" + ImagePullPolicyIfNotPresent ImagePullPolicyEnum = "if-not-present" +) + +func (i ImagePullPolicyEnum) String() string { + return string(i) +} diff --git a/src/backend/dispatch-k8s-manager/pkg/logs/logs.go b/src/backend/dispatch-k8s-manager/pkg/logs/logs.go index 21b03f0c10d..78aa7a1ecfe 100644 --- a/src/backend/dispatch-k8s-manager/pkg/logs/logs.go +++ b/src/backend/dispatch-k8s-manager/pkg/logs/logs.go @@ -5,9 +5,6 @@ import ( "disaptch-k8s-manager/pkg/config" "disaptch-k8s-manager/pkg/types" "fmt" - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" - "gopkg.in/natefinch/lumberjack.v2" "net" "net/http" "net/http/httputil" @@ -15,6 +12,10 @@ import ( "path/filepath" "runtime/debug" "strings" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "gopkg.in/natefinch/lumberjack.v2" ) var Logs *logrus.Logger @@ -118,3 +119,11 @@ func Warn(args ...interface{}) { func Error(args ...interface{}) { Logs.Error(args...) } + +func Errorf(format string, args ...interface{}) { + Logs.Errorf(format, args...) +} + +func WithError(err error) *logrus.Entry { + return Logs.WithError(err) +} diff --git a/src/backend/dispatch-k8s-manager/pkg/task/builder_task.go b/src/backend/dispatch-k8s-manager/pkg/task/builder_task.go index e0c8f6ed7bb..469a9d31652 100644 --- a/src/backend/dispatch-k8s-manager/pkg/task/builder_task.go +++ b/src/backend/dispatch-k8s-manager/pkg/task/builder_task.go @@ -9,17 +9,18 @@ import ( "disaptch-k8s-manager/pkg/prometheus" "disaptch-k8s-manager/pkg/types" "fmt" + "time" + "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/watch" - "time" ) func DoCreateBuilder(taskId string, dep *kubeclient.Deployment) { _, err := kubeclient.CreateDockerRegistry(dep.Pod.PullImageSecret) if err != nil { - failTask(taskId, errors.Wrap(err, "create builder pull image secret error").Error()) + FailTask(taskId, errors.Wrap(err, "create builder pull image secret error").Error()) return } @@ -29,14 +30,14 @@ func DoCreateBuilder(taskId string, dep *kubeclient.Deployment) { } // 创建失败后的操作 - failTask(taskId, errors.Wrap(err, "create builder error").Error()) + FailTask(taskId, errors.Wrap(err, "create builder error").Error()) deleteBuilderLinkRes(dep.Name) } func DoStartBuilder(taskId string, builderName string, data []byte) { err := kubeclient.PatchDeployment(builderName, data) if err != nil { - failTask(taskId, errors.Wrap(err, "start builder error").Error()) + FailTask(taskId, errors.Wrap(err, "start builder error").Error()) return } } @@ -55,7 +56,7 @@ func DoStopBuilder(taskId string, builderName string, data []byte) { err = kubeclient.PatchDeployment(builderName, data) if err != nil { - failTask(taskId, errors.Wrap(err, "stop builder error").Error()) + FailTask(taskId, errors.Wrap(err, "stop builder error").Error()) return } @@ -123,14 +124,14 @@ func saveRealResourceUsage(builderName string, pods []*corev1.Pod) error { func DoDeleteBuilder(taskId string, builderName string) { err := kubeclient.DeleteDeployment(builderName) if err != nil { - failTask(taskId, errors.Wrap(err, "delete builder error").Error()) + FailTask(taskId, errors.Wrap(err, "delete builder error").Error()) return } deleteBuilderLinkRes(builderName) deleteBuilderLinkDbData(builderName) - okTask(taskId) + OkTask(taskId) } // deleteBuilderLinkRes 删除构建机相关联的kubernetes资源 @@ -177,7 +178,7 @@ func watchBuilderTaskPodCreateOrStart(event watch.Event, pod *corev1.Pod, taskId { switch podStatus.Phase { case corev1.PodPending: - updateTask(taskId, types.TaskRunning) + UpdateTask(taskId, types.TaskRunning) // 对于task的start/create来说,启动了就算成功,而不关心启动成功还是失败了 case corev1.PodRunning, corev1.PodSucceeded, corev1.PodFailed: { @@ -206,7 +207,7 @@ func watchBuilderTaskPodCreateOrStart(event watch.Event, pod *corev1.Pod, taskId } defer redis.UnLock(key) - okTask(taskId) + OkTask(taskId) // mysql中保存分配至节点成功的构建机最近三次节点信息,用来做下一次调度的依据 if builderName == "" { @@ -222,13 +223,13 @@ func watchBuilderTaskPodCreateOrStart(event watch.Event, pod *corev1.Pod, taskId return } case corev1.PodUnknown: - updateTask(taskId, types.TaskUnknown) + UpdateTask(taskId, types.TaskUnknown) } } case watch.Error: { logs.Error("add job error. ", pod) - failTask(taskId, podStatus.Message+"|"+podStatus.Reason) + FailTask(taskId, podStatus.Message+"|"+podStatus.Reason) } } } @@ -265,14 +266,14 @@ func watchBuilderTaskDeploymentStop(event watch.Event, dep *appsv1.Deployment, t switch event.Type { case watch.Modified: if dep.Spec.Replicas != nil && *dep.Spec.Replicas == 0 { - okTask(taskId) + OkTask(taskId) } case watch.Error: logs.Error("stop builder error. ", dep) if len(dep.Status.Conditions) > 0 { - failTask(taskId, dep.Status.Conditions[0].String()) + FailTask(taskId, dep.Status.Conditions[0].String()) } else { - failTask(taskId, "stop builder error") + FailTask(taskId, "stop builder error") } } } diff --git a/src/backend/dispatch-k8s-manager/pkg/task/job_task.go b/src/backend/dispatch-k8s-manager/pkg/task/job_task.go index 0c8abc352c4..478363cdd98 100644 --- a/src/backend/dispatch-k8s-manager/pkg/task/job_task.go +++ b/src/backend/dispatch-k8s-manager/pkg/task/job_task.go @@ -5,6 +5,7 @@ import ( "disaptch-k8s-manager/pkg/logs" "disaptch-k8s-manager/pkg/types" "fmt" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/watch" @@ -18,12 +19,12 @@ func DoCreateBuildAndPushImageJob( // 创建镜像拉取凭据 _, err := kubeclient.CreateDockerRegistry(job.Pod.PullImageSecret) if err != nil { - failTask(taskId, errors.Wrap(err, "create build and push image job pull image secret error").Error()) + FailTask(taskId, errors.Wrap(err, "create build and push image job pull image secret error").Error()) return } if _, err = kubeclient.CreateDockerRegistry(kanikoSecret); err != nil { - failTask(taskId, errors.Wrap(err, "create build and push image push secret error").Error()) + FailTask(taskId, errors.Wrap(err, "create build and push image push secret error").Error()) return } @@ -32,14 +33,14 @@ func DoCreateBuildAndPushImageJob( return } - failTask(taskId, errors.Wrap(err, "create job error").Error()) + FailTask(taskId, errors.Wrap(err, "create job error").Error()) deleteJobLinkRes(job.Name) } func DoCreateJob(taskId string, job *kubeclient.Job) { _, err := kubeclient.CreateDockerRegistry(job.Pod.PullImageSecret) if err != nil { - failTask(taskId, errors.Wrap(err, "create job pull image secret error").Error()) + FailTask(taskId, errors.Wrap(err, "create job pull image secret error").Error()) return } @@ -49,7 +50,7 @@ func DoCreateJob(taskId string, job *kubeclient.Job) { } // 创建失败后的操作 - failTask(taskId, errors.Wrap(err, "create job error").Error()) + FailTask(taskId, errors.Wrap(err, "create job error").Error()) deleteJobLinkRes(job.Name) } @@ -57,13 +58,13 @@ func DoCreateJob(taskId string, job *kubeclient.Job) { func DoDeleteJob(taskId string, jobName string) { err := kubeclient.DeleteJob(jobName) if err != nil { - failTask(taskId, errors.Wrap(err, "delete job error").Error()) + FailTask(taskId, errors.Wrap(err, "delete job error").Error()) return } deleteJobLinkRes(jobName) - okTask(taskId) + OkTask(taskId) } // deleteJobLinkRes 删除JOB相关联的kubernetes资源 @@ -103,18 +104,18 @@ func watchJobTaskPodCreateOrStart(event watch.Event, pod *corev1.Pod, taskId str { switch podStatus.Phase { case corev1.PodPending: - updateTask(taskId, types.TaskRunning) + UpdateTask(taskId, types.TaskRunning) // 对于task的start/create来说,启动了就算成功,而不关系启动成功还是失败了 case corev1.PodRunning, corev1.PodSucceeded, corev1.PodFailed: - okTask(taskId) + OkTask(taskId) case corev1.PodUnknown: - updateTask(taskId, types.TaskUnknown) + UpdateTask(taskId, types.TaskUnknown) } } case watch.Error: { logs.Error("add job error. ", pod) - failTask(taskId, podStatus.Message+"|"+podStatus.Reason) + FailTask(taskId, podStatus.Message+"|"+podStatus.Reason) } } } diff --git a/src/backend/dispatch-k8s-manager/pkg/task/task.go b/src/backend/dispatch-k8s-manager/pkg/task/task.go index dc9fbf4c388..bb9bbf4fbff 100644 --- a/src/backend/dispatch-k8s-manager/pkg/task/task.go +++ b/src/backend/dispatch-k8s-manager/pkg/task/task.go @@ -4,7 +4,6 @@ import ( "disaptch-k8s-manager/pkg/db/mysql" "disaptch-k8s-manager/pkg/logs" "disaptch-k8s-manager/pkg/types" - "github.com/pkg/errors" ) func InitTask() { @@ -12,23 +11,30 @@ func InitTask() { go WatchTaskDeployment() } -func okTask(taskId string) { +func OkTask(taskId string) { err := mysql.UpdateTask(taskId, types.TaskSucceeded, "") if err != nil { - logs.Error(errors.Wrapf(err, "save okTask %s %s error. ", taskId, "")) + logs.Errorf("save OkTask %s error %s", taskId, err.Error()) } } -func updateTask(taskId string, state types.TaskState) { +func OkTaskWithMessage(taskId string, message string) { + err := mysql.UpdateTask(taskId, types.TaskSucceeded, message) + if err != nil { + logs.Errorf("save OkTaskWithMessage %s %s error %s", taskId, message, err.Error()) + } +} + +func UpdateTask(taskId string, state types.TaskState) { err := mysql.UpdateTask(taskId, state, "") if err != nil { - logs.Error(errors.Wrapf(err, "update okTask %s %s error. ", taskId, "")) + logs.Errorf("save UpdateTask %s %s error %s", taskId, state, err.Error()) } } -func failTask(taskId string, message string) { +func FailTask(taskId string, message string) { err := mysql.UpdateTask(taskId, types.TaskFailed, message) if err != nil { - logs.Error(errors.Wrapf(err, "save failTask %s %s error. ", taskId, message)) + logs.Errorf("save FailTask %s %s error %s", taskId, message, err.Error()) } } diff --git a/src/backend/dispatch-k8s-manager/pkg/types/task_type.go b/src/backend/dispatch-k8s-manager/pkg/types/task_type.go index ce8bdb9bdd9..75f5bc54043 100644 --- a/src/backend/dispatch-k8s-manager/pkg/types/task_type.go +++ b/src/backend/dispatch-k8s-manager/pkg/types/task_type.go @@ -23,6 +23,11 @@ const ( TaskActionDelete TaskAction = "delete" ) +// docker 交互操作 +const ( + TaskDockerActionInspect TaskAction = "inspect" +) + type TaskLabelType string const ( @@ -36,6 +41,7 @@ type TaskBelong string const ( TaskBelongBuilder = "builder" TaskBelongJob = "job" + TaskBelongDocker = "docker" ) type Task struct { diff --git a/src/backend/dispatch-k8s-manager/resources/config.yaml b/src/backend/dispatch-k8s-manager/resources/config.yaml index f54d2fc5e10..3b68ed3b474 100644 --- a/src/backend/dispatch-k8s-manager/resources/config.yaml +++ b/src/backend/dispatch-k8s-manager/resources/config.yaml @@ -2,7 +2,7 @@ server: port: 8081 mysql: - dataSourceName: root:123456@tcp(localhost:3306)/devops_kubernetes_manager?parseTime=true&loc=Local + dataSourceName: root:123456@tcp(localhost:3306)/devops_ci_kubernetes_manager?parseTime=true&loc=Local connMaxLifetime: 3 maxOpenConns: 10 maxIdleConns: 10 @@ -86,6 +86,9 @@ apiserver: apiToken: key: Devops-Token value: landun - rsaPrivateKey: | - # 在这里保存私钥用来解密apitoken - # 推荐使用rsa-generate生成公私钥,rsa-generate可通过make打包获得 + # 在这里保存私钥用来解密apitoken + # 推荐使用rsa-generate生成公私钥,rsa-generate可通过make打包获得 + rsaPrivateKey: "" + +docker: + enable: true diff --git a/src/backend/dispatch-k8s-manager/swagger/apiserver/docs.go b/src/backend/dispatch-k8s-manager/swagger/apiserver/docs.go index 609c9e87d6b..b1a5352f9bf 100644 --- a/src/backend/dispatch-k8s-manager/swagger/apiserver/docs.go +++ b/src/backend/dispatch-k8s-manager/swagger/apiserver/docs.go @@ -1,5 +1,5 @@ -// Package apiserver GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT. + package apiserver import "github.com/swaggo/swag" @@ -309,6 +309,55 @@ const docTemplate = `{ } } }, + "/docker/inspect": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "docker" + ], + "summary": "docker inspect命令(同时会pull)", + "parameters": [ + { + "type": "string", + "description": "凭证信息", + "name": "Devops-Token", + "in": "header", + "required": true + }, + { + "description": "构建机信息", + "name": "info", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/service.DockerInspectInfo" + } + } + ], + "responses": { + "200": { + "description": "任务ID", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.Result" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/service.TaskId" + } + } + } + ] + } + } + } + } + }, "/jobs": { "post": { "consumes": [ @@ -618,7 +667,11 @@ const docTemplate = `{ }, "info": { "description": "构建并推送镜像的具体信息", - "$ref": "#/definitions/service.buildImageInfo" + "allOf": [ + { + "$ref": "#/definitions/service.buildImageInfo" + } + ] }, "name": { "description": "唯一名称", @@ -627,11 +680,19 @@ const docTemplate = `{ }, "podNameSelector": { "description": "Pod名称调度", - "$ref": "#/definitions/service.PodNameSelector" + "allOf": [ + { + "$ref": "#/definitions/service.PodNameSelector" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] } } }, @@ -675,19 +736,35 @@ const docTemplate = `{ }, "privateBuilder": { "description": "私有构建机配置", - "$ref": "#/definitions/service.DedicatedBuilder" + "allOf": [ + { + "$ref": "#/definitions/service.DedicatedBuilder" + } + ] }, "registry": { "description": "镜像凭证", - "$ref": "#/definitions/types.Registry" + "allOf": [ + { + "$ref": "#/definitions/types.Registry" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] }, "specialBuilder": { "description": "特殊构建机配置", - "$ref": "#/definitions/service.DedicatedBuilder" + "allOf": [ + { + "$ref": "#/definitions/service.DedicatedBuilder" + } + ] } } }, @@ -708,6 +785,27 @@ const docTemplate = `{ } } }, + "service.BuilderState": { + "type": "string", + "enum": [ + "readyToRun", + "notExist", + "pending", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "BuilderReadyToRun", + "BuilderNotExist", + "BuilderPending", + "BuilderRunning", + "BuilderSucceeded", + "BuilderFailed", + "BuilderUnknown" + ] + }, "service.BuilderStatus": { "type": "object", "properties": { @@ -715,7 +813,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/service.BuilderState" } } }, @@ -766,6 +864,17 @@ const docTemplate = `{ } } }, + "service.Credential": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "service.DedicatedBuilder": { "type": "object", "properties": { @@ -774,6 +883,31 @@ const docTemplate = `{ } } }, + "service.DockerInspectInfo": { + "type": "object", + "required": [ + "name", + "ref" + ], + "properties": { + "cred": { + "description": "拉取镜像凭据", + "allOf": [ + { + "$ref": "#/definitions/service.Credential" + } + ] + }, + "name": { + "description": "任务名称,唯一", + "type": "string" + }, + "ref": { + "description": "docker镜像信息 如:docker:latest", + "type": "string" + } + } + }, "service.Job": { "type": "object", "required": [ @@ -818,18 +952,47 @@ const docTemplate = `{ }, "podNameSelector": { "description": "Pod名称调度选项", - "$ref": "#/definitions/service.PodNameSelector" + "allOf": [ + { + "$ref": "#/definitions/service.PodNameSelector" + } + ] }, "registry": { "description": "镜像凭证", - "$ref": "#/definitions/types.Registry" + "allOf": [ + { + "$ref": "#/definitions/types.Registry" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] } } }, + "service.JobState": { + "type": "string", + "enum": [ + "pending", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "JobPending", + "JobRunning", + "JobSucceeded", + "JobFailed", + "JobUnknown" + ] + }, "service.JobStatus": { "type": "object", "properties": { @@ -840,7 +1003,7 @@ const docTemplate = `{ "type": "string" }, "state": { - "type": "string" + "$ref": "#/definitions/service.JobState" } } }, @@ -875,7 +1038,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/types.TaskState" } } }, @@ -958,6 +1121,23 @@ const docTemplate = `{ "type": "integer" } } + }, + "types.TaskState": { + "type": "string", + "enum": [ + "waiting", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "TaskWaiting", + "TaskRunning", + "TaskSucceeded", + "TaskFailed", + "TaskUnknown" + ] } } }` @@ -972,6 +1152,8 @@ var SwaggerInfo = &swag.Spec{ Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.json b/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.json index d53e59cc2a0..66563b9aa23 100644 --- a/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.json +++ b/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.json @@ -300,6 +300,55 @@ } } }, + "/docker/inspect": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "docker" + ], + "summary": "docker inspect命令(同时会pull)", + "parameters": [ + { + "type": "string", + "description": "凭证信息", + "name": "Devops-Token", + "in": "header", + "required": true + }, + { + "description": "构建机信息", + "name": "info", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/service.DockerInspectInfo" + } + } + ], + "responses": { + "200": { + "description": "任务ID", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.Result" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/service.TaskId" + } + } + } + ] + } + } + } + } + }, "/jobs": { "post": { "consumes": [ @@ -609,7 +658,11 @@ }, "info": { "description": "构建并推送镜像的具体信息", - "$ref": "#/definitions/service.buildImageInfo" + "allOf": [ + { + "$ref": "#/definitions/service.buildImageInfo" + } + ] }, "name": { "description": "唯一名称", @@ -618,11 +671,19 @@ }, "podNameSelector": { "description": "Pod名称调度", - "$ref": "#/definitions/service.PodNameSelector" + "allOf": [ + { + "$ref": "#/definitions/service.PodNameSelector" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] } } }, @@ -666,19 +727,35 @@ }, "privateBuilder": { "description": "私有构建机配置", - "$ref": "#/definitions/service.DedicatedBuilder" + "allOf": [ + { + "$ref": "#/definitions/service.DedicatedBuilder" + } + ] }, "registry": { "description": "镜像凭证", - "$ref": "#/definitions/types.Registry" + "allOf": [ + { + "$ref": "#/definitions/types.Registry" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] }, "specialBuilder": { "description": "特殊构建机配置", - "$ref": "#/definitions/service.DedicatedBuilder" + "allOf": [ + { + "$ref": "#/definitions/service.DedicatedBuilder" + } + ] } } }, @@ -699,6 +776,27 @@ } } }, + "service.BuilderState": { + "type": "string", + "enum": [ + "readyToRun", + "notExist", + "pending", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "BuilderReadyToRun", + "BuilderNotExist", + "BuilderPending", + "BuilderRunning", + "BuilderSucceeded", + "BuilderFailed", + "BuilderUnknown" + ] + }, "service.BuilderStatus": { "type": "object", "properties": { @@ -706,7 +804,7 @@ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/service.BuilderState" } } }, @@ -757,6 +855,17 @@ } } }, + "service.Credential": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "service.DedicatedBuilder": { "type": "object", "properties": { @@ -765,6 +874,31 @@ } } }, + "service.DockerInspectInfo": { + "type": "object", + "required": [ + "name", + "ref" + ], + "properties": { + "cred": { + "description": "拉取镜像凭据", + "allOf": [ + { + "$ref": "#/definitions/service.Credential" + } + ] + }, + "name": { + "description": "任务名称,唯一", + "type": "string" + }, + "ref": { + "description": "docker镜像信息 如:docker:latest", + "type": "string" + } + } + }, "service.Job": { "type": "object", "required": [ @@ -809,18 +943,47 @@ }, "podNameSelector": { "description": "Pod名称调度选项", - "$ref": "#/definitions/service.PodNameSelector" + "allOf": [ + { + "$ref": "#/definitions/service.PodNameSelector" + } + ] }, "registry": { "description": "镜像凭证", - "$ref": "#/definitions/types.Registry" + "allOf": [ + { + "$ref": "#/definitions/types.Registry" + } + ] }, "resource": { "description": "工作负载资源", - "$ref": "#/definitions/service.CommonWorkLoadResource" + "allOf": [ + { + "$ref": "#/definitions/service.CommonWorkLoadResource" + } + ] } } }, + "service.JobState": { + "type": "string", + "enum": [ + "pending", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "JobPending", + "JobRunning", + "JobSucceeded", + "JobFailed", + "JobUnknown" + ] + }, "service.JobStatus": { "type": "object", "properties": { @@ -831,7 +994,7 @@ "type": "string" }, "state": { - "type": "string" + "$ref": "#/definitions/service.JobState" } } }, @@ -866,7 +1029,7 @@ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/types.TaskState" } } }, @@ -949,6 +1112,23 @@ "type": "integer" } } + }, + "types.TaskState": { + "type": "string", + "enum": [ + "waiting", + "running", + "succeeded", + "failed", + "unknown" + ], + "x-enum-varnames": [ + "TaskWaiting", + "TaskRunning", + "TaskSucceeded", + "TaskFailed", + "TaskUnknown" + ] } } } \ No newline at end of file diff --git a/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.yaml b/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.yaml index 03fecc91edb..96ebce4efc4 100644 --- a/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.yaml +++ b/src/backend/dispatch-k8s-manager/swagger/apiserver/swagger.yaml @@ -6,17 +6,20 @@ definitions: description: Job存活时间 type: integer info: - $ref: '#/definitions/service.buildImageInfo' + allOf: + - $ref: '#/definitions/service.buildImageInfo' description: 构建并推送镜像的具体信息 name: description: 唯一名称 maxLength: 32 type: string podNameSelector: - $ref: '#/definitions/service.PodNameSelector' + allOf: + - $ref: '#/definitions/service.PodNameSelector' description: Pod名称调度 resource: - $ref: '#/definitions/service.CommonWorkLoadResource' + allOf: + - $ref: '#/definitions/service.CommonWorkLoadResource' description: 工作负载资源 required: - info @@ -49,16 +52,20 @@ definitions: $ref: '#/definitions/types.NFS' type: array privateBuilder: - $ref: '#/definitions/service.DedicatedBuilder' + allOf: + - $ref: '#/definitions/service.DedicatedBuilder' description: 私有构建机配置 registry: - $ref: '#/definitions/types.Registry' + allOf: + - $ref: '#/definitions/types.Registry' description: 镜像凭证 resource: - $ref: '#/definitions/service.CommonWorkLoadResource' + allOf: + - $ref: '#/definitions/service.CommonWorkLoadResource' description: 工作负载资源 specialBuilder: - $ref: '#/definitions/service.DedicatedBuilder' + allOf: + - $ref: '#/definitions/service.DedicatedBuilder' description: 特殊构建机配置 required: - image @@ -76,12 +83,30 @@ definitions: type: string type: object type: object + service.BuilderState: + enum: + - readyToRun + - notExist + - pending + - running + - succeeded + - failed + - unknown + type: string + x-enum-varnames: + - BuilderReadyToRun + - BuilderNotExist + - BuilderPending + - BuilderRunning + - BuilderSucceeded + - BuilderFailed + - BuilderUnknown service.BuilderStatus: properties: message: type: string status: - type: string + $ref: '#/definitions/service.BuilderState' type: object service.CommonWorkLoadResource: properties: @@ -119,11 +144,34 @@ definitions: - requestDiskIO - requestMem type: object + service.Credential: + properties: + password: + type: string + username: + type: string + type: object service.DedicatedBuilder: properties: name: type: string type: object + service.DockerInspectInfo: + properties: + cred: + allOf: + - $ref: '#/definitions/service.Credential' + description: 拉取镜像凭据 + name: + description: 任务名称,唯一 + type: string + ref: + description: docker镜像信息 如:docker:latest + type: string + required: + - name + - ref + type: object service.Job: properties: activeDeadlineSeconds: @@ -152,19 +200,36 @@ definitions: $ref: '#/definitions/types.NFS' type: array podNameSelector: - $ref: '#/definitions/service.PodNameSelector' + allOf: + - $ref: '#/definitions/service.PodNameSelector' description: Pod名称调度选项 registry: - $ref: '#/definitions/types.Registry' + allOf: + - $ref: '#/definitions/types.Registry' description: 镜像凭证 resource: - $ref: '#/definitions/service.CommonWorkLoadResource' + allOf: + - $ref: '#/definitions/service.CommonWorkLoadResource' description: 工作负载资源 required: - image - name - resource type: object + service.JobState: + enum: + - pending + - running + - succeeded + - failed + - unknown + type: string + x-enum-varnames: + - JobPending + - JobRunning + - JobSucceeded + - JobFailed + - JobUnknown service.JobStatus: properties: message: @@ -172,7 +237,7 @@ definitions: podIp: type: string state: - type: string + $ref: '#/definitions/service.JobState' type: object service.PodNameSelector: properties: @@ -195,7 +260,7 @@ definitions: detail: type: string status: - type: string + $ref: '#/definitions/types.TaskState' type: object service.buildImageInfo: properties: @@ -252,6 +317,20 @@ definitions: status: type: integer type: object + types.TaskState: + enum: + - waiting + - running + - succeeded + - failed + - unknown + type: string + x-enum-varnames: + - TaskWaiting + - TaskRunning + - TaskSucceeded + - TaskFailed + - TaskUnknown info: contact: {} title: kubernetes-manager api文档 @@ -432,6 +511,35 @@ paths: summary: 获取远程登录链接 tags: - builder + /docker/inspect: + post: + consumes: + - application/json + parameters: + - description: 凭证信息 + in: header + name: Devops-Token + required: true + type: string + - description: 构建机信息 + in: body + name: info + required: true + schema: + $ref: '#/definitions/service.DockerInspectInfo' + responses: + "200": + description: 任务ID + schema: + allOf: + - $ref: '#/definitions/types.Result' + - properties: + data: + $ref: '#/definitions/service.TaskId' + type: object + summary: docker inspect命令(同时会pull) + tags: + - docker /jobs: post: consumes: diff --git a/src/backend/dispatch-k8s-manager/swagger/init-swager.sh b/src/backend/dispatch-k8s-manager/swagger/init-swager.sh old mode 100644 new mode 100755 From 59e0a949941ec68232d6ae0fa8122fca275807aa Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 25 May 2023 09:44:31 +0800 Subject: [PATCH 0002/1143] =?UTF-8?q?kubernetes-manager=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81docker=20inspect=20image=20#8862?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../local_chart/kubernetes-management/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml b/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml index 63110efdd33..8a3ea65fbc3 100644 --- a/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml +++ b/helm-charts/core/ci/local_chart/kubernetes-management/templates/deployment.yaml @@ -73,7 +73,7 @@ spec: {{- end}} {{- if .Values.kubernetesManager.debug }} - name: KUBERNETES_MANAGER_DEBUG_ENABLE - value: true + value: "true" {{- end}} workingDir: /data/workspace/kubernetes-manager livenessProbe: From e382689b1170c21abfbe6f7342246fec996f5aa2 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Fri, 12 Apr 2024 18:24:38 +0800 Subject: [PATCH 0003/1143] =?UTF-8?q?bugfix:=20windwos=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BF=9B=E7=A8=8B=E6=97=B6=E5=81=B6=E7=8E=B0?= =?UTF-8?q?142=E9=97=AE=E9=A2=98=20#10179?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/src/pkg/util/command/command.go | 38 --------------- .../pkg/util/command/{user.go => process.go} | 38 +++++++++++++++ .../command/{user_win.go => process_win.go} | 47 +++++++++++++++++++ 3 files changed, 85 insertions(+), 38 deletions(-) rename src/agent/agent/src/pkg/util/command/{user.go => process.go} (79%) rename src/agent/agent/src/pkg/util/command/{user_win.go => process_win.go} (62%) diff --git a/src/agent/agent/src/pkg/util/command/command.go b/src/agent/agent/src/pkg/util/command/command.go index b395eec0479..7db03efc47d 100644 --- a/src/agent/agent/src/pkg/util/command/command.go +++ b/src/agent/agent/src/pkg/util/command/command.go @@ -28,7 +28,6 @@ package command import ( - "errors" "fmt" "os" "os/exec" @@ -66,40 +65,3 @@ func RunCommand(command string, args []string, workDir string, envMap map[string return outPut, nil } - -func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { - cmd := exec.Command(command) - - if len(args) > 0 { - cmd.Args = append(cmd.Args, args...) - } - - if workDir != "" { - cmd.Dir = workDir - } - - cmd.Env = os.Environ() - if envMap != nil { - for k, v := range envMap { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) - } - } - - err := setUser(cmd, runUser) - if err != nil { - logs.Error("set user failed: ", err.Error()) - return -1, errors.New( - fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) - } - - logs.Info("cmd.Path: ", cmd.Path) - logs.Info("cmd.Args: ", cmd.Args) - logs.Info("cmd.workDir: ", cmd.Dir) - logs.Info("runUser: ", runUser) - - err = cmd.Start() - if err != nil { - return -1, err - } - return cmd.Process.Pid, nil -} diff --git a/src/agent/agent/src/pkg/util/command/user.go b/src/agent/agent/src/pkg/util/command/process.go similarity index 79% rename from src/agent/agent/src/pkg/util/command/user.go rename to src/agent/agent/src/pkg/util/command/process.go index cb20e752865..93b5acef78b 100644 --- a/src/agent/agent/src/pkg/util/command/user.go +++ b/src/agent/agent/src/pkg/util/command/process.go @@ -33,6 +33,7 @@ package command import ( "errors" "fmt" + "os" "os/exec" "os/user" "strconv" @@ -44,6 +45,43 @@ import ( "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" ) +func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { + cmd := exec.Command(command) + + if len(args) > 0 { + cmd.Args = append(cmd.Args, args...) + } + + if workDir != "" { + cmd.Dir = workDir + } + + cmd.Env = os.Environ() + if envMap != nil { + for k, v := range envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } + + err := setUser(cmd, runUser) + if err != nil { + logs.Error("set user failed: ", err.Error()) + return -1, errors.New( + fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) + } + + logs.Info("cmd.Path: ", cmd.Path) + logs.Info("cmd.Args: ", cmd.Args) + logs.Info("cmd.workDir: ", cmd.Dir) + logs.Info("runUser: ", runUser) + + err = cmd.Start() + if err != nil { + return -1, err + } + return cmd.Process.Pid, nil +} + var envHome = "HOME" var envUser = "USER" var envLogName = "LOGNAME" diff --git a/src/agent/agent/src/pkg/util/command/user_win.go b/src/agent/agent/src/pkg/util/command/process_win.go similarity index 62% rename from src/agent/agent/src/pkg/util/command/user_win.go rename to src/agent/agent/src/pkg/util/command/process_win.go index d37f9aca68f..85d3aefbdec 100644 --- a/src/agent/agent/src/pkg/util/command/user_win.go +++ b/src/agent/agent/src/pkg/util/command/process_win.go @@ -31,11 +31,58 @@ package command import ( + "fmt" + "os" "os/exec" + "syscall" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" + "github.com/pkg/errors" ) +const createNewConsole = 0x00000010 + +func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { + cmd := exec.Command(command) + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: createNewConsole, + NoInheritHandles: true, + } + + if len(args) > 0 { + cmd.Args = append(cmd.Args, args...) + } + + if workDir != "" { + cmd.Dir = workDir + } + + cmd.Env = os.Environ() + if envMap != nil { + for k, v := range envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } + + err := setUser(cmd, runUser) + if err != nil { + logs.Error("set user failed: ", err.Error()) + return -1, errors.New( + fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) + } + + logs.Info("cmd.Path: ", cmd.Path) + logs.Info("cmd.Args: ", cmd.Args) + logs.Info("cmd.workDir: ", cmd.Dir) + logs.Info("runUser: ", runUser) + + err = cmd.Start() + if err != nil { + return -1, err + } + return cmd.Process.Pid, nil +} + func setUser(_ *exec.Cmd, runUser string) error { logs.Info("set user(windows): ", runUser) return nil From eca52a6c24bc726f0045ecee497888c1e1f2dff0 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Sat, 13 Apr 2024 09:42:33 +0800 Subject: [PATCH 0004/1143] =?UTF-8?q?bugfix:=20windwos=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BF=9B=E7=A8=8B=E6=97=B6=E5=81=B6=E7=8E=B0?= =?UTF-8?q?142=E9=97=AE=E9=A2=98=20#10179?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent/src/pkg/job/build.go | 156 ++++----------- src/agent/agent/src/pkg/job/build_manager.go | 50 +---- .../pkg/job/{job_test.go => build_test.go} | 0 src/agent/agent/src/pkg/job/do_build.go | 184 ++++++++++++++++++ src/agent/agent/src/pkg/job/do_build_win.go | 134 +++++++++++++ src/agent/agent/src/pkg/job/job.go | 43 ---- .../agent/src/pkg/util/command/process.go | 16 +- .../util/command/process_exit_group_win.go | 79 ++++++++ .../agent/src/pkg/util/command/process_win.go | 27 ++- 9 files changed, 465 insertions(+), 224 deletions(-) rename src/agent/agent/src/pkg/job/{job_test.go => build_test.go} (100%) create mode 100644 src/agent/agent/src/pkg/job/do_build.go create mode 100644 src/agent/agent/src/pkg/job/do_build_win.go delete mode 100644 src/agent/agent/src/pkg/job/job.go create mode 100644 src/agent/agent/src/pkg/util/command/process_exit_group_win.go diff --git a/src/agent/agent/src/pkg/job/build.go b/src/agent/agent/src/pkg/job/build.go index 31c33a32fb2..484ea12e65d 100644 --- a/src/agent/agent/src/pkg/job/build.go +++ b/src/agent/agent/src/pkg/job/build.go @@ -44,7 +44,6 @@ import ( "github.com/TencentBlueKing/bk-ci/agent/src/pkg/config" exitcode "github.com/TencentBlueKing/bk-ci/agent/src/pkg/exiterror" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/httputil" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" @@ -61,8 +60,6 @@ func init() { BuildTotalManager = new(BuildTotalManagerType) } -const buildIntervalInSeconds = 5 - // AgentStartup 上报构建机启动 func AgentStartup() (agentStatus string, err error) { result, err := api.AgentStartup() @@ -224,50 +221,11 @@ func runBuild(buildInfo *api.ThirdPartyBuildInfo) error { workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.MakeTmpDirErrorEnum)) return tmpMkErr } - if systemutil.IsWindows() { - startCmd := config.GetJava() - agentLogPrefix := fmt.Sprintf("%s_%s_agent", buildInfo.BuildId, buildInfo.VmSeqId) - errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) - args := []string{ - "-Djava.io.tmpdir=" + tmpDir, - "-Ddevops.agent.error.file=" + errorMsgFile, - "-Dbuild.type=AGENT", - "-DAGENT_LOG_PREFIX=" + agentLogPrefix, - "-Xmx2g", // #5806 兼容性问题,必须独立一行 - "-jar", - config.BuildAgentJarPath(), - getEncodedBuildInfo(buildInfo)} - pid, err := command.StartProcess(startCmd, args, workDir, goEnv, runUser) - if err != nil { - errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) - logs.Error(errMsg) - workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.BuildProcessStartErrorEnum)) - return err - } - // 添加需要构建结束后删除的文件 - buildInfo.ToDelTmpFiles = []string{errorMsgFile} - GBuildManager.AddBuild(pid, buildInfo) - logs.Info(fmt.Sprintf("[%s]|Job#_%s|Build started, pid:%d ", buildInfo.BuildId, buildInfo.VmSeqId, pid)) - return nil - } else { - startScriptFile, err := writeStartBuildAgentScript(buildInfo, tmpDir) - if err != nil { - errMsg := i18n.Localize("CreateStartScriptFailed", map[string]interface{}{"err": err.Error()}) - logs.Error(errMsg) - workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.PrepareScriptCreateErrorEnum)) - return err - } - pid, err := command.StartProcess(startScriptFile, []string{}, workDir, goEnv, runUser) - if err != nil { - errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) - logs.Error(errMsg) - workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.BuildProcessStartErrorEnum)) - return err - } - GBuildManager.AddBuild(pid, buildInfo) - logs.Info(fmt.Sprintf("[%s]|Job#_%s|Build started, pid:%d ", buildInfo.BuildId, buildInfo.VmSeqId, pid)) + if err := doBuild(buildInfo, tmpDir, workDir, goEnv, runUser); err != nil { + return err } + return nil } @@ -279,71 +237,6 @@ func getEncodedBuildInfo(buildInfo *api.ThirdPartyBuildInfo) string { return codedBuildInfo } -func writeStartBuildAgentScript(buildInfo *api.ThirdPartyBuildInfo, tmpDir string) (string, error) { - logs.Info("write start build agent script to file") - // 套娃,多加一层脚本,使用exec新起进程,这样才会读取 .bash_profile - prepareScriptFile := fmt.Sprintf( - "%s/devops_agent_prepare_start_%s_%s_%s.sh", - systemutil.GetWorkDir(), buildInfo.ProjectId, buildInfo.BuildId, buildInfo.VmSeqId) - scriptFile := fmt.Sprintf( - "%s/devops_agent_start_%s_%s_%s.sh", - systemutil.GetWorkDir(), buildInfo.ProjectId, buildInfo.BuildId, buildInfo.VmSeqId) - - errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) - buildInfo.ToDelTmpFiles = []string{ - scriptFile, prepareScriptFile, errorMsgFile, - } - - logs.Info("start agent script: ", scriptFile) - agentLogPrefix := fmt.Sprintf("%s_%s_agent", buildInfo.BuildId, buildInfo.VmSeqId) - lines := []string{ - "#!" + getCurrentShell(), - fmt.Sprintf("cd %s", systemutil.GetWorkDir()), - fmt.Sprintf("%s -Ddevops.slave.agent.start.file=%s -Ddevops.slave.agent.prepare.start.file=%s "+ - "-Ddevops.agent.error.file=%s "+ - "-Dbuild.type=AGENT -DAGENT_LOG_PREFIX=%s -Xmx2g -Djava.io.tmpdir=%s -jar %s %s", - config.GetJava(), scriptFile, prepareScriptFile, - errorMsgFile, - agentLogPrefix, tmpDir, config.BuildAgentJarPath(), getEncodedBuildInfo(buildInfo)), - } - scriptContent := strings.Join(lines, "\n") - - err := exitcode.WriteFileWithCheck(scriptFile, []byte(scriptContent), os.ModePerm) - defer func() { - _ = systemutil.Chmod(scriptFile, os.ModePerm) - _ = systemutil.Chmod(prepareScriptFile, os.ModePerm) - }() - if err != nil { - return "", err - } else { - prepareScriptContent := strings.Join(getShellLines(scriptFile), "\n") - err := exitcode.WriteFileWithCheck(prepareScriptFile, []byte(prepareScriptContent), os.ModePerm) - if err != nil { - return "", err - } else { - return prepareScriptFile, nil - } - } -} - -// getShellLines 根据不同的shell的参数要求,这里可能需要不同的参数或者参数顺序 -func getShellLines(scriptFile string) (newLines []string) { - shell := getCurrentShell() - switch shell { - case "/bin/tcsh": - newLines = []string{ - "#!" + shell, - "exec " + shell + " " + scriptFile + " -l", - } - default: - newLines = []string{ - "#!" + shell, - "exec " + shell + " -l " + scriptFile, - } - } - return newLines -} - func workerBuildFinish(buildInfo *api.ThirdPartyBuildWithStatus) { if buildInfo == nil { logs.Warn("buildInfo not exist") @@ -428,15 +321,38 @@ func removeFileThan7Days(dir string, f fs.DirEntry) { } } -func getCurrentShell() (shell string) { - if config.GAgentConfig.DetectShell { - shell = os.Getenv("SHELL") - if strings.TrimSpace(shell) == "" { - shell = "/bin/bash" - } - } else { - shell = "/bin/bash" +const ( + errorMsgFileSuffix = "build_msg.log" + prepareStartScriptFilePrefix = "devops_agent_prepare_start" + prepareStartScriptFileSuffix = ".sh" + startScriptFilePrefix = "devops_agent_start" + startScriptFileSuffix = ".sh" +) + +// getWorkerErrorMsgFile 获取worker执行错误信息的日志文件 +func getWorkerErrorMsgFile(buildId, vmSeqId string) string { + return fmt.Sprintf("%s/build_tmp/%s_%s_%s", + systemutil.GetWorkDir(), buildId, vmSeqId, errorMsgFileSuffix) +} + +// getUnixWorkerPrepareStartScriptFile 获取unix系统,主要是darwin和linux的prepare start script文件 +func getUnixWorkerPrepareStartScriptFile(projectId, buildId, vmSeqId string) string { + return fmt.Sprintf("%s/%s_%s_%s_%s%s", + systemutil.GetWorkDir(), prepareStartScriptFilePrefix, projectId, buildId, vmSeqId, prepareStartScriptFileSuffix) +} + +// getUnixWorkerStartScriptFile 获取unix系统,主要是darwin和linux的prepare start script文件 +func getUnixWorkerStartScriptFile(projectId, buildId, vmSeqId string) string { + return fmt.Sprintf("%s/%s_%s_%s_%s%s", + systemutil.GetWorkDir(), startScriptFilePrefix, projectId, buildId, vmSeqId, startScriptFileSuffix) +} + +// 校验当前是否有正在跑的任务 +func CheckRunningJob() bool { + if GBuildManager.GetPreInstancesCount() > 0 || + GBuildManager.GetInstanceCount() > 0 || + GBuildDockerManager.GetInstanceCount() > 0 { + return true } - logs.Info("current shell: ", shell) - return + return false } diff --git a/src/agent/agent/src/pkg/job/build_manager.go b/src/agent/agent/src/pkg/job/build_manager.go index 8b483e7aca3..3d1e78b6314 100644 --- a/src/agent/agent/src/pkg/job/build_manager.go +++ b/src/agent/agent/src/pkg/job/build_manager.go @@ -30,16 +30,12 @@ package job import ( "encoding/json" "fmt" - "os" "strings" "sync" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/api" - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" - "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" ) // buildManager 二进制构建对象管理 @@ -90,54 +86,10 @@ func (b *buildManager) AddBuild(processId int, buildInfo *api.ThirdPartyBuildInf b.instances.Store(processId, buildInfo) // 启动构建了就删除preInstance b.DeletePreInstance(buildInfo.BuildId) - - // #5806 预先录入异常信息,在构建进程正常结束时清理掉。如果没清理掉,则说明进程非正常退出,可能被OS或人为杀死 - errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) - _ = fileutil.WriteString(errorMsgFile, i18n.Localize("BuilderProcessWasKilled", nil)) - _ = systemutil.Chmod(errorMsgFile, os.ModePerm) - b.waitProcessDone(processId) } -func (b *buildManager) waitProcessDone(processId int) { - process, err := os.FindProcess(processId) - inf, ok := b.instances.Load(processId) - var info *api.ThirdPartyBuildInfo - if ok { - info = inf.(*api.ThirdPartyBuildInfo) - } - if err != nil { - errMsg := i18n.Localize("BuildProcessErr", map[string]interface{}{"pid": processId, "err": err.Error()}) - logs.Warn(errMsg) - b.instances.Delete(processId) - workerBuildFinish(info.ToFinish(false, errMsg, api.BuildProcessRunErrorEnum)) - return - } - - state, err := process.Wait() - // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 - msgFile := getWorkerErrorMsgFile(info.BuildId, info.VmSeqId) - msg, _ := fileutil.GetString(msgFile) - logs.Info(fmt.Sprintf("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", info.BuildId, processId, state, err, msg)) - - if err != nil { - if len(msg) == 0 { - msg = err.Error() - } - } - success := true - if len(msg) == 0 { - msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": processId}) - } else { - success = false - } - - buildInfo := info +func (b *buildManager) DeleteBuild(processId int) { b.instances.Delete(processId) - if success { - workerBuildFinish(buildInfo.ToFinish(success, msg, api.NoErrorEnum)) - } else { - workerBuildFinish(buildInfo.ToFinish(success, msg, api.BuildProcessRunErrorEnum)) - } } func (b *buildManager) GetPreInstancesCount() int { diff --git a/src/agent/agent/src/pkg/job/job_test.go b/src/agent/agent/src/pkg/job/build_test.go similarity index 100% rename from src/agent/agent/src/pkg/job/job_test.go rename to src/agent/agent/src/pkg/job/build_test.go diff --git a/src/agent/agent/src/pkg/job/do_build.go b/src/agent/agent/src/pkg/job/do_build.go new file mode 100644 index 00000000000..0a00b70d69a --- /dev/null +++ b/src/agent/agent/src/pkg/job/do_build.go @@ -0,0 +1,184 @@ +//go:build linux || darwin +// +build linux darwin + +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package job + +import ( + "fmt" + "os" + "strings" + + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/api" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/config" + exitcode "github.com/TencentBlueKing/bk-ci/agent/src/pkg/exiterror" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" + "github.com/TencentBlueKing/bk-ci/agentcommon/logs" + "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" +) + +func doBuild( + buildInfo *api.ThirdPartyBuildInfo, + tmpDir string, + workDir string, + goEnv map[string]string, + runUser string, +) error { + startScriptFile, err := writeStartBuildAgentScript(buildInfo, tmpDir) + if err != nil { + errMsg := i18n.Localize("CreateStartScriptFailed", map[string]interface{}{"err": err.Error()}) + logs.Error(errMsg) + workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.PrepareScriptCreateErrorEnum)) + return err + } + + cmd, err := command.StartProcessCmd(startScriptFile, []string{}, workDir, goEnv, runUser) + if err != nil { + errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) + logs.Error(errMsg) + workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.BuildProcessStartErrorEnum)) + return err + } + + pid := cmd.Process.Pid + GBuildManager.AddBuild(pid, buildInfo) + logs.Info(fmt.Sprintf("[%s]|Job#_%s|Build started, pid:%d ", buildInfo.BuildId, buildInfo.VmSeqId, pid)) + + // #5806 预先录入异常信息,在构建进程正常结束时清理掉。如果没清理掉,则说明进程非正常退出,可能被OS或人为杀死 + errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) + _ = fileutil.WriteString(errorMsgFile, i18n.Localize("BuilderProcessWasKilled", nil)) + _ = systemutil.Chmod(errorMsgFile, os.ModePerm) + + err = cmd.Wait() + // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 + msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) + msg, _ := fileutil.GetString(msgFile) + logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + + if err != nil { + if len(msg) == 0 { + msg = err.Error() + } + } + success := true + if len(msg) == 0 { + msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + } else { + success = false + } + + GBuildManager.DeleteBuild(pid) + if success { + workerBuildFinish(buildInfo.ToFinish(success, msg, api.NoErrorEnum)) + } else { + workerBuildFinish(buildInfo.ToFinish(success, msg, api.BuildProcessRunErrorEnum)) + } + + return nil +} + +func writeStartBuildAgentScript(buildInfo *api.ThirdPartyBuildInfo, tmpDir string) (string, error) { + logs.Info("write start build agent script to file") + // 套娃,多加一层脚本,使用exec新起进程,这样才会读取 .bash_profile + prepareScriptFile := fmt.Sprintf( + "%s/devops_agent_prepare_start_%s_%s_%s.sh", + systemutil.GetWorkDir(), buildInfo.ProjectId, buildInfo.BuildId, buildInfo.VmSeqId) + scriptFile := fmt.Sprintf( + "%s/devops_agent_start_%s_%s_%s.sh", + systemutil.GetWorkDir(), buildInfo.ProjectId, buildInfo.BuildId, buildInfo.VmSeqId) + + errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) + buildInfo.ToDelTmpFiles = []string{ + scriptFile, prepareScriptFile, errorMsgFile, + } + + logs.Info("start agent script: ", scriptFile) + agentLogPrefix := fmt.Sprintf("%s_%s_agent", buildInfo.BuildId, buildInfo.VmSeqId) + lines := []string{ + "#!" + getCurrentShell(), + fmt.Sprintf("cd %s", systemutil.GetWorkDir()), + fmt.Sprintf("%s -Ddevops.slave.agent.start.file=%s -Ddevops.slave.agent.prepare.start.file=%s "+ + "-Ddevops.agent.error.file=%s "+ + "-Dbuild.type=AGENT -DAGENT_LOG_PREFIX=%s -Xmx2g -Djava.io.tmpdir=%s -jar %s %s", + config.GetJava(), scriptFile, prepareScriptFile, + errorMsgFile, + agentLogPrefix, tmpDir, config.BuildAgentJarPath(), getEncodedBuildInfo(buildInfo)), + } + scriptContent := strings.Join(lines, "\n") + + err := exitcode.WriteFileWithCheck(scriptFile, []byte(scriptContent), os.ModePerm) + defer func() { + _ = systemutil.Chmod(scriptFile, os.ModePerm) + _ = systemutil.Chmod(prepareScriptFile, os.ModePerm) + }() + if err != nil { + return "", err + } else { + prepareScriptContent := strings.Join(getShellLines(scriptFile), "\n") + err := exitcode.WriteFileWithCheck(prepareScriptFile, []byte(prepareScriptContent), os.ModePerm) + if err != nil { + return "", err + } else { + return prepareScriptFile, nil + } + } +} + +// getShellLines 根据不同的shell的参数要求,这里可能需要不同的参数或者参数顺序 +func getShellLines(scriptFile string) (newLines []string) { + shell := getCurrentShell() + switch shell { + case "/bin/tcsh": + newLines = []string{ + "#!" + shell, + "exec " + shell + " " + scriptFile + " -l", + } + default: + newLines = []string{ + "#!" + shell, + "exec " + shell + " -l " + scriptFile, + } + } + return newLines +} + +func getCurrentShell() (shell string) { + if config.GAgentConfig.DetectShell { + shell = os.Getenv("SHELL") + if strings.TrimSpace(shell) == "" { + shell = "/bin/bash" + } + } else { + shell = "/bin/bash" + } + logs.Info("current shell: ", shell) + return +} diff --git a/src/agent/agent/src/pkg/job/do_build_win.go b/src/agent/agent/src/pkg/job/do_build_win.go new file mode 100644 index 00000000000..7c74dec5cf2 --- /dev/null +++ b/src/agent/agent/src/pkg/job/do_build_win.go @@ -0,0 +1,134 @@ +//go:build windows +// +build windows + +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package job + +import ( + "fmt" + "os" + + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/api" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/config" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" + "github.com/TencentBlueKing/bk-ci/agentcommon/logs" + "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" +) + +func doBuild( + buildInfo *api.ThirdPartyBuildInfo, + tmpDir string, + workDir string, + goEnv map[string]string, + runUser string, +) error { + // TODO: #10179 根据环境变量判断是否使用进程组 + var exitGroup *command.ProcessExitGroup = nil + if true { + exitGroup, err := command.NewProcessExitGroup() + if err != nil { + errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) + logs.Error(errMsg) + workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.BuildProcessStartErrorEnum)) + return err + } + + defer func() { + logs.Infof("%s exit group dispose", buildInfo.BuildId) + exitGroup.Dispose() + }() + } + + startCmd := config.GetJava() + agentLogPrefix := fmt.Sprintf("%s_%s_agent", buildInfo.BuildId, buildInfo.VmSeqId) + errorMsgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) + args := []string{ + "-Djava.io.tmpdir=" + tmpDir, + "-Ddevops.agent.error.file=" + errorMsgFile, + "-Dbuild.type=AGENT", + "-DAGENT_LOG_PREFIX=" + agentLogPrefix, + "-Xmx2g", // #5806 兼容性问题,必须独立一行 + "-jar", + config.BuildAgentJarPath(), + getEncodedBuildInfo(buildInfo)} + cmd, err := command.StartProcessCmd(startCmd, args, workDir, goEnv, runUser) + if err != nil { + errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) + logs.Error(errMsg) + workerBuildFinish(buildInfo.ToFinish(false, errMsg, api.BuildProcessStartErrorEnum)) + return err + } + pid := cmd.Process.Pid + + if exitGroup != nil { + logs.Infof("%s process %d add exit group ", buildInfo.BuildId, pid) + if err := exitGroup.AddProcess(cmd.Process); err != nil { + logs.Errorf("%s add process to %d exit group error %s", buildInfo.BuildId, pid, err.Error()) + } + } + + // 添加需要构建结束后删除的文件 + buildInfo.ToDelTmpFiles = []string{errorMsgFile} + + GBuildManager.AddBuild(pid, buildInfo) + logs.Info(fmt.Sprintf("[%s]|Job#_%s|Build started, pid:%d ", buildInfo.BuildId, buildInfo.VmSeqId, pid)) + + // #5806 预先录入异常信息,在构建进程正常结束时清理掉。如果没清理掉,则说明进程非正常退出,可能被OS或人为杀死 + _ = fileutil.WriteString(errorMsgFile, i18n.Localize("BuilderProcessWasKilled", nil)) + _ = systemutil.Chmod(errorMsgFile, os.ModePerm) + + err = cmd.Wait() + // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 + msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) + msg, _ := fileutil.GetString(msgFile) + logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + + if err != nil { + if len(msg) == 0 { + msg = err.Error() + } + } + success := true + if len(msg) == 0 { + msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + } else { + success = false + } + + GBuildManager.DeleteBuild(pid) + if success { + workerBuildFinish(buildInfo.ToFinish(success, msg, api.NoErrorEnum)) + } else { + workerBuildFinish(buildInfo.ToFinish(success, msg, api.BuildProcessRunErrorEnum)) + } + + return nil +} diff --git a/src/agent/agent/src/pkg/job/job.go b/src/agent/agent/src/pkg/job/job.go deleted file mode 100644 index 00ba6971cb4..00000000000 --- a/src/agent/agent/src/pkg/job/job.go +++ /dev/null @@ -1,43 +0,0 @@ -package job - -import ( - "fmt" - - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" -) - -const ( - errorMsgFileSuffix = "build_msg.log" - prepareStartScriptFilePrefix = "devops_agent_prepare_start" - prepareStartScriptFileSuffix = ".sh" - startScriptFilePrefix = "devops_agent_start" - startScriptFileSuffix = ".sh" -) - -// getWorkerErrorMsgFile 获取worker执行错误信息的日志文件 -func getWorkerErrorMsgFile(buildId, vmSeqId string) string { - return fmt.Sprintf("%s/build_tmp/%s_%s_%s", - systemutil.GetWorkDir(), buildId, vmSeqId, errorMsgFileSuffix) -} - -// getUnixWorkerPrepareStartScriptFile 获取unix系统,主要是darwin和linux的prepare start script文件 -func getUnixWorkerPrepareStartScriptFile(projectId, buildId, vmSeqId string) string { - return fmt.Sprintf("%s/%s_%s_%s_%s%s", - systemutil.GetWorkDir(), prepareStartScriptFilePrefix, projectId, buildId, vmSeqId, prepareStartScriptFileSuffix) -} - -// getUnixWorkerStartScriptFile 获取unix系统,主要是darwin和linux的prepare start script文件 -func getUnixWorkerStartScriptFile(projectId, buildId, vmSeqId string) string { - return fmt.Sprintf("%s/%s_%s_%s_%s%s", - systemutil.GetWorkDir(), startScriptFilePrefix, projectId, buildId, vmSeqId, startScriptFileSuffix) -} - -// 校验当前是否有正在跑的任务 -func CheckRunningJob() bool { - if GBuildManager.GetPreInstancesCount() > 0 || - GBuildManager.GetInstanceCount() > 0 || - GBuildDockerManager.GetInstanceCount() > 0 { - return true - } - return false -} diff --git a/src/agent/agent/src/pkg/util/command/process.go b/src/agent/agent/src/pkg/util/command/process.go index 93b5acef78b..1663345d738 100644 --- a/src/agent/agent/src/pkg/util/command/process.go +++ b/src/agent/agent/src/pkg/util/command/process.go @@ -46,6 +46,14 @@ import ( ) func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { + cmd, err := StartProcessCmd(command, args, workDir, envMap, runUser) + if err != nil { + return -1, err + } + return cmd.Process.Pid, nil +} + +func StartProcessCmd(command string, args []string, workDir string, envMap map[string]string, runUser string) (*exec.Cmd, error) { cmd := exec.Command(command) if len(args) > 0 { @@ -66,8 +74,7 @@ func StartProcess(command string, args []string, workDir string, envMap map[stri err := setUser(cmd, runUser) if err != nil { logs.Error("set user failed: ", err.Error()) - return -1, errors.New( - fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) + return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) } logs.Info("cmd.Path: ", cmd.Path) @@ -77,9 +84,10 @@ func StartProcess(command string, args []string, workDir string, envMap map[stri err = cmd.Start() if err != nil { - return -1, err + return nil, err } - return cmd.Process.Pid, nil + + return cmd, nil } var envHome = "HOME" diff --git a/src/agent/agent/src/pkg/util/command/process_exit_group_win.go b/src/agent/agent/src/pkg/util/command/process_exit_group_win.go new file mode 100644 index 00000000000..f7dc82ec196 --- /dev/null +++ b/src/agent/agent/src/pkg/util/command/process_exit_group_win.go @@ -0,0 +1,79 @@ +//go:build windows +// +build windows + +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package command + +import ( + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +// We use this struct to retreive process handle(which is unexported) +// from os.Process using unsafe operation. +type process struct { + Pid int + Handle uintptr +} + +type ProcessExitGroup windows.Handle + +func NewProcessExitGroup() (ProcessExitGroup, error) { + handle, err := windows.CreateJobObject(nil, nil) + if err != nil { + return 0, err + } + + info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{ + BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{ + LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, + }, + } + if _, err := windows.SetInformationJobObject( + handle, + windows.JobObjectExtendedLimitInformation, + uintptr(unsafe.Pointer(&info)), + uint32(unsafe.Sizeof(info))); err != nil { + return 0, err + } + + return ProcessExitGroup(handle), nil +} + +func (g ProcessExitGroup) Dispose() error { + return windows.CloseHandle(windows.Handle(g)) +} + +func (g ProcessExitGroup) AddProcess(p *os.Process) error { + return windows.AssignProcessToJobObject( + windows.Handle(g), + windows.Handle((*process)(unsafe.Pointer(p)).Handle)) +} diff --git a/src/agent/agent/src/pkg/util/command/process_win.go b/src/agent/agent/src/pkg/util/command/process_win.go index 85d3aefbdec..e4c9e7588c3 100644 --- a/src/agent/agent/src/pkg/util/command/process_win.go +++ b/src/agent/agent/src/pkg/util/command/process_win.go @@ -37,16 +37,27 @@ import ( "syscall" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" - "github.com/pkg/errors" ) const createNewConsole = 0x00000010 func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { + cmd, err := StartProcessCmd(command, args, workDir, envMap, runUser) + if err != nil { + return -1, err + } + return cmd.Process.Pid, nil +} + +func StartProcessCmd(command string, args []string, workDir string, envMap map[string]string, runUser string) (*exec.Cmd, error) { cmd := exec.Command(command) - cmd.SysProcAttr = &syscall.SysProcAttr{ - CreationFlags: createNewConsole, - NoInheritHandles: true, + + // TODO: #10179 读取环境变量判断是否使用 newConsole + if true { + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: createNewConsole, + NoInheritHandles: true, + } } if len(args) > 0 { @@ -67,8 +78,7 @@ func StartProcess(command string, args []string, workDir string, envMap map[stri err := setUser(cmd, runUser) if err != nil { logs.Error("set user failed: ", err.Error()) - return -1, errors.New( - fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) + return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) } logs.Info("cmd.Path: ", cmd.Path) @@ -78,9 +88,10 @@ func StartProcess(command string, args []string, workDir string, envMap map[stri err = cmd.Start() if err != nil { - return -1, err + return nil, err } - return cmd.Process.Pid, nil + + return cmd, nil } func setUser(_ *exec.Cmd, runUser string) error { From 2925784f2b296dd4bb125304241c8fbdd22ccfc2 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 18 Apr 2024 16:27:54 +0800 Subject: [PATCH 0005/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent/src/pkg/agent/heartbeat.go | 14 +++- src/agent/agent/src/pkg/config/config.go | 7 +- src/agent/agent/src/pkg/config/env.go | 69 ++++++++++++++++++ src/agent/agent/src/pkg/constant/constant.go | 6 +- .../agent/src/pkg/constant/constant_out.go | 5 ++ src/agent/agent/src/pkg/job/build.go | 7 +- src/agent/agent/src/pkg/job/do_build.go | 73 ++++++++++++++++++- src/agent/agent/src/pkg/job/do_build_win.go | 65 +++++++++++++++-- .../agent/src/pkg/util/command/command.go | 38 ++++++++++ .../pkg/util/command/{process.go => user.go} | 48 +----------- .../command/{process_win.go => user_win.go} | 60 +-------------- .../process_exit_group_win.go | 3 +- 12 files changed, 272 insertions(+), 123 deletions(-) create mode 100644 src/agent/agent/src/pkg/config/env.go rename src/agent/agent/src/pkg/util/command/{process.go => user.go} (73%) rename src/agent/agent/src/pkg/util/command/{process_win.go => user_win.go} (54%) rename src/agent/agent/src/pkg/util/{command => process}/process_exit_group_win.go (96%) diff --git a/src/agent/agent/src/pkg/agent/heartbeat.go b/src/agent/agent/src/pkg/agent/heartbeat.go index df76588e8fa..0dd92bd62bd 100644 --- a/src/agent/agent/src/pkg/agent/heartbeat.go +++ b/src/agent/agent/src/pkg/agent/heartbeat.go @@ -85,7 +85,19 @@ func agentHeartbeat(heartbeatResponse *api.AgentHeartbeatResponse) { } // agent环境变量 - config.GEnvVars = heartbeatResponse.Envs + flag := false + if heartbeatResponse.Envs != nil { + config.GApiEnvVars.RangeDo(func(k, v string) bool { + if heartbeatResponse.Envs[k] != v { + flag = true + return false + } + return true + }) + } + if flag { + config.GApiEnvVars.SetEnvs(heartbeatResponse.Envs) + } /* 忽略一些在Windows机器上VPN代理软件所产生的虚拟网卡(有Mac地址)的IP,一般这类IP diff --git a/src/agent/agent/src/pkg/config/config.go b/src/agent/agent/src/pkg/config/config.go index b054d43d36d..eddf3f00eef 100644 --- a/src/agent/agent/src/pkg/config/config.go +++ b/src/agent/agent/src/pkg/config/config.go @@ -114,9 +114,7 @@ type AgentEnv struct { var GAgentEnv *AgentEnv var GAgentConfig *AgentConfig -var GEnvVars map[string]string var UseCert bool - var IsDebug bool = false // Init 加载和初始化配置 @@ -129,6 +127,11 @@ func Init(isDebug bool) { } initCert() LoadAgentEnv() + + GApiEnvVars = &GEnvVarsT{ + envs: make(map[string]string), + lock: sync.RWMutex{}, + } } // LoadAgentEnv 加载Agent环境 diff --git a/src/agent/agent/src/pkg/config/env.go b/src/agent/agent/src/pkg/config/env.go new file mode 100644 index 00000000000..211303663b2 --- /dev/null +++ b/src/agent/agent/src/pkg/config/env.go @@ -0,0 +1,69 @@ +package config + +import ( + "os" + "strings" + "sync" +) + +// GApiEnvVars 来自页面配置的环境变量 +var GApiEnvVars *GEnvVarsT + +type GEnvVarsT struct { + envs map[string]string + lock sync.RWMutex +} + +func (e *GEnvVarsT) Get(key string) (string, bool) { + e.lock.RLock() + defer e.lock.RUnlock() + res, ok := e.envs[key] + return res, ok +} + +func (e *GEnvVarsT) SetEnvs(envs map[string]string) { + e.lock.Lock() + defer e.lock.Unlock() + e.envs = envs +} + +func (e *GEnvVarsT) RangeDo(do func(k, v string) bool) { + e.lock.RLock() + defer e.lock.RUnlock() + for k, v := range e.envs { + ok := do(k, v) + if !ok { + return + } + } +} + +// FetchEnvAndCheck 查询是否有某个环境变量,同时校验是否符合要求 +func FetchEnvAndCheck(key string, checkValue string) bool { + v, ok := FetchEnv(key) + if !ok { + return checkValue == "" + } + return v == checkValue +} + +// FetchEnv 查询是否有某个环境变量,需要同时查询系统和后台变量 +func FetchEnv(key string) (string, bool) { + // 优先使用后台配置的 + v, ok := GApiEnvVars.Get(key) + if ok { + return v, true + } + + for _, envStr := range os.Environ() { + parts := strings.Split(envStr, "=") + if len(parts) < 2 { + continue + } + if parts[0] == key { + return parts[1], true + } + } + + return "", false +} diff --git a/src/agent/agent/src/pkg/constant/constant.go b/src/agent/agent/src/pkg/constant/constant.go index 4e24909a71d..31da7ce1f00 100644 --- a/src/agent/agent/src/pkg/constant/constant.go +++ b/src/agent/agent/src/pkg/constant/constant.go @@ -31,8 +31,12 @@ package constant // 用来放一些常量,可能内外部不一致 - const ( DockerDataDir = "/data/landun/workspace" DAEMON_EXIT_CODE = 88 + + // DEVOPS_AGENT_ENABLE_NEW_CONSOLE 如果设为true 则windows启动进程时使用 newConsole + DEVOPS_AGENT_ENABLE_NEW_CONSOLE = "DEVOPS_AGENT_ENABLE_NEW_CONSOLE" + // DEVOPS_AGENT_ENABLE_EXIT_GROUP 启动Agent杀掉构建进程组的兜底逻辑 + DEVOPS_AGENT_ENABLE_EXIT_GROUP = "DEVOPS_AGENT_ENABLE_EXIT_GROUP" ) diff --git a/src/agent/agent/src/pkg/constant/constant_out.go b/src/agent/agent/src/pkg/constant/constant_out.go index 412d636e906..e948c342ae4 100644 --- a/src/agent/agent/src/pkg/constant/constant_out.go +++ b/src/agent/agent/src/pkg/constant/constant_out.go @@ -35,4 +35,9 @@ package constant const ( DockerDataDir = "/data/devops/workspace" DAEMON_EXIT_CODE = 88 + + // DEVOPS_AGENT_ENABLE_NEW_CONSOLE 如果设为true 则windows启动进程时使用 newConsole + DEVOPS_AGENT_ENABLE_NEW_CONSOLE = "DEVOPS_AGENT_ENABLE_NEW_CONSOLE" + // DEVOPS_AGENT_ENABLE_EXIT_GROUP 启动Agent杀掉构建进程组的兜底逻辑 + DEVOPS_AGENT_ENABLE_EXIT_GROUP = "DEVOPS_AGENT_ENABLE_EXIT_GROUP" ) diff --git a/src/agent/agent/src/pkg/job/build.go b/src/agent/agent/src/pkg/job/build.go index 484ea12e65d..ddc9ac3a1b6 100644 --- a/src/agent/agent/src/pkg/job/build.go +++ b/src/agent/agent/src/pkg/job/build.go @@ -208,10 +208,11 @@ func runBuild(buildInfo *api.ThirdPartyBuildInfo) error { "DEVOPS_GATEWAY": config.GetGateWay(), "BK_CI_LOCALE_LANGUAGE": config.GAgentConfig.Language, } - if config.GEnvVars != nil { - for k, v := range config.GEnvVars { + if config.GApiEnvVars != nil { + config.GApiEnvVars.RangeDo(func(k, v string) bool { goEnv[k] = v - } + return true + }) } // #5806 定义临时目录 tmpDir, tmpMkErr := systemutil.MkBuildTmpDir() diff --git a/src/agent/agent/src/pkg/job/do_build.go b/src/agent/agent/src/pkg/job/do_build.go index 0a00b70d69a..8770af7c0e8 100644 --- a/src/agent/agent/src/pkg/job/do_build.go +++ b/src/agent/agent/src/pkg/job/do_build.go @@ -33,13 +33,17 @@ package job import ( "fmt" "os" + "os/exec" + "runtime" "strings" + "syscall" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/api" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/config" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/constant" exitcode "github.com/TencentBlueKing/bk-ci/agent/src/pkg/exiterror" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" + ucommand "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" @@ -60,7 +64,11 @@ func doBuild( return err } - cmd, err := command.StartProcessCmd(startScriptFile, []string{}, workDir, goEnv, runUser) + enableExitGroup := config.FetchEnvAndCheck(constant.DEVOPS_AGENT_ENABLE_EXIT_GROUP, "true") + if enableExitGroup { + logs.Infof("%s enable exit group", buildInfo.BuildId) + } + cmd, err := StartProcessCmd(startScriptFile, []string{}, workDir, goEnv, runUser, enableExitGroup) if err != nil { errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) logs.Error(errMsg) @@ -78,6 +86,18 @@ func doBuild( _ = systemutil.Chmod(errorMsgFile, os.ModePerm) err = cmd.Wait() + if enableExitGroup { + go func() { + pid := cmd.Process.Pid + logs.Infof("%s do kill %d process group", buildInfo.BuildId, pid) + // 杀死进程组 + err = syscall.Kill(-pid, syscall.SIGTERM) + if err != nil { + logs.Errorf("%s failed to kill %d process group: %s", buildInfo.BuildId, pid, err.Error()) + return + } + }() + } // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) msg, _ := fileutil.GetString(msgFile) @@ -182,3 +202,52 @@ func getCurrentShell() (shell string) { logs.Info("current shell: ", shell) return } + +func StartProcessCmd( + command string, + args []string, + workDir string, + envMap map[string]string, + runUser string, + enableExitGroup bool, +) (*exec.Cmd, error) { + cmd := exec.Command(command) + + // arm64机器目前无法通过worker杀进程 + if enableExitGroup || (systemutil.IsMacos() && runtime.GOARCH == "arm") { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } + + if len(args) > 0 { + cmd.Args = append(cmd.Args, args...) + } + + if workDir != "" { + cmd.Dir = workDir + } + + cmd.Env = os.Environ() + if envMap != nil { + for k, v := range envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } + + err := ucommand.SetUser(cmd, runUser) + if err != nil { + logs.Error("set user failed: ", err.Error()) + return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) + } + + logs.Info("cmd.Path: ", cmd.Path) + logs.Info("cmd.Args: ", cmd.Args) + logs.Info("cmd.workDir: ", cmd.Dir) + logs.Info("runUser: ", runUser) + + err = cmd.Start() + if err != nil { + return nil, err + } + + return cmd, nil +} diff --git a/src/agent/agent/src/pkg/job/do_build_win.go b/src/agent/agent/src/pkg/job/do_build_win.go index 7c74dec5cf2..e95e79f7ea8 100644 --- a/src/agent/agent/src/pkg/job/do_build_win.go +++ b/src/agent/agent/src/pkg/job/do_build_win.go @@ -33,11 +33,15 @@ package job import ( "fmt" "os" + "os/exec" + "syscall" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/api" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/config" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/constant" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/i18n" - "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" + ucommand "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/command" + "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/process" "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" "github.com/TencentBlueKing/bk-ci/agentcommon/utils/fileutil" @@ -50,10 +54,11 @@ func doBuild( goEnv map[string]string, runUser string, ) error { - // TODO: #10179 根据环境变量判断是否使用进程组 - var exitGroup *command.ProcessExitGroup = nil - if true { - exitGroup, err := command.NewProcessExitGroup() + var err error + var exitGroup process.ProcessExitGroup + enableExitGroup := config.FetchEnvAndCheck(constant.DEVOPS_AGENT_ENABLE_EXIT_GROUP, "true") + if enableExitGroup { + exitGroup, err = process.NewProcessExitGroup() if err != nil { errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) logs.Error(errMsg) @@ -79,7 +84,7 @@ func doBuild( "-jar", config.BuildAgentJarPath(), getEncodedBuildInfo(buildInfo)} - cmd, err := command.StartProcessCmd(startCmd, args, workDir, goEnv, runUser) + cmd, err := StartProcessCmd(startCmd, args, workDir, goEnv, runUser) if err != nil { errMsg := i18n.Localize("StartWorkerProcessFailed", map[string]interface{}{"err": err.Error()}) logs.Error(errMsg) @@ -88,7 +93,7 @@ func doBuild( } pid := cmd.Process.Pid - if exitGroup != nil { + if enableExitGroup { logs.Infof("%s process %d add exit group ", buildInfo.BuildId, pid) if err := exitGroup.AddProcess(cmd.Process); err != nil { logs.Errorf("%s add process to %d exit group error %s", buildInfo.BuildId, pid, err.Error()) @@ -132,3 +137,49 @@ func doBuild( return nil } + +const createNewConsole = 0x00000010 + +func StartProcessCmd(command string, args []string, workDir string, envMap map[string]string, runUser string) (*exec.Cmd, error) { + cmd := exec.Command(command) + + if config.FetchEnvAndCheck(constant.DEVOPS_AGENT_ENABLE_NEW_CONSOLE, "true") { + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: createNewConsole, + NoInheritHandles: true, + } + } + + if len(args) > 0 { + cmd.Args = append(cmd.Args, args...) + } + + if workDir != "" { + cmd.Dir = workDir + } + + cmd.Env = os.Environ() + if envMap != nil { + for k, v := range envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } + + err := ucommand.SetUser(cmd, runUser) + if err != nil { + logs.Error("set user failed: ", err.Error()) + return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) + } + + logs.Info("cmd.Path: ", cmd.Path) + logs.Info("cmd.Args: ", cmd.Args) + logs.Info("cmd.workDir: ", cmd.Dir) + logs.Info("runUser: ", runUser) + + err = cmd.Start() + if err != nil { + return nil, err + } + + return cmd, nil +} diff --git a/src/agent/agent/src/pkg/util/command/command.go b/src/agent/agent/src/pkg/util/command/command.go index 7db03efc47d..4cb54b8e411 100644 --- a/src/agent/agent/src/pkg/util/command/command.go +++ b/src/agent/agent/src/pkg/util/command/command.go @@ -28,6 +28,7 @@ package command import ( + "errors" "fmt" "os" "os/exec" @@ -65,3 +66,40 @@ func RunCommand(command string, args []string, workDir string, envMap map[string return outPut, nil } + +func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { + cmd := exec.Command(command) + + if len(args) > 0 { + cmd.Args = append(cmd.Args, args...) + } + + if workDir != "" { + cmd.Dir = workDir + } + + cmd.Env = os.Environ() + if envMap != nil { + for k, v := range envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + } + + err := SetUser(cmd, runUser) + if err != nil { + logs.Error("set user failed: ", err.Error()) + return -1, errors.New( + fmt.Sprintf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error())) + } + + logs.Info("cmd.Path: ", cmd.Path) + logs.Info("cmd.Args: ", cmd.Args) + logs.Info("cmd.workDir: ", cmd.Dir) + logs.Info("runUser: ", runUser) + + err = cmd.Start() + if err != nil { + return -1, err + } + return cmd.Process.Pid, nil +} diff --git a/src/agent/agent/src/pkg/util/command/process.go b/src/agent/agent/src/pkg/util/command/user.go similarity index 73% rename from src/agent/agent/src/pkg/util/command/process.go rename to src/agent/agent/src/pkg/util/command/user.go index 1663345d738..2d7bf4063a2 100644 --- a/src/agent/agent/src/pkg/util/command/process.go +++ b/src/agent/agent/src/pkg/util/command/user.go @@ -33,7 +33,6 @@ package command import ( "errors" "fmt" - "os" "os/exec" "os/user" "strconv" @@ -45,56 +44,11 @@ import ( "github.com/TencentBlueKing/bk-ci/agent/src/pkg/util/systemutil" ) -func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { - cmd, err := StartProcessCmd(command, args, workDir, envMap, runUser) - if err != nil { - return -1, err - } - return cmd.Process.Pid, nil -} - -func StartProcessCmd(command string, args []string, workDir string, envMap map[string]string, runUser string) (*exec.Cmd, error) { - cmd := exec.Command(command) - - if len(args) > 0 { - cmd.Args = append(cmd.Args, args...) - } - - if workDir != "" { - cmd.Dir = workDir - } - - cmd.Env = os.Environ() - if envMap != nil { - for k, v := range envMap { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) - } - } - - err := setUser(cmd, runUser) - if err != nil { - logs.Error("set user failed: ", err.Error()) - return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) - } - - logs.Info("cmd.Path: ", cmd.Path) - logs.Info("cmd.Args: ", cmd.Args) - logs.Info("cmd.workDir: ", cmd.Dir) - logs.Info("runUser: ", runUser) - - err = cmd.Start() - if err != nil { - return nil, err - } - - return cmd, nil -} - var envHome = "HOME" var envUser = "USER" var envLogName = "LOGNAME" -func setUser(cmd *exec.Cmd, runUser string) error { +func SetUser(cmd *exec.Cmd, runUser string) error { if len(runUser) == 0 { // 传空则直接返回 return nil diff --git a/src/agent/agent/src/pkg/util/command/process_win.go b/src/agent/agent/src/pkg/util/command/user_win.go similarity index 54% rename from src/agent/agent/src/pkg/util/command/process_win.go rename to src/agent/agent/src/pkg/util/command/user_win.go index e4c9e7588c3..b1e502fb492 100644 --- a/src/agent/agent/src/pkg/util/command/process_win.go +++ b/src/agent/agent/src/pkg/util/command/user_win.go @@ -31,70 +31,12 @@ package command import ( - "fmt" - "os" "os/exec" - "syscall" "github.com/TencentBlueKing/bk-ci/agentcommon/logs" ) -const createNewConsole = 0x00000010 - -func StartProcess(command string, args []string, workDir string, envMap map[string]string, runUser string) (int, error) { - cmd, err := StartProcessCmd(command, args, workDir, envMap, runUser) - if err != nil { - return -1, err - } - return cmd.Process.Pid, nil -} - -func StartProcessCmd(command string, args []string, workDir string, envMap map[string]string, runUser string) (*exec.Cmd, error) { - cmd := exec.Command(command) - - // TODO: #10179 读取环境变量判断是否使用 newConsole - if true { - cmd.SysProcAttr = &syscall.SysProcAttr{ - CreationFlags: createNewConsole, - NoInheritHandles: true, - } - } - - if len(args) > 0 { - cmd.Args = append(cmd.Args, args...) - } - - if workDir != "" { - cmd.Dir = workDir - } - - cmd.Env = os.Environ() - if envMap != nil { - for k, v := range envMap { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) - } - } - - err := setUser(cmd, runUser) - if err != nil { - logs.Error("set user failed: ", err.Error()) - return nil, fmt.Errorf("%s, Please check [devops.slave.user] in the {agent_dir}/.agent.properties", err.Error()) - } - - logs.Info("cmd.Path: ", cmd.Path) - logs.Info("cmd.Args: ", cmd.Args) - logs.Info("cmd.workDir: ", cmd.Dir) - logs.Info("runUser: ", runUser) - - err = cmd.Start() - if err != nil { - return nil, err - } - - return cmd, nil -} - -func setUser(_ *exec.Cmd, runUser string) error { +func SetUser(_ *exec.Cmd, runUser string) error { logs.Info("set user(windows): ", runUser) return nil } diff --git a/src/agent/agent/src/pkg/util/command/process_exit_group_win.go b/src/agent/agent/src/pkg/util/process/process_exit_group_win.go similarity index 96% rename from src/agent/agent/src/pkg/util/command/process_exit_group_win.go rename to src/agent/agent/src/pkg/util/process/process_exit_group_win.go index f7dc82ec196..2b033e9f389 100644 --- a/src/agent/agent/src/pkg/util/command/process_exit_group_win.go +++ b/src/agent/agent/src/pkg/util/process/process_exit_group_win.go @@ -28,7 +28,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package command +package process import ( "os" @@ -39,6 +39,7 @@ import ( // We use this struct to retreive process handle(which is unexported) // from os.Process using unsafe operation. +// Source https://gist.github.com/hallazzang/76f3970bfc949831808bbebc8ca15209 type process struct { Pid int Handle uintptr From e645fe8bed0363771700184a940d466cc473fc6f Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 18 Apr 2024 19:52:08 +0800 Subject: [PATCH 0006/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent/src/pkg/job/do_build.go | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/agent/agent/src/pkg/job/do_build.go b/src/agent/agent/src/pkg/job/do_build.go index 8770af7c0e8..a940638689d 100644 --- a/src/agent/agent/src/pkg/job/do_build.go +++ b/src/agent/agent/src/pkg/job/do_build.go @@ -64,7 +64,8 @@ func doBuild( return err } - enableExitGroup := config.FetchEnvAndCheck(constant.DEVOPS_AGENT_ENABLE_EXIT_GROUP, "true") + enableExitGroup := config.FetchEnvAndCheck(constant.DEVOPS_AGENT_ENABLE_EXIT_GROUP, "true") || + (systemutil.IsMacos() && runtime.GOARCH == "arm64") if enableExitGroup { logs.Infof("%s enable exit group", buildInfo.BuildId) } @@ -85,18 +86,25 @@ func doBuild( _ = fileutil.WriteString(errorMsgFile, i18n.Localize("BuilderProcessWasKilled", nil)) _ = systemutil.Chmod(errorMsgFile, os.ModePerm) - err = cmd.Wait() if enableExitGroup { - go func() { - pid := cmd.Process.Pid - logs.Infof("%s do kill %d process group", buildInfo.BuildId, pid) - // 杀死进程组 - err = syscall.Kill(-pid, syscall.SIGTERM) - if err != nil { - logs.Errorf("%s failed to kill %d process group: %s", buildInfo.BuildId, pid, err.Error()) - return - } - }() + pgId, errPg := syscall.Getpgid(pid) + if errPg != nil { + logs.Errorf("%s %d get pgid error %s", buildInfo.BuildId, pid, errPg.Error()) + } + err = cmd.Wait() + if errPg == nil { + go func() { + logs.Infof("%s do kill %d process group %d", buildInfo.BuildId, pid, pgId) + // 杀死进程组 + errPg = syscall.Kill(-pgId, syscall.SIGKILL) + if errPg != nil { + logs.Errorf("%s failed to kill %d process group %d : %s", buildInfo.BuildId, pid, pgId, errPg.Error()) + return + } + }() + } + } else { + err = cmd.Wait() } // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) @@ -214,7 +222,7 @@ func StartProcessCmd( cmd := exec.Command(command) // arm64机器目前无法通过worker杀进程 - if enableExitGroup || (systemutil.IsMacos() && runtime.GOARCH == "arm") { + if enableExitGroup { cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} } From a74b60e4b9aeaf4fcfefacd741d2ed09c32669be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Fri, 19 Apr 2024 10:31:14 +0800 Subject: [PATCH 0007/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/event/dispatcher/pipeline/mq/MQ.kt | 1 + .../PipelineBuildStatusBroadCastEvent.kt | 8 + .../common/pipeline/event/CallBackData.kt | 2 + .../common/pipeline/utils/EventUtils.kt | 77 ++++ .../devops/common/redis/RedisOperation.kt | 17 +- .../core/metrics/api-metrics/build.gradle.kts | 1 + .../devops/metrics/pojo/po/MetricsLocalPO.kt | 37 ++ .../devops/metrics/pojo/po/MetricsUserPO.kt | 97 +++++ .../metrics/config/MetricsUserConfig.kt | 88 ++++ .../MetricsUserListenerConfiguration.kt | 91 ++++ .../listener/BuildMetricsUserListener.kt | 51 +++ .../service/builds/MetricsCacheService.kt | 232 +++++++++++ .../service/builds/MetricsHeartBeatService.kt | 182 ++++++++ .../service/builds/MetricsUserRunner.kt | 44 ++ .../service/builds/MetricsUserService.kt | 392 ++++++++++++++++++ .../engine/pojo/PipelineBuildContainer.kt | 1 + .../process/engine/pojo/PipelineBuildTask.kt | 3 +- .../engine/control/VmOperateTaskGenerator.kt | 17 +- .../engine/dao/PipelineBuildContainerDao.kt | 6 + .../engine/dao/PipelineBuildTaskDao.kt | 6 + .../service/PipelineContainerService.kt | 5 +- .../service/vmbuild/EngineVMBuildService.kt | 46 +- .../process/engine/atom/TaskAtomService.kt | 20 +- .../process/engine/control/BuildEndControl.kt | 5 +- .../engine/control/BuildStartControl.kt | 10 +- .../impl/StartActionTaskContainerCmd.kt | 26 ++ .../impl/UpdateStateContainerCmdFinally.kt | 49 ++- .../stage/impl/StartContainerStageCmd.kt | 5 +- .../impl/UpdateStateForStageCmdFinally.kt | 18 +- .../listener/run/PipelineTaskPauseListener.kt | 13 +- .../process/engine/control/CallBackControl.kt | 33 +- .../engine/control/CallBackControlTest.kt | 4 +- .../2010_ci_process-update_v1.10_mysql.sql | 19 + 33 files changed, 1528 insertions(+), 78 deletions(-) create mode 100644 src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/EventUtils.kt create mode 100644 src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsLocalPO.kt create mode 100644 src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsUserPO.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserListenerConfiguration.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/BuildMetricsUserListener.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsHeartBeatService.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserRunner.kt create mode 100644 src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt index a54b456f535..7a3df114dd2 100644 --- a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt @@ -257,6 +257,7 @@ object MQ { // 回调 const val EXCHANGE_PIPELINE_BUILD_CALL_BACK_FANOUT = "e.engine.pipeline.build.callback.fanout" const val QUEUE_PIPELINE_BUILD_STATUS_CHANGE = "e.engine.pipeline.build.callback.change" + const val QUEUE_PIPELINE_BUILD_STATUS_METRICS = "q.engine.pipeline.build.callback.metrics" // 蓝盾项目管理 const val EXCHANGE_PROJECT_CREATE_FANOUT = "e.project.create.exchange.fanout" diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/pipeline/PipelineBuildStatusBroadCastEvent.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/pipeline/PipelineBuildStatusBroadCastEvent.kt index 7be78203f8d..dc9c9cbfd2a 100644 --- a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/pipeline/PipelineBuildStatusBroadCastEvent.kt +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/pipeline/PipelineBuildStatusBroadCastEvent.kt @@ -30,6 +30,7 @@ package com.tencent.devops.common.event.pojo.pipeline import com.tencent.devops.common.event.annotation.Event import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ import com.tencent.devops.common.event.enums.ActionType +import java.time.LocalDateTime /** * 构建状态的广播事件,用于通知等 @@ -42,7 +43,14 @@ data class PipelineBuildStatusBroadCastEvent( override val userId: String, val buildId: String, val stageId: String? = null, + val containerHashId: String? = null, + val jobId: String? = null, val taskId: String? = null, + val stepId: String? = null, + val executeCount: Int?, + val buildStatus: String?, + val atomCode: String? = null, + val eventTime: LocalDateTime? = LocalDateTime.now(), override var actionType: ActionType, override var delayMills: Int = 0 ) : IPipelineEvent(actionType, source, projectId, pipelineId, userId, delayMills) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/event/CallBackData.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/event/CallBackData.kt index 91a5126717b..43c38262c8d 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/event/CallBackData.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/event/CallBackData.kt @@ -97,6 +97,8 @@ enum class CallBackEvent { BUILD_TASK_END, BUILD_STAGE_START, BUILD_STAGE_END, + BUILD_JOB_START, + BUILD_JOB_END, BUILD_TASK_PAUSE } diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/EventUtils.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/EventUtils.kt new file mode 100644 index 00000000000..e2ba28f9420 --- /dev/null +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/EventUtils.kt @@ -0,0 +1,77 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.common.pipeline.utils + +import com.tencent.devops.common.event.enums.ActionType +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent +import com.tencent.devops.common.pipeline.event.CallBackEvent + +@Suppress("ComplexMethod") +object EventUtils { + fun PipelineBuildStatusBroadCastEvent.toEventType(): CallBackEvent? { + if (!taskId.isNullOrBlank()) { + if (actionType == ActionType.START) { + return CallBackEvent.BUILD_TASK_START + } + if (actionType == ActionType.REFRESH) { + return CallBackEvent.BUILD_TASK_PAUSE + } + if (actionType == ActionType.END) { + return CallBackEvent.BUILD_TASK_END + } + } + + if (!containerHashId.isNullOrBlank()) { + if (actionType == ActionType.START) { + return CallBackEvent.BUILD_JOB_START + } + if (actionType == ActionType.END) { + return CallBackEvent.BUILD_JOB_END + } + } + + if (!stageId.isNullOrBlank()) { + if (actionType == ActionType.START) { + return CallBackEvent.BUILD_STAGE_START + } + if (actionType == ActionType.END) { + return CallBackEvent.BUILD_STAGE_END + } + } + + if (taskId.isNullOrBlank() && containerHashId.isNullOrBlank() && stageId.isNullOrBlank()) { + if (actionType == ActionType.START) { + return CallBackEvent.BUILD_START + } + if (actionType == ActionType.END) { + return CallBackEvent.BUILD_END + } + } + return null + } +} diff --git a/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisOperation.kt b/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisOperation.kt index d1d9195ecb5..1f830a06cfd 100644 --- a/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisOperation.kt +++ b/src/backend/ci/core/common/common-redis/src/main/kotlin/com/tencent/devops/common/redis/RedisOperation.kt @@ -29,15 +29,15 @@ package com.tencent.devops.common.redis import com.tencent.devops.common.redis.split.RedisSplitProperties import io.micrometer.core.instrument.util.NamedThreadFactory +import java.util.Date +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit import org.springframework.data.redis.core.Cursor import org.springframework.data.redis.core.DefaultTypedTuple import org.springframework.data.redis.core.RedisCallback import org.springframework.data.redis.core.RedisTemplate import org.springframework.data.redis.core.ScanOptions import org.springframework.data.redis.core.script.RedisScript -import java.util.Date -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit @Suppress("TooManyFunctions", "UNUSED", "ComplexMethod") class RedisOperation( @@ -228,6 +228,11 @@ class RedisOperation( return masterRedisTemplate.opsForHash().get(getFinalKey(key, isDistinguishCluster), hashKey) } + fun hmGet(key: String, hashKeys: Collection, isDistinguishCluster: Boolean? = false): List? { + return masterRedisTemplate.opsForHash() + .multiGet(getFinalKey(key, isDistinguishCluster), hashKeys) + } + fun hdelete(key: String, hashKey: String, isDistinguishCluster: Boolean? = false) { // 双写 writeSlaveIfNeed { @@ -236,12 +241,12 @@ class RedisOperation( masterRedisTemplate.opsForHash().delete(getFinalKey(key, isDistinguishCluster), hashKey) } - fun hdelete(key: String, hashKeys: Collection, isDistinguishCluster: Boolean? = false) { + fun hdelete(key: String, hashKeys: Array, isDistinguishCluster: Boolean? = false) { // 双写 writeSlaveIfNeed { - slaveRedisTemplate!!.opsForHash().delete(getFinalKey(key, isDistinguishCluster), hashKeys) + slaveRedisTemplate!!.opsForHash().delete(getFinalKey(key, isDistinguishCluster), *hashKeys) } - masterRedisTemplate.opsForHash().delete(getFinalKey(key, isDistinguishCluster), hashKeys) + masterRedisTemplate.opsForHash().delete(getFinalKey(key, isDistinguishCluster), *hashKeys) } fun hhaskey(key: String, hashKey: String, isDistinguishCluster: Boolean? = false): Boolean { diff --git a/src/backend/ci/core/metrics/api-metrics/build.gradle.kts b/src/backend/ci/core/metrics/api-metrics/build.gradle.kts index c11b0a0f011..2f7765c8e71 100644 --- a/src/backend/ci/core/metrics/api-metrics/build.gradle.kts +++ b/src/backend/ci/core/metrics/api-metrics/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { api(project(":core:common:common-api")) api(project(":core:common:common-web")) api(project(":core:common:common-event")) + api(project(":core:common:common-pipeline")) } plugins { diff --git a/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsLocalPO.kt b/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsLocalPO.kt new file mode 100644 index 00000000000..5262000a8da --- /dev/null +++ b/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsLocalPO.kt @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.metrics.pojo.po + +import io.micrometer.core.instrument.Meter + +data class MetricsLocalPO( + var data: MetricsUserPO, + val meters: MutableList +) { + constructor(data: MetricsUserPO) : this(data, mutableListOf()) +} diff --git a/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsUserPO.kt b/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsUserPO.kt new file mode 100644 index 00000000000..48b1bd36e83 --- /dev/null +++ b/src/backend/ci/core/metrics/api-metrics/src/main/kotlin/com/tencent/devops/metrics/pojo/po/MetricsUserPO.kt @@ -0,0 +1,97 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.metrics.pojo.po + +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent +import com.tencent.devops.common.pipeline.event.CallBackEvent +import com.tencent.devops.common.pipeline.utils.EventUtils.toEventType +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset + +data class MetricsUserPO( + var startTime: LocalDateTime, + val projectId: String, + val pipelineId: String, + val buildId: String, + val jobId: String?, + val stepId: String?, + val status: String, + val atomCode: String?, + val eventType: CallBackEvent, + var endTime: LocalDateTime? +) { + constructor(event: PipelineBuildStatusBroadCastEvent) : this( + startTime = LocalDateTime.now(), + projectId = event.projectId, + pipelineId = event.pipelineId, + buildId = event.buildId, + jobId = event.jobId, + stepId = event.stepId, + status = checkNotNull(event.buildStatus), + atomCode = event.atomCode, + eventType = checkNotNull(event.toEventType()), + endTime = null + ) + + companion object { + const val DELIMITER = "," + fun load(str: String?): MetricsUserPO? { + if (str.isNullOrBlank()) return null + val list = str.split(DELIMITER) + if (list.size != 10) return null + return MetricsUserPO( + LocalDateTime.ofInstant(Instant.ofEpochSecond(list[0].toLong()), ZoneOffset.ofHours(8)), + list[1], + list[2], + list[3], + list[4].ifEmpty { null }, + list[5].ifEmpty { null }, + list[6], + list[7].ifEmpty { null }, + CallBackEvent.valueOf(list[8]), + list[9].ifEmpty { null }?.let { + LocalDateTime.ofInstant(Instant.ofEpochSecond(it.toLong()), ZoneOffset.ofHours(8)) + } + ) + } + } + + override fun toString(): String { + return startTime.toInstant(ZoneOffset.ofHours(8)).epochSecond.toString() + DELIMITER + + projectId + DELIMITER + + pipelineId + DELIMITER + + buildId + DELIMITER + + (jobId ?: "") + DELIMITER + + (stepId ?: "") + DELIMITER + + status + DELIMITER + + (atomCode ?: "") + DELIMITER + + eventType.name + DELIMITER + + (endTime?.toInstant(ZoneOffset.ofHours(8))?.epochSecond?.toString() ?: "") + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt new file mode 100644 index 00000000000..389c4cc2c5a --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt @@ -0,0 +1,88 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.metrics.config + +import io.micrometer.core.instrument.Clock +import io.micrometer.prometheus.PrometheusConfig +import io.micrometer.prometheus.PrometheusMeterRegistry +import io.prometheus.client.CollectorRegistry +import io.prometheus.client.exporter.common.TextFormat +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint +import org.springframework.boot.actuate.metrics.export.prometheus.TextOutputFormat +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class MetricsUserConfig { + + companion object { + const val gaugeBuildKey = "pipeline_running_time_seconds" + const val gaugeBuildStatusKey = "pipeline_status_info" + const val gaugeBuildJobKey = "pipeline_job_running_time_seconds" + const val gaugeBuildStepKey = "pipeline_step_running_time_seconds" + const val gaugeBuildStepStatusKey = "pipeline_step_status_info" + } + + /*注册默认的 prometheusMeterRegistry*/ + @Bean + fun prometheusMeterRegistry( + prometheusConfig: PrometheusConfig, + collectorRegistry: CollectorRegistry, + clock: Clock + ): PrometheusMeterRegistry { + return PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock) + } + + @Bean + fun userPrometheusMeterRegistry(): PrometheusMeterRegistry { + return PrometheusMeterRegistry(PrometheusConfig.DEFAULT) + } + + @Bean + fun userPrometheusEndpoint(userPrometheusMeterRegistry: PrometheusMeterRegistry): UserPrometheusEndpoint { + return UserPrometheusEndpoint(userPrometheusMeterRegistry) + } + + @WebEndpoint(id = "userPrometheus") + class UserPrometheusEndpoint(private val meterRegistry: PrometheusMeterRegistry) { + + @ReadOperation(producesFrom = TextOutputFormat::class) + fun scrape(): String { + return meterRegistry.scrape( + TextFormat.CONTENT_TYPE_004, setOf( + gaugeBuildKey, + gaugeBuildStatusKey, + gaugeBuildJobKey, + gaugeBuildStepKey, + gaugeBuildStepStatusKey + ) + ) + } + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserListenerConfiguration.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserListenerConfiguration.kt new file mode 100644 index 00000000000..aebef2bebd6 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserListenerConfiguration.kt @@ -0,0 +1,91 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.metrics.config + +import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ +import com.tencent.devops.common.event.dispatcher.pipeline.mq.Tools +import com.tencent.devops.metrics.listener.BuildMetricsUserListener +import org.springframework.amqp.core.Binding +import org.springframework.amqp.core.BindingBuilder +import org.springframework.amqp.core.FanoutExchange +import org.springframework.amqp.core.Queue +import org.springframework.amqp.rabbit.connection.ConnectionFactory +import org.springframework.amqp.rabbit.core.RabbitAdmin +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class MetricsUserListenerConfiguration { + /** + * 构建构建回调广播交换机 + */ + @Bean + fun pipelineBuildStatusCallbackFanoutExchange(): FanoutExchange { + val fanoutExchange = FanoutExchange(MQ.EXCHANGE_PIPELINE_BUILD_CALL_BACK_FANOUT, true, false) + fanoutExchange.isDelayed = true + return fanoutExchange + } + + @Bean + fun pipelineBuildStatusMetricsQueue(): Queue { + return Queue(MQ.QUEUE_PIPELINE_BUILD_STATUS_METRICS) + } + + @Bean + fun pipelineBuildStatusMetricsQueueBind( + @Autowired pipelineBuildStatusMetricsQueue: Queue, + @Autowired pipelineBuildStatusCallbackFanoutExchange: FanoutExchange + ): Binding { + return BindingBuilder.bind(pipelineBuildStatusMetricsQueue).to(pipelineBuildStatusCallbackFanoutExchange) + } + + @Bean + fun pipelineBuildCallBackListenerContainer( + @Autowired connectionFactory: ConnectionFactory, + @Autowired pipelineBuildStatusMetricsQueue: Queue, + @Autowired rabbitAdmin: RabbitAdmin, + @Autowired buildListener: BuildMetricsUserListener, + @Autowired messageConverter: Jackson2JsonMessageConverter + ): SimpleMessageListenerContainer { + + return Tools.createSimpleMessageListenerContainer( + connectionFactory = connectionFactory, + queue = pipelineBuildStatusMetricsQueue, + rabbitAdmin = rabbitAdmin, + buildListener = buildListener, + messageConverter = messageConverter, + startConsumerMinInterval = 10000, + consecutiveActiveTrigger = 5, + concurrency = 1, + maxConcurrency = 50 + ) + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/BuildMetricsUserListener.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/BuildMetricsUserListener.kt new file mode 100644 index 00000000000..e909c5d32a9 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/BuildMetricsUserListener.kt @@ -0,0 +1,51 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.metrics.listener + +import com.tencent.devops.common.event.listener.Listener +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent +import com.tencent.devops.metrics.service.builds.MetricsUserService +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +class BuildMetricsUserListener @Autowired constructor( + private val metricsUserService: MetricsUserService +) : Listener { + + companion object { + val logger = LoggerFactory.getLogger(BuildMetricsUserListener::class.java) + } + + override fun execute(event: PipelineBuildStatusBroadCastEvent) { + kotlin.runCatching { metricsUserService.execute(event) }.onFailure { + logger.warn("BuildMetricsUserListener error |$event", it) + } + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt new file mode 100644 index 00000000000..e8789df5782 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt @@ -0,0 +1,232 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.metrics.service.builds + +import com.google.common.collect.MapMaker +import com.google.common.hash.Hashing +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.metrics.pojo.po.MetricsUserPO +import groovy.util.ObservableMap +import java.beans.PropertyChangeEvent +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service + +@Service +class MetricsCacheService @Autowired constructor( + private val metricsHeartBeatService: MetricsHeartBeatService, + @Qualifier("redisStringHashOperation") + private val redisHashOperation: RedisOperation +) { + + private val cache = ObservableMap( + MapMaker() + .concurrencyLevel(10) + .makeMap() + ) + + lateinit var addFunction: (key: String, value: MetricsUserPO) -> Unit + + lateinit var removeFunction: (key: String, value: MetricsUserPO) -> Unit + + lateinit var updateFunction: (key: String, oldValue: MetricsUserPO, newValue: MetricsUserPO) -> Unit + + companion object { + fun podKey(key: String) = "build_metrics:pod:$key" + fun updateKey() = "build_metrics:update" + val logger: Logger = LoggerFactory.getLogger(MetricsCacheService::class.java) + } + + fun init() { + cache.addPropertyChangeListener { change: PropertyChangeEvent -> + when { + change is ObservableMap.PropertyAddedEvent && this::addFunction.isInitialized -> { + addFunction(change.propertyName, change.newValue as MetricsUserPO) + } + + change is ObservableMap.PropertyUpdatedEvent && this::updateFunction.isInitialized -> { + updateFunction( + change.propertyName, + change.oldValue as MetricsUserPO, + change.newValue as MetricsUserPO + ) + } + + change is ObservableMap.PropertyRemovedEvent && this::removeFunction.isInitialized -> { + removeFunction(change.propertyName, change.oldValue as MetricsUserPO) + } + } + } + Thread(CacheUpdateProcess(metricsHeartBeatService.getPodName(), cache, redisHashOperation)).start() + metricsHeartBeatService.init() + } + + fun removeCache(key: String) { + cache.remove(key) + redisHashOperation.hdelete(podKey(metricsHeartBeatService.getPodName()), key) + } + + fun buildCacheStart( + buildId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$executeCount") + redisHashOperation.hset(podKey(metricsHeartBeatService.getPodName()), key, data.toString()) + cache[key] = data + return key + } + + fun buildCacheEnd( + buildId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$executeCount") + cacheEnd(key, data) + return key + } + + fun jobCacheStart( + buildId: String, + jobId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$jobId-$executeCount") + redisHashOperation.hset(podKey(metricsHeartBeatService.getPodName()), key, data.toString()) + cache[key] = data + return key + } + + fun jobCacheEnd( + buildId: String, + jobId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$jobId-$executeCount") + cacheEnd(key, data) + return key + } + + fun stepCacheStart( + buildId: String, + stepId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$stepId-$executeCount") + redisHashOperation.hset(podKey(metricsHeartBeatService.getPodName()), key, data.toString()) + cache[key] = data + return key + } + + fun stepCacheEnd( + buildId: String, + stepId: String, + executeCount: Int, + data: MetricsUserPO + ): String { + val key = hash("$buildId-$stepId-$executeCount") + cacheEnd(key, data) + return key + } + + private fun hash(input: String): String { + return Hashing.murmur3_128().hashBytes(input.toByteArray()).toString() + } + + private fun cacheEnd(key: String, data: MetricsUserPO) { + val cacheKey = cache[key] as MetricsUserPO? + if (cacheKey != null) { + cache[key] = data.apply { startTime = cacheKey.startTime } + redisHashOperation.hset(podKey(metricsHeartBeatService.getPodName()), key, data.toString()) + } else { + redisHashOperation.hset(updateKey(), key, data.toString()) + } + } + + private class CacheUpdateProcess( + private val podHashKey: String, + private val cache: ObservableMap, + private val redisOperation: RedisOperation + ) : Runnable { + + companion object { + const val SLEEP = 5000L + } + + override fun run() { + logger.info("CacheUpdateProcess begin") + while (true) { + kotlin.runCatching { executeUpdate() }.onFailure { + logger.error("metrics execute update process find a error|${it.message}", it) + } + kotlin.runCatching { executeAdd() }.onFailure { + logger.error("metrics execute add process find a error|${it.message}", it) + } + Thread.sleep(SLEEP) + } + } + + private fun executeUpdate() { + val update = redisOperation.hkeys(updateKey()) ?: return + val snapshot = cache.keys.toList() + update.parallelStream().forEach { ready -> + if (ready in snapshot) { + // 如果key存在,说明状态维护在当前实例 + val load = MetricsUserPO.load(redisOperation.hget(updateKey(), ready)) ?: return@forEach + redisOperation.hdelete(updateKey(), ready) + cache[ready] = load.apply { startTime = (cache[ready] as MetricsUserPO?)?.startTime ?: startTime } + } + } + } + + private fun executeAdd() { + val add = redisOperation.hkeys(podKey(podHashKey)) ?: return + val snapshot = cache.keys.toList() + logger.info("executeAdd local size=${snapshot.size}|${add.size}") + add.parallelStream().forEach { ready -> + if (ready !in snapshot) { + // 如果key不存在,说明是需要新增的 + val load = MetricsUserPO.load(redisOperation.hget(podKey(podHashKey), ready)) ?: return@forEach + cache[ready] = load + } + } + snapshot.parallelStream().forEach { already -> + if (already !in add) { + cache.remove(already) + } + } + } + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsHeartBeatService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsHeartBeatService.kt new file mode 100644 index 00000000000..19747c58584 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsHeartBeatService.kt @@ -0,0 +1,182 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.metrics.service.builds + +import com.tencent.devops.common.redis.RedisLock +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.metrics.pojo.po.MetricsUserPO +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.util.UUID +import org.apache.commons.lang3.math.NumberUtils +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service + +@Service +class MetricsHeartBeatService @Autowired constructor( + @Qualifier("redisStringHashOperation") + private val redisHashOperation: RedisOperation +) { + private val podName: String = System.getenv("POD_NAME") ?: UUID.randomUUID().toString() + + companion object { + private fun heartBeatKey() = "build_metrics:heart_beat" + private val logger = LoggerFactory.getLogger(MetricsHeartBeatService::class.java) + } + + fun init() { + Thread(HeartBeatProcess(podName, redisHashOperation)).start() + Thread(HeartBeatManagerProcess(redisHashOperation)).start() + } + + fun getPodName(): String = podName + + private class HeartBeatProcess( + private val podHashKey: String, + private val redisOperation: RedisOperation + ) : Runnable { + + companion object { + private const val EXPIRED_SECOND = 60L + const val SLEEP = 5000L + } + + override fun run() { + logger.info("HeartBeatProcess run") + while (true) { + kotlin.runCatching { execute() }.onFailure { + logger.error("metrics heart beat error|${it.message}", it) + } + Thread.sleep(SLEEP) + } + } + + private fun execute() { + redisOperation.hset( + heartBeatKey(), + podHashKey, + LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).epochSecond.toString() + ) + } + } + + private class HeartBeatManagerProcess( + private val redisOperation: RedisOperation + ) : Runnable { + + companion object { + private const val REDIS_LOCK_KEY = "metrics_heart_beat_manager_process_redis_lock" + const val SLEEP = 15000L + const val CHUNKED = 100 + } + + override fun run() { + logger.info("HeartBeatManagerProcess begin") + while (true) { + val redisLock = RedisLock(redisOperation, REDIS_LOCK_KEY, 60L) + try { + val lockSuccess = redisLock.tryLock() + if (lockSuccess) { + logger.info("HeartBeatManagerProcess get lock.") + heartBeatCheck() + invalidUpdateCheck() + } + } catch (e: Throwable) { + logger.error("HeartBeatManagerProcess failed ${e.message}", e) + } finally { + Thread.sleep(SLEEP) + redisLock.unlock() + } + } + } + + private fun invalidUpdateCheck() { + val updateKey = redisOperation.hkeys(MetricsCacheService.updateKey())?.ifEmpty { null } ?: return + val limit = LocalDateTime.now().plusHours(-1) + val needDelete = mutableListOf() + updateKey.chunked(CHUNKED).forEach { keys -> + val updateValues = redisOperation.hmGet(MetricsCacheService.updateKey(), keys) + ?: return@forEach + keys.forEachIndexed { index, key -> + val load = MetricsUserPO.load(updateValues[index]) ?: return@forEachIndexed + if (load.endTime!! < limit) { + needDelete.add(key) + } + } + } + if (needDelete.isNotEmpty()) { + redisOperation.hdelete(MetricsCacheService.updateKey(), needDelete.toTypedArray()) + } + } + + private fun heartBeatCheck() { + val heartBeats = redisOperation.hentries(heartBeatKey()) ?: return + val limit = LocalDateTime.now().plusMinutes(-1).toInstant(ZoneOffset.ofHours(8)).epochSecond + val lose = mutableListOf() + val live = mutableListOf() + heartBeats.toList().parallelStream().forEach { (podName, lastOnlineTime) -> + if (NumberUtils.toLong(lastOnlineTime) < limit) { + lose.add(podName) + } else { + live.add(podName) + } + } + logger.info("heartBeatCheck start check lose=$lose|live=$live") + if (live.isEmpty()) { + return + } + lose.forEach { losePod -> + if (afterLosePod(losePod, live)) { + redisOperation.hdelete(heartBeatKey(), losePod) + redisOperation.delete(MetricsCacheService.podKey(losePod)) + } + } + } + + private fun afterLosePod(losePod: String, live: List): Boolean { + val losePodKeys = redisOperation.hkeys(MetricsCacheService.podKey(losePod))?.ifEmpty { null } ?: return true + // 分块处理,优化性能。 + losePodKeys.chunked(CHUNKED).forEachIndexed { index, keys -> + val losePodValues = redisOperation.hmGet(MetricsCacheService.podKey(losePod), keys) + ?: return@forEachIndexed + // 分配近似达到负载均衡的效果 + redisOperation.hmset( + MetricsCacheService.podKey(live[index % live.size]), + keys.zip(losePodValues).toMap() + ) + redisOperation.hdelete(MetricsCacheService.podKey(losePod), keys.toTypedArray()) + } + // 双重校验数据一致性 + val check = redisOperation.hkeys(MetricsCacheService.podKey(losePod)) + return check.isNullOrEmpty() + } + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserRunner.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserRunner.kt new file mode 100644 index 00000000000..d24bee85015 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserRunner.kt @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.metrics.service.builds + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner +import org.springframework.stereotype.Component + +@Component +class MetricsUserRunner @Autowired constructor( + private val metricsUserService: MetricsUserService +) : ApplicationRunner { + + override fun run(args: ApplicationArguments) { + metricsUserService.init() + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt new file mode 100644 index 00000000000..9d29bfa5323 --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -0,0 +1,392 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.metrics.service.builds + +import com.google.common.collect.MapMaker +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent +import com.tencent.devops.common.pipeline.event.CallBackEvent +import com.tencent.devops.metrics.config.MetricsUserConfig +import com.tencent.devops.metrics.pojo.po.MetricsLocalPO +import com.tencent.devops.metrics.pojo.po.MetricsUserPO +import io.micrometer.core.instrument.Gauge +import io.micrometer.core.instrument.Meter +import io.micrometer.prometheus.PrometheusMeterRegistry +import java.time.Duration +import java.time.LocalDateTime +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service + +@Service +class MetricsUserService @Autowired constructor( + @Qualifier("userPrometheusMeterRegistry") + private val registry: PrometheusMeterRegistry, + private val metricsCacheService: MetricsCacheService +) { + private val local = MapMaker() + .concurrencyLevel(10) + .makeMap() + private val executor = Executors.newSingleThreadScheduledExecutor() + + companion object { + val logger: Logger = LoggerFactory.getLogger(MetricsUserService::class.java) + } + + fun init() { + metricsCacheService.addFunction = this::metricsAdd + metricsCacheService.removeFunction = this::metricsRemove + metricsCacheService.updateFunction = this::metricsUpdate + metricsCacheService.init() + } + + fun execute(event: PipelineBuildStatusBroadCastEvent) { + val date = MetricsUserPO(event) + when (date.eventType) { + CallBackEvent.BUILD_START -> { + date.startTime = checkNotNull(event.eventTime) + metricsCacheService.buildCacheStart(event.buildId, checkNotNull(event.executeCount), date) + } + + CallBackEvent.BUILD_JOB_START -> { + if (event.jobId == null) { + // job id 用户没填写将不会上报指标 + return + } + date.startTime = checkNotNull(event.eventTime) + metricsCacheService.jobCacheStart( + event.buildId, + checkNotNull(event.jobId), + checkNotNull(event.executeCount), + date + ) + } + + CallBackEvent.BUILD_TASK_START -> { + date.startTime = checkNotNull(event.eventTime) + if (event.stepId == null) { + // stepId id 用户没填写将不会上报指标 + return + } + metricsCacheService.stepCacheStart( + event.buildId, + checkNotNull(event.stepId), + checkNotNull(event.executeCount), + date + ) + } + + CallBackEvent.BUILD_END -> { + date.endTime = checkNotNull(event.eventTime) + metricsCacheService.buildCacheEnd(event.buildId, checkNotNull(event.executeCount), date) + + } + + CallBackEvent.BUILD_JOB_END -> { + if (event.jobId == null) { + // job id 用户没填写将不会上报指标 + return + } + date.endTime = checkNotNull(event.eventTime) + metricsCacheService.jobCacheEnd( + event.buildId, + checkNotNull(event.jobId), + checkNotNull(event.executeCount), + date + ) + + } + + CallBackEvent.BUILD_TASK_END -> { + date.endTime = checkNotNull(event.eventTime) + if (event.stepId == null) { + // stepId id 用户没填写将不会上报指标 + return + } + metricsCacheService.stepCacheEnd( + event.buildId, + checkNotNull(event.stepId), + checkNotNull(event.executeCount), + date + ) + } + + else -> {} + } + } + + fun metricsAdd(key: String, value: MetricsUserPO) { + local[key] = MetricsLocalPO(value) + logger.debug("metricsAdd|key={}|value={}|localSize={}", key, value, local.size) + with(value) { + when (eventType) { + CallBackEvent.BUILD_START -> { + val buildGauge = registerBuildGauge( + key = key, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + description = "build metrics for $buildId" + ) + local[key]?.meters?.add(buildGauge) + val buildStatusGauge = registerBuildStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + status = status, + description = "build status metrics for $buildId" + ) + local[key]?.meters?.add(buildStatusGauge) + } + + CallBackEvent.BUILD_JOB_START -> { + val buildJobGauge = registerBuildJobGauge( + key = key, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + jobId = checkNotNull(jobId), + description = "job metrics for $buildId|$jobId" + ) + local[key]?.meters?.add(buildJobGauge) + } + + CallBackEvent.BUILD_TASK_START -> { + val buildStepGauge = registerBuildStepGauge( + key = key, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + jobId = checkNotNull(jobId), + stepId = checkNotNull(stepId), + atomCode = checkNotNull(atomCode), + description = "step metrics for $buildId|$stepId" + ) + local[key]?.meters?.add(buildStepGauge) + val buildStepStatusGauge = registerBuildStepStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + jobId = jobId!!, + stepId = stepId!!, + status = status, + description = "step status metrics for $buildId|$stepId" + ) + local[key]?.meters?.add(buildStepStatusGauge) + } + + else -> { + /*其余情况属于END状态,应当去除*/ + metricsCacheService.removeCache(key) + } + } + } + } + + fun metricsRemove(key: String, value: MetricsUserPO) { + val metrics = local[key] + logger.debug("metricsRemove|key={}|value={}|metrics={}", key, value, metrics) + if (metrics != null) { + // 异步删除 + executor.schedule({ + metrics.meters.forEach { meter -> + registry.remove(meter) + } + local.remove(key) + }, 5, TimeUnit.MINUTES) + } + } + + fun metricsUpdate(key: String, oldValue: MetricsUserPO, newValue: MetricsUserPO) { + val metrics = local[key] + logger.debug("metricsUpdate|key={}|oldValue={}|newValue={}|metrics={}", key, oldValue, newValue, metrics) + if (metrics != null) { + metrics.data = newValue + with(newValue) { + when (eventType) { + CallBackEvent.BUILD_END -> { + metrics.meters.find { it.id.name == MetricsUserConfig.gaugeBuildStatusKey }?.run { + registry.remove(this) + } + registerBuildStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + status = status, + description = "build status metrics for $buildId" + ) + metricsCacheService.removeCache(key) + } + + CallBackEvent.BUILD_JOB_END -> { + metricsCacheService.removeCache(key) + } + + CallBackEvent.BUILD_TASK_END -> { + metrics.meters.find { it.id.name == MetricsUserConfig.gaugeBuildStepStatusKey }?.run { + registry.remove(this) + } + registerBuildStepStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + jobId = jobId!!, + stepId = stepId!!, + status = status, + description = "step status metrics for $buildId|$stepId" + ) + metricsCacheService.removeCache(key) + } + + else -> {} + } + } + } + } + + private fun registerBuildGauge( + key: String, + projectId: String, + pipelineId: String, + buildId: String, + description: String + ): Meter { + return Gauge.builder( + MetricsUserConfig.gaugeBuildKey, + local + ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } + .tags( + "bk_project_id", projectId, + "pipeline_id", pipelineId, + "build_id", buildId + ) + .description(description) + .register(registry) + } + + private fun registerBuildStatusGauge( + projectId: String, + pipelineId: String, + buildId: String, + status: String, + description: String + ): Meter { + return Gauge.builder( + MetricsUserConfig.gaugeBuildStatusKey + ) { 1 } + .tags( + "bk_project_id", projectId, + "pipeline_id", pipelineId, + "build_id", buildId, + "status", status + ) + .description(description) + .register(registry) + } + + private fun registerBuildJobGauge( + key: String, + projectId: String, + pipelineId: String, + buildId: String, + jobId: String, + description: String + ): Meter { + return Gauge.builder( + MetricsUserConfig.gaugeBuildJobKey, + local + ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } + .tags( + "bk_project_id", projectId, + "pipeline_id", pipelineId, + "build_id", buildId, + "job_id", jobId + ) + .description(description) + .register(registry) + } + + + private fun registerBuildStepGauge( + key: String, + projectId: String, + pipelineId: String, + buildId: String, + jobId: String, + stepId: String, + atomCode: String, + description: String + ): Meter { + return Gauge.builder( + MetricsUserConfig.gaugeBuildStepKey, + local + ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } + .tags( + "bk_project_id", projectId, + "pipeline_id", pipelineId, + "build_id", buildId, + "job_id", jobId, + "step_id", stepId, + "plugin_id", atomCode + ) + .description(description) + .register(registry) + } + + private fun registerBuildStepStatusGauge( + projectId: String, + pipelineId: String, + buildId: String, + jobId: String, + stepId: String, + status: String, + description: String + ): Meter { + return Gauge.builder( + MetricsUserConfig.gaugeBuildStepStatusKey + ) { 1 } + .tags( + "bk_project_id", projectId, + "pipeline_id", pipelineId, + "build_id", buildId, + "job_id", jobId, + "step_id", stepId, + "status", status + ) + .description(description) + .register(registry) + } + + private fun computeStartTime(cache: MetricsLocalPO): Double { + return Duration.between(cache.data.startTime, cache.data.endTime ?: LocalDateTime.now()).seconds.toDouble() + } +} diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildContainer.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildContainer.kt index 002bb285cf2..d5c1436ff12 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildContainer.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildContainer.kt @@ -37,6 +37,7 @@ data class PipelineBuildContainer( val stageId: String, val containerId: String, // 与seq id同值 val containerHashId: String?, // 与model中的container.containerHashId同值 + val jobId: String?, val matrixGroupFlag: Boolean?, val matrixGroupId: String?, val containerType: String, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildTask.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildTask.kt index 0e02e613aea..d0a6d86155f 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildTask.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/engine/pojo/PipelineBuildTask.kt @@ -61,7 +61,8 @@ data class PipelineBuildTask( var errorMsg: String? = null, val atomCode: String? = null, val stepId: String? = null, - var totalTime: Long? = null + var totalTime: Long? = null, + val jobId: String? = null ) { fun getTaskParam(paramName: String): String { return if (taskParams[paramName] != null) { diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/control/VmOperateTaskGenerator.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/control/VmOperateTaskGenerator.kt index 13fb819a3e0..27cd6c4fbb9 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/control/VmOperateTaskGenerator.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/control/VmOperateTaskGenerator.kt @@ -55,11 +55,19 @@ class VmOperateTaskGenerator { fun isVmAtom(task: PipelineBuildTask) = isStartVM(task) || isStopVM(task) + fun isVmAtom(atomCode: String) = isStartVM(atomCode) || isStopVM(atomCode) + fun isStartVM(task: PipelineBuildTask) = task.taskAtom == START_VM_TASK_ATOM || task.taskAtom == START_NORMAL_TASK_ATOM fun isStopVM(task: PipelineBuildTask) = task.taskAtom == SHUTDOWN_VM_TASK_ATOM || task.taskAtom == SHUTDOWN_NORMAL_TASK_ATOM + + fun isStartVM(atomCode: String) = + atomCode.startsWith(START_VM_TASK_ATOM) || atomCode.startsWith(START_NORMAL_TASK_ATOM) + + fun isStopVM(atomCode: String) = + atomCode.startsWith(SHUTDOWN_VM_TASK_ATOM) || atomCode.startsWith(SHUTDOWN_NORMAL_TASK_ATOM) } /** @@ -120,7 +128,8 @@ class VmOperateTaskGenerator { subBuildId = null, additionalOptions = additionalOptions, atomCode = atomCode, - stepId = null + stepId = null, + jobId = container.jobId ) } @@ -182,7 +191,8 @@ class VmOperateTaskGenerator { subBuildId = null, additionalOptions = additionalOptions, atomCode = "$SHUTDOWN_VM_TASK_ATOM-END", - stepId = null + stepId = null, + jobId = container.jobId ) ) @@ -216,7 +226,8 @@ class VmOperateTaskGenerator { subBuildId = null, additionalOptions = additionalOptions, atomCode = "$SHUTDOWN_VM_TASK_ATOM-FINISH", - stepId = null + stepId = null, + jobId = container.jobId ) ) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildContainerDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildContainerDao.kt index 6376b3e7e94..1b8ad9fe54d 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildContainerDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildContainerDao.kt @@ -63,6 +63,7 @@ class PipelineBuildContainerDao { SEQ, CONTAINER_ID, CONTAINER_HASH_ID, + JOB_ID, STATUS, START_TIME, END_TIME, @@ -81,6 +82,7 @@ class PipelineBuildContainerDao { buildContainer.seq, buildContainer.containerId, buildContainer.containerHashId, + buildContainer.jobId, buildContainer.status.ordinal, buildContainer.startTime, buildContainer.endTime, @@ -103,6 +105,7 @@ class PipelineBuildContainerDao { STAGE_ID, CONTAINER_ID, CONTAINER_HASH_ID, + JOB_ID, MATRIX_GROUP_FLAG, MATRIX_GROUP_ID, CONTAINER_TYPE, @@ -122,6 +125,7 @@ class PipelineBuildContainerDao { it.stageId, it.containerId, it.containerHashId, + it.jobId, it.matrixGroupFlag, it.matrixGroupId, it.containerType, @@ -153,6 +157,7 @@ class PipelineBuildContainerDao { .set(CONTAINER_TYPE, it.containerType) .set(CONTAINER_ID, it.containerId) .set(CONTAINER_HASH_ID, it.containerHashId) + .set(JOB_ID, it.jobId) .set(STATUS, it.status.ordinal) .set(START_TIME, it.startTime) .set(END_TIME, it.endTime) @@ -346,6 +351,7 @@ class PipelineBuildContainerDao { containerType = containerType, containerId = containerId, containerHashId = containerHashId, + jobId = jobId, matrixGroupFlag = matrixGroupFlag, matrixGroupId = matrixGroupId, seq = seq, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt index b3f7e1509b3..4d67a3c728e 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildTaskDao.kt @@ -66,6 +66,7 @@ class PipelineBuildTaskDao { CONTAINER_TYPE, CONTAINER_ID, CONTAINER_HASH_ID, + JOB_ID, TASK_SEQ, TASK_ID, STEP_ID, @@ -90,6 +91,7 @@ class PipelineBuildTaskDao { buildTask.containerType, buildTask.containerId, buildTask.containerHashId, + buildTask.jobId, buildTask.taskSeq, buildTask.taskId, buildTask.stepId, @@ -143,6 +145,7 @@ class PipelineBuildTaskDao { ERROR_CODE, ERROR_MSG, CONTAINER_HASH_ID, + JOB_ID, ATOM_CODE ).also { insert -> taskList.forEach { @@ -178,6 +181,7 @@ class PipelineBuildTaskDao { it.errorCode, it.errorMsg?.coerceAtMaxLength(PIPELINE_TASK_MESSAGE_STRING_LENGTH_MAX), it.containerHashId, + it.jobId, it.atomCode ) } @@ -216,6 +220,7 @@ class PipelineBuildTaskDao { .set(ERROR_MSG, it.errorMsg?.coerceAtMaxLength(PIPELINE_TASK_MESSAGE_STRING_LENGTH_MAX)) .set(ERROR_CODE, it.errorCode) .set(CONTAINER_HASH_ID, it.containerHashId) + .set(JOB_ID, it.jobId) .set(ATOM_CODE, it.atomCode) .where(BUILD_ID.eq(it.buildId).and(TASK_ID.eq(it.taskId)).and(PROJECT_ID.eq(it.projectId))) .execute() @@ -393,6 +398,7 @@ class PipelineBuildTaskDao { containerId = containerId, containerHashId = containerHashId, containerType = containerType, + jobId = jobId, taskSeq = taskSeq, taskId = taskId, stepId = stepId, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt index e5d635cce42..5efb6077216 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt @@ -384,6 +384,7 @@ class PipelineContainerService @Autowired constructor( stageId = stage.id!!, containerId = container.id!!, containerHashId = container.containerHashId ?: "", + jobId = container.jobId, containerType = container.getClassType(), seq = context.containerSeq, status = BuildStatus.QUEUE, @@ -653,6 +654,7 @@ class PipelineContainerService @Autowired constructor( stageId = stage.id!!, containerId = container.id!!, containerHashId = container.containerHashId ?: "", + jobId = container.jobId, containerType = container.getClassType(), seq = context.containerSeq, status = BuildStatus.QUEUE, @@ -930,7 +932,8 @@ class PipelineContainerService @Autowired constructor( subProjectId = null, subBuildId = null, atomCode = atomElement.getAtomCode(), - stepId = atomElement.stepId + stepId = atomElement.stepId, + jobId = container.jobId ) fun setUpTriggerContainer( diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt index bfa2e1c28d9..09dc19b15b8 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt @@ -266,12 +266,14 @@ class EngineVMBuildService @Autowired(required = false) constructor( Triple(envList, contextMap, tm) } + is NormalContainer -> { val tm = transMinuteTimeoutToMills(container.controlOption.jobControlOption.timeout) val contextMap = pipelineContextService.getAllBuildContext(variables).toMutableMap() fillContainerContext(contextMap, null, c.matrixContext, asCodeSettings?.enable) Triple(mutableListOf(), contextMap, tm) } + else -> throw OperationException("vmName($vmName) is an illegal container type: $c") } buildingHeartBeatUtils.addHeartBeat(buildId, vmSeqId, System.currentTimeMillis()) @@ -399,14 +401,17 @@ class EngineVMBuildService @Autowired(required = false) constructor( message = "Job Timeout" ActionType.TERMINATE } + finalBuildStatus.isFailure() -> { message = "Agent failed to start!" ActionType.TERMINATE } + finalBuildStatus.isCancel() -> { message = "Job Cancel" ActionType.TERMINATE } + else -> { message = "Agent startup!" ActionType.START @@ -515,19 +520,23 @@ class EngineVMBuildService @Autowired(required = false) constructor( LOG.info("ENGINE|$buildId|BC_QUEUE|${task.projectId}|j($vmSeqId)|${task.taskId}|${task.taskName}") BuildTask(buildId, vmSeqId, BuildTaskStatus.WAIT, task.executeCount) } + task.taskAtom.isNotBlank() -> { // 排除非构建机的插件任务 继续等待直到它完成 LOG.info("ENGINE|$buildId|BC_NOT_VM|${task.projectId}|j($vmSeqId)|${task.taskId}|${task.taskName}") BuildTask(buildId, vmSeqId, BuildTaskStatus.WAIT, task.executeCount) } + task.taskId == VMUtils.genEndPointTaskId(task.taskSeq) -> { // 全部完成了 pipelineRuntimeService.claimBuildTask(task, userId) // 刷新一下这个结束的任务节点时间 BuildTask(buildId, vmSeqId, BuildTaskStatus.END, task.executeCount) } + pipelineTaskService.isNeedPause(taskId = task.taskId, buildId = task.buildId, taskRecord = task) -> { // 如果插件配置了前置暂停, 暂停期间关闭当前构建机,节约资源。 pipelineTaskService.executePause(taskId = task.taskId, buildId = task.buildId, taskRecord = task) LOG.info("ENGINE|$buildId|BC_PAUSE|${task.projectId}|j($vmSeqId)|${task.taskId}|${task.taskAtom}") pipelineEventDispatcher.dispatch( + // task 前置暂停 PipelineBuildStatusBroadCastEvent( source = "TaskPause-${task.containerId}-${task.buildId}", projectId = task.projectId, @@ -536,7 +545,13 @@ class EngineVMBuildService @Autowired(required = false) constructor( buildId = task.buildId, taskId = task.taskId, stageId = task.stageId, - actionType = ActionType.REFRESH + containerHashId = task.containerHashId, + jobId = task.jobId, + stepId = task.stepId, + atomCode = task.atomCode, + executeCount = task.executeCount, + actionType = ActionType.REFRESH, + buildStatus = task.status.name ) ) BuildTask(buildId, vmSeqId, BuildTaskStatus.END, task.executeCount) @@ -555,6 +570,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( ) BuildTask(buildId, vmSeqId, BuildTaskStatus.WAIT, task.executeCount) } + else -> { val allVariable = buildVariableService.getAllVariable(task.projectId, task.pipelineId, buildId) // 构造扩展变量 @@ -581,9 +597,13 @@ class EngineVMBuildService @Autowired(required = false) constructor( jmxElements.execute(task.taskType) } pipelineEventDispatcher.dispatch( + // market task 启动 PipelineBuildStatusBroadCastEvent( source = "vm-build-claim($vmSeqId)", projectId = task.projectId, pipelineId = task.pipelineId, - userId = task.starter, buildId = buildId, taskId = task.taskId, actionType = ActionType.START + userId = task.starter, buildId = buildId, taskId = task.taskId, actionType = ActionType.START, + containerHashId = task.containerHashId, jobId = task.jobId, stageId = task.stageId, + stepId = task.stepId, atomCode = task.atomCode, executeCount = task.executeCount, + buildStatus = task.status.name ) ) val signToken = UUIDUtil.generate() @@ -767,6 +787,18 @@ class EngineVMBuildService @Autowired(required = false) constructor( source = "completeClaimBuildTask", sendEventFlag = false ) + + pipelineEventDispatcher.dispatch( + // market task 结束 + PipelineBuildStatusBroadCastEvent( + source = "task-end-${result.taskId}", projectId = buildInfo.projectId, + pipelineId = buildInfo.pipelineId, userId = buildInfo.startUser, + buildId = buildId, taskId = result.taskId, actionType = ActionType.END, + containerHashId = task.containerHashId, jobId = task.jobId, stageId = task.stageId, + stepId = task.stepId, atomCode = task.atomCode, executeCount = task.executeCount, + buildStatus = task.status.name + ) + ) } if (buildStatus.isFailure()) { @@ -790,13 +822,6 @@ class EngineVMBuildService @Autowired(required = false) constructor( } } - pipelineEventDispatcher.dispatch( - PipelineBuildStatusBroadCastEvent( - source = "task-end-${result.taskId}", projectId = buildInfo.projectId, - pipelineId = buildInfo.pipelineId, userId = buildInfo.startUser, - buildId = buildId, taskId = result.taskId, actionType = ActionType.END - ) - ) // 发送度量数据 sendElementData(projectId = projectId, buildId = buildId, result = result) @@ -835,6 +860,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( LOG.warn("ENGINE|$buildId|BCT_CANCEL_NOT_FINISH|${buildInfo.projectId}|job#$vmSeqId|$taskId") BuildStatus.CANCELED } + result.success -> { pipelineTaskService.removeRetryCache(buildId, result.taskId) pipelineTaskService.removeFailTaskVar( @@ -843,6 +869,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( ) // 清理插件错误信息(重试插件成功的情况下) BuildStatus.SUCCEED } + else -> { when { pipelineTaskService.isRetryWhenFail( @@ -861,6 +888,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( ) BuildStatus.RETRY } + else -> { // 记录错误插件信息 pipelineTaskService.createFailTaskVar( buildId = buildId, projectId = buildInfo.projectId, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt index 36ad7c2858d..023cb6022ca 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/TaskAtomService.kt @@ -45,11 +45,10 @@ import com.tencent.devops.common.pipeline.pojo.element.market.MarketBuildAtomEle import com.tencent.devops.common.pipeline.pojo.element.market.MarketBuildLessAtomElement import com.tencent.devops.common.service.utils.CommonUtils import com.tencent.devops.common.service.utils.SpringContextUtil -import com.tencent.devops.process.engine.common.VMUtils import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_BACKGROUND_SERVICE_RUNNING_ERROR import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_BACKGROUND_SERVICE_TASK_EXECUTION -import com.tencent.devops.process.engine.control.VmOperateTaskGenerator +import com.tencent.devops.process.engine.common.VMUtils import com.tencent.devops.process.engine.exception.BuildTaskException import com.tencent.devops.process.engine.pojo.PipelineBuildTask import com.tencent.devops.process.engine.pojo.UpdateTaskInfo @@ -88,9 +87,7 @@ class TaskAtomService @Autowired(required = false) constructor( jmxElements.execute(task.taskType) var atomResponse = AtomResponse(BuildStatus.FAILED) try { - if (!VmOperateTaskGenerator.isVmAtom(task)) { - dispatchBroadCastEvent(task, ActionType.START) - } + dispatchBroadCastEvent(task, ActionType.START) // 更新状态 pipelineTaskService.updateTaskStatus( task = task, @@ -145,6 +142,7 @@ class TaskAtomService @Autowired(required = false) constructor( private fun dispatchBroadCastEvent(task: PipelineBuildTask, actionType: ActionType) { pipelineEventDispatcher.dispatch( + // 内置task启动/结束,包含 startVM、stopVM PipelineBuildStatusBroadCastEvent( source = "task-${task.taskId}", projectId = task.projectId, @@ -152,7 +150,13 @@ class TaskAtomService @Autowired(required = false) constructor( userId = task.starter, taskId = task.taskId, buildId = task.buildId, - actionType = actionType + actionType = actionType, + containerHashId = task.containerHashId, + jobId = task.jobId, + stepId = task.stepId, + atomCode = task.atomCode, + executeCount = task.executeCount, + buildStatus = task.status.name ) ) } @@ -264,9 +268,7 @@ class TaskAtomService @Autowired(required = false) constructor( logger.warn("Fail to post the task($task): ${ignored.message}") } - if (!VmOperateTaskGenerator.isVmAtom(task)) { - dispatchBroadCastEvent(task, ActionType.END) - } + dispatchBroadCastEvent(task, ActionType.END) buildLogPrinter.stopLog( buildId = task.buildId, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildEndControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildEndControl.kt index 61864764ea3..ee82d4b55ac 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildEndControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildEndControl.kt @@ -246,13 +246,16 @@ class BuildEndControl @Autowired constructor( JsonUtil.toJson(buildInfo.errorInfoList!!) } else null ), + // build 结束 PipelineBuildStatusBroadCastEvent( source = source, projectId = projectId, pipelineId = pipelineId, userId = userId, buildId = buildId, - actionType = ActionType.END + actionType = ActionType.END, + buildStatus = buildStatus.name, + executeCount = buildInfo.executeCount ), PipelineBuildWebSocketPushEvent( source = "pauseTask", diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt index 297f0d53bad..61e1fda6955 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt @@ -258,7 +258,7 @@ class BuildStartControl @Autowired constructor( ), executeCount = executeCount ) - broadcastStartEvent(buildInfo) + broadcastStartEvent(buildInfo, executeCount) } else { pipelineRuntimeService.updateExecuteCount( projectId = projectId, @@ -450,7 +450,7 @@ class BuildStartControl @Autowired constructor( } } - private fun PipelineBuildStartEvent.broadcastStartEvent(buildInfo: BuildInfo) { + private fun PipelineBuildStartEvent.broadcastStartEvent(buildInfo: BuildInfo, executeCount: Int) { pipelineEventDispatcher.dispatch( // 广播构建即将启动消息给订阅者 PipelineBuildStartBroadCastEvent( @@ -462,14 +462,16 @@ class BuildStartControl @Autowired constructor( startTime = buildInfo.startTime, triggerType = buildInfo.trigger ), - // 根据状态做响应的扩展广播消息给订阅者 + // build 启动,根据状态做响应的扩展广播消息给订阅者 PipelineBuildStatusBroadCastEvent( source = source, projectId = projectId, pipelineId = pipelineId, userId = userId, buildId = buildId, - actionType = ActionType.START + actionType = ActionType.START, + executeCount = executeCount, + buildStatus = buildInfo.status.name ) ) } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt index 3b42cb56da0..5baca314c62 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt @@ -30,6 +30,7 @@ package com.tencent.devops.process.engine.control.command.container.impl import com.tencent.devops.common.api.pojo.ErrorType import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.enums.ActionType +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent import com.tencent.devops.common.expression.ExpressionParseException import com.tencent.devops.common.log.utils.BuildLogPrinter import com.tencent.devops.common.pipeline.enums.BuildStatus @@ -94,6 +95,9 @@ class StartActionTaskContainerCmd( commandContext.buildStatus = BuildStatus.SUCCEED } val waitToDoTask = findTask(commandContext) + if (waitToDoTask != null && TaskUtils.isStartVMTask(waitToDoTask)) { + sendJobStartCallback(commandContext) + } if (waitToDoTask == null) { // 非fast kill的强制终止时到最后无任务,最终状态必定是FAILED val fastKill = FastKillUtils.isFastKillCode(commandContext.event.errorCode) if (!fastKill && actionType.isTerminate() && !commandContext.buildStatus.isFailure()) { @@ -115,6 +119,28 @@ class StartActionTaskContainerCmd( } } + private fun sendJobStartCallback(commandContext: ContainerContext) { + with(commandContext.container) { + pipelineEventDispatcher.dispatch( + // job 启动 + PipelineBuildStatusBroadCastEvent( + source = "StartActionTaskContainerCmd", + projectId = projectId, + pipelineId = pipelineId, + userId = commandContext.event.userId, + buildId = buildId, + actionType = ActionType.START, + stageId = stageId, + containerHashId = containerHashId, + jobId = jobId, + stepId = null, + executeCount = executeCount, + buildStatus = commandContext.buildStatus.name + ) + ) + } + } + private fun setContextBuildStatus(commandContext: ContainerContext) { val container = commandContext.container val runEvenCancelTaskIdKey = ContainerUtils.getContainerRunEvenCancelTaskKey( diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt index 4f6638a8748..286160a45a8 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt @@ -29,6 +29,7 @@ package com.tencent.devops.process.engine.control.command.container.impl import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.enums.ActionType +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent import com.tencent.devops.common.log.utils.BuildLogPrinter import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.redis.RedisOperation @@ -91,15 +92,32 @@ class UpdateStateContainerCmdFinally( if (matrixGroupId.isNullOrBlank()) { sendBackStage(commandContext = commandContext) } else { - pipelineEventDispatcher.dispatch( - commandContext.event.copy( - actionType = ActionType.REFRESH, - containerId = matrixGroupId, - containerHashId = null, - source = commandContext.latestSummary, - reason = "Matrix(${commandContext.container.containerId}) inner container finished" + with(commandContext.event) { + pipelineEventDispatcher.dispatch( + commandContext.event.copy( + actionType = ActionType.REFRESH, + containerId = matrixGroupId, + containerHashId = null, + source = commandContext.latestSummary, + reason = "Matrix(${commandContext.container.containerId}) inner container finished" + ), + // matrix job 结束 + PipelineBuildStatusBroadCastEvent( + source = "StartActionTaskContainerCmd", + projectId = projectId, + pipelineId = pipelineId, + userId = userId, + buildId = buildId, + actionType = actionType, + stageId = stageId, + containerHashId = containerHashId, + jobId = commandContext.container.jobId, + stepId = null, + executeCount = executeCount, + buildStatus = commandContext.buildStatus.name + ) ) - ) + } } } } @@ -211,6 +229,21 @@ class UpdateStateContainerCmdFinally( buildId = buildId, stageId = stageId, actionType = ActionType.REFRESH + ), + // job 结束 + PipelineBuildStatusBroadCastEvent( + source = "StartActionTaskContainerCmd", + projectId = projectId, + pipelineId = pipelineId, + userId = userId, + buildId = buildId, + actionType = actionType, + stageId = stageId, + containerHashId = containerHashId, + jobId = commandContext.container.jobId, + stepId = null, + executeCount = executeCount, + buildStatus = commandContext.buildStatus.name ) ) diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/StartContainerStageCmd.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/StartContainerStageCmd.kt index 2dbf81538a1..87c08e1e5fa 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/StartContainerStageCmd.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/StartContainerStageCmd.kt @@ -110,6 +110,7 @@ class StartContainerStageCmd( private fun sendStageStartCallback(commandContext: StageContext) { pipelineEventDispatcher.dispatch( + // stage 启动 PipelineBuildStatusBroadCastEvent( source = "StartContainerStageCmd", projectId = commandContext.stage.projectId, @@ -117,7 +118,9 @@ class StartContainerStageCmd( userId = commandContext.event.userId, buildId = commandContext.stage.buildId, actionType = ActionType.START, - stageId = commandContext.stage.stageId + stageId = commandContext.stage.stageId, + executeCount = commandContext.executeCount, + buildStatus = commandContext.buildStatus.name ) ) } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/UpdateStateForStageCmdFinally.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/UpdateStateForStageCmdFinally.kt index b687cb9e1f7..8e097d6c107 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/UpdateStateForStageCmdFinally.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/stage/impl/UpdateStateForStageCmdFinally.kt @@ -49,9 +49,9 @@ import com.tencent.devops.process.engine.service.PipelineRuntimeService import com.tencent.devops.process.engine.service.PipelineStageService import com.tencent.devops.process.engine.service.detail.StageBuildDetailService import com.tencent.devops.process.engine.service.record.StageBuildRecordService +import java.time.LocalDateTime import org.slf4j.LoggerFactory import org.springframework.stereotype.Service -import java.time.LocalDateTime /** * 每一个Stage结束后续命令处理 @@ -97,7 +97,7 @@ class UpdateStateForStageCmdFinally( pipelineStageService.pauseStage(stage) } else { nextOrFinish(event, stage, commandContext, false) - sendStageEndCallBack(stage, event) + sendStageEndCallBack(commandContext) } } else if (commandContext.buildStatus.isFinish()) { // 当前Stage结束 if (commandContext.buildStatus == BuildStatus.SKIP) { // 跳过 @@ -106,15 +106,19 @@ class UpdateStateForStageCmdFinally( pipelineStageService.refreshCheckStageStatus(userId = event.userId, buildStage = stage, inOrOut = false) } nextOrFinish(event, stage, commandContext, commandContext.buildStatus.isSuccess()) - sendStageEndCallBack(stage, event) + sendStageEndCallBack(commandContext) } } - private fun sendStageEndCallBack(stage: PipelineBuildStage, event: PipelineBuildStageEvent) { + private fun sendStageEndCallBack(commandContext: StageContext) { + val event = commandContext.event + val stage = commandContext.stage pipelineEventDispatcher.dispatch( + // stage 结束 PipelineBuildStatusBroadCastEvent( source = "UpdateStateForStageCmdFinally", projectId = stage.projectId, pipelineId = stage.pipelineId, - userId = event.userId, buildId = stage.buildId, stageId = stage.stageId, actionType = ActionType.END + userId = event.userId, buildId = stage.buildId, stageId = stage.stageId, actionType = ActionType.END, + buildStatus = commandContext.buildStatus.name, executeCount = stage.executeCount ) ) } @@ -197,9 +201,11 @@ class UpdateStateForStageCmdFinally( event.source == BS_QUALITY_PASS_STAGE -> { qualityCheckOutPass(commandContext) } + event.source == BS_QUALITY_ABORT_STAGE || event.actionType.isEnd() -> { qualityCheckOutFailed(commandContext) } + else -> { val checkStatus = pipelineStageService.checkStageQuality( event = event, @@ -211,11 +217,13 @@ class UpdateStateForStageCmdFinally( BuildStatus.QUALITY_CHECK_PASS -> { qualityCheckOutPass(commandContext) } + BuildStatus.QUALITY_CHECK_WAIT -> { // #5246 如果设置了把关人则卡在运行状态等待审核 qualityCheckOutNeedReview(commandContext) needBreak = true } + else -> { qualityCheckOutFailed(commandContext) } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineTaskPauseListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineTaskPauseListener.kt index 55ef1d0824d..a765f5bca03 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineTaskPauseListener.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineTaskPauseListener.kt @@ -213,15 +213,22 @@ class PipelineTaskPauseListener @Autowired constructor( executeCount = task.executeCount, containerType = containerRecord?.containerType ?: "vmBuild" ), + // pause task 结束 PipelineBuildStatusBroadCastEvent( source = "pauseCancel-${task.containerId}-${task.buildId}", projectId = task.projectId, pipelineId = task.pipelineId, userId = task.starter, buildId = task.buildId, - taskId = null, - stageId = null, - actionType = ActionType.END + taskId = task.taskId, + stageId = task.stageId, + actionType = ActionType.END, + containerHashId = task.containerHashId, + jobId = task.jobId, + stepId = task.stepId, + atomCode = task.atomCode, + executeCount = task.executeCount, + buildStatus = BuildStatus.CANCELED.name ) ) } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/engine/control/CallBackControl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/engine/control/CallBackControl.kt index 0dcca6ff977..4c892cd6fa4 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/engine/control/CallBackControl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/engine/control/CallBackControl.kt @@ -32,7 +32,6 @@ import com.tencent.devops.common.api.exception.RemoteServiceException import com.tencent.devops.common.api.util.Watcher import com.tencent.devops.common.api.util.timestampmilli import com.tencent.devops.common.client.Client -import com.tencent.devops.common.event.enums.ActionType import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent import com.tencent.devops.common.pipeline.Model import com.tencent.devops.common.pipeline.container.Container @@ -49,6 +48,7 @@ import com.tencent.devops.common.pipeline.event.SimpleModel import com.tencent.devops.common.pipeline.event.SimpleStage import com.tencent.devops.common.pipeline.event.SimpleTask import com.tencent.devops.common.pipeline.event.StreamEnabledEvent +import com.tencent.devops.common.pipeline.utils.EventUtils.toEventType import com.tencent.devops.common.service.trace.TraceTag import com.tencent.devops.common.service.utils.LogUtils import com.tencent.devops.common.util.HttpRetryUtils @@ -167,31 +167,10 @@ class CallBackControl @Autowired constructor( val projectId = event.projectId val pipelineId = event.pipelineId val buildId = event.buildId - - val callBackEvent = - if (event.taskId.isNullOrBlank()) { - if (event.stageId.isNullOrBlank()) { - if (event.actionType == ActionType.START) { - CallBackEvent.BUILD_START - } else { - CallBackEvent.BUILD_END - } - } else { - if (event.actionType == ActionType.START) { - CallBackEvent.BUILD_STAGE_START - } else { - CallBackEvent.BUILD_STAGE_END - } - } - } else { - if (event.actionType == ActionType.START) { - CallBackEvent.BUILD_TASK_START - } else if (event.actionType == ActionType.REFRESH) { - CallBackEvent.BUILD_TASK_PAUSE - } else { - CallBackEvent.BUILD_TASK_END - } - } + if (event.atomCode != null && VmOperateTaskGenerator.isVmAtom(event.atomCode!!)) { + return + } + val callBackEvent = event.toEventType() ?: return logger.info("$projectId|$pipelineId|$buildId|${callBackEvent.name}|${event.stageId}|${event.taskId}|callback") val list = mutableListOf() @@ -245,9 +224,11 @@ class CallBackControl @Autowired constructor( is PipelineEvent -> { data.pipelineId } + is BuildEvent -> { data.buildId } + else -> "" } val watcher = Watcher(id = "${it.projectId}|${it.callBackUrl}|${it.events}|$uniqueId") diff --git a/src/backend/ci/core/process/biz-process/src/test/kotlin/com/tencent/devops/process/engine/control/CallBackControlTest.kt b/src/backend/ci/core/process/biz-process/src/test/kotlin/com/tencent/devops/process/engine/control/CallBackControlTest.kt index c0b6cf7813f..b64f6c1c90b 100644 --- a/src/backend/ci/core/process/biz-process/src/test/kotlin/com/tencent/devops/process/engine/control/CallBackControlTest.kt +++ b/src/backend/ci/core/process/biz-process/src/test/kotlin/com/tencent/devops/process/engine/control/CallBackControlTest.kt @@ -111,7 +111,9 @@ class CallBackControlTest : TestBase() { initBuildStartEnd(CallBackEvent.BUILD_START) val buildStartEvent = PipelineBuildStatusBroadCastEvent( source = "vm-build-claim($firstContainerId)", projectId = projectId, pipelineId = pipelineId, - userId = userId, buildId = buildId, actionType = ActionType.START + userId = userId, buildId = buildId, actionType = ActionType.START, stageId = null, + containerHashId = null, jobId = null, taskId = null, stepId = null, executeCount = null, + buildStatus = null ) // val startTime = System.currentTimeMillis() diff --git a/support-files/sql/2002_v1.x/2010_ci_process-update_v1.10_mysql.sql b/support-files/sql/2002_v1.x/2010_ci_process-update_v1.10_mysql.sql index aec14e3d3fc..aa17d81a3c9 100644 --- a/support-files/sql/2002_v1.x/2010_ci_process-update_v1.10_mysql.sql +++ b/support-files/sql/2002_v1.x/2010_ci_process-update_v1.10_mysql.sql @@ -20,6 +20,25 @@ IF NOT EXISTS(SELECT 1 alter table T_AUDIT_RESOURCE ADD INDEX IDX_SEARCH_ID (`RESOURCE_TYPE`, `PROJECT_ID`, `RESOURCE_ID`); END IF; + +IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_PIPELINE_BUILD_TASK' + AND COLUMN_NAME = 'JOB_ID') THEN + ALTER TABLE `T_PIPELINE_BUILD_TASK` + ADD COLUMN `JOB_ID` varchar(128) NULL COMMENT 'job id'; + END IF; + + +IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_PIPELINE_BUILD_CONTAINER' + AND COLUMN_NAME = 'JOB_ID') THEN + ALTER TABLE `T_PIPELINE_BUILD_CONTAINER` + ADD COLUMN `JOB_ID` varchar(128) NULL COMMENT 'job id'; + END IF; COMMIT; END From 44905e2d606415f47ed99255a70dadc5b4d3ca2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Fri, 19 Apr 2024 11:13:45 +0800 Subject: [PATCH 0008/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/container/impl/UpdateStateContainerCmdFinally.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt index 286160a45a8..e9b69e49c51 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/UpdateStateContainerCmdFinally.kt @@ -108,7 +108,7 @@ class UpdateStateContainerCmdFinally( pipelineId = pipelineId, userId = userId, buildId = buildId, - actionType = actionType, + actionType = ActionType.END, stageId = stageId, containerHashId = containerHashId, jobId = commandContext.container.jobId, @@ -237,7 +237,7 @@ class UpdateStateContainerCmdFinally( pipelineId = pipelineId, userId = userId, buildId = buildId, - actionType = actionType, + actionType = ActionType.END, stageId = stageId, containerHashId = containerHashId, jobId = commandContext.container.jobId, From 7acfe6c5fb710c383a8f63b49a515b8c1661a562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Mon, 22 Apr 2024 11:13:38 +0800 Subject: [PATCH 0009/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/builds/MetricsUserService.kt | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index 9d29bfa5323..535e87ab7e8 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -39,8 +39,8 @@ import io.micrometer.core.instrument.Meter import io.micrometer.prometheus.PrometheusMeterRegistry import java.time.Duration import java.time.LocalDateTime -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit +import java.util.LinkedList +import java.util.concurrent.ConcurrentMap import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -56,10 +56,37 @@ class MetricsUserService @Autowired constructor( private val local = MapMaker() .concurrencyLevel(10) .makeMap() - private val executor = Executors.newSingleThreadScheduledExecutor() + val delayArray: LinkedList>> = + LinkedList(MutableList(DELAY_LIMIT) { mutableListOf() }) companion object { val logger: Logger = LoggerFactory.getLogger(MetricsUserService::class.java) + const val DELAY_LIMIT = 5 + } + + class DeleteDelayProcess( + private val delayArray: LinkedList>>, + private val registry: PrometheusMeterRegistry, + private val local: ConcurrentMap + ) : Runnable { + + companion object { + const val SLEEP = 60000L + } + + override fun run() { + while (true) { + delayArray.addFirst(mutableListOf()) + val ready = delayArray.removeLast() + ready.forEach { (key, metrics) -> + metrics.meters.forEach { meter -> + registry.remove(meter) + } + local.remove(key) + } + Thread.sleep(SLEEP) + } + } } fun init() { @@ -67,6 +94,7 @@ class MetricsUserService @Autowired constructor( metricsCacheService.removeFunction = this::metricsRemove metricsCacheService.updateFunction = this::metricsUpdate metricsCacheService.init() + Thread(DeleteDelayProcess(delayArray, registry, local)).start() } fun execute(event: PipelineBuildStatusBroadCastEvent) { @@ -217,12 +245,7 @@ class MetricsUserService @Autowired constructor( logger.debug("metricsRemove|key={}|value={}|metrics={}", key, value, metrics) if (metrics != null) { // 异步删除 - executor.schedule({ - metrics.meters.forEach { meter -> - registry.remove(meter) - } - local.remove(key) - }, 5, TimeUnit.MINUTES) + delayArray.first.add(key to metrics) } } From a0a9223915eaaac16d7efb3e44889dab840a381d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Mon, 22 Apr 2024 11:14:59 +0800 Subject: [PATCH 0010/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/builds/MetricsUserService.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index 535e87ab7e8..aaff0683470 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -76,17 +76,21 @@ class MetricsUserService @Autowired constructor( override fun run() { while (true) { - delayArray.addFirst(mutableListOf()) - val ready = delayArray.removeLast() - ready.forEach { (key, metrics) -> - metrics.meters.forEach { meter -> - registry.remove(meter) - } - local.remove(key) - } + kotlin.runCatching { execute() } Thread.sleep(SLEEP) } } + + private fun execute() { + delayArray.addFirst(mutableListOf()) + val ready = delayArray.removeLast() + ready.forEach { (key, metrics) -> + metrics.meters.forEach { meter -> + registry.remove(meter) + } + local.remove(key) + } + } } fun init() { From 9fd9f0dc7a9213160e2b5644b40213db9e0b1524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Mon, 22 Apr 2024 12:48:04 +0800 Subject: [PATCH 0011/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/builds/MetricsUserService.kt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index aaff0683470..9fe4737107a 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -264,12 +264,14 @@ class MetricsUserService @Autowired constructor( metrics.meters.find { it.id.name == MetricsUserConfig.gaugeBuildStatusKey }?.run { registry.remove(this) } - registerBuildStatusGauge( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - status = status, - description = "build status metrics for $buildId" + metrics.meters.add( + registerBuildStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + status = status, + description = "build status metrics for $buildId" + ) ) metricsCacheService.removeCache(key) } @@ -282,14 +284,16 @@ class MetricsUserService @Autowired constructor( metrics.meters.find { it.id.name == MetricsUserConfig.gaugeBuildStepStatusKey }?.run { registry.remove(this) } - registerBuildStepStatusGauge( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - jobId = jobId!!, - stepId = stepId!!, - status = status, - description = "step status metrics for $buildId|$stepId" + metrics.meters.add( + registerBuildStepStatusGauge( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + jobId = jobId!!, + stepId = stepId!!, + status = status, + description = "step status metrics for $buildId|$stepId" + ) ) metricsCacheService.removeCache(key) } From 5e24e36c8896b7cfd9d009ef9305ae1c43b175c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Tue, 14 May 2024 12:27:59 +0800 Subject: [PATCH 0012/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metrics/config/MetricsUserConfig.kt | 5 + .../service/builds/MetricsCacheService.kt | 28 +++++- .../service/builds/MetricsUserService.kt | 93 ++++++++++++++++--- .../devops/process/pojo/BuildBasicInfo.kt | 5 +- .../engine/service/PipelineRuntimeService.kt | 14 +-- .../builds/PipelineBuildFacadeService.kt | 3 +- .../devops/project/pojo/ProjectProperties.kt | 6 +- 7 files changed, 126 insertions(+), 28 deletions(-) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt index 389c4cc2c5a..63b991024ec 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsUserConfig.kt @@ -32,6 +32,7 @@ import io.micrometer.prometheus.PrometheusConfig import io.micrometer.prometheus.PrometheusMeterRegistry import io.prometheus.client.CollectorRegistry import io.prometheus.client.exporter.common.TextFormat +import org.springframework.beans.factory.annotation.Value import org.springframework.boot.actuate.endpoint.annotation.ReadOperation import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint import org.springframework.boot.actuate.metrics.export.prometheus.TextOutputFormat @@ -49,6 +50,10 @@ class MetricsUserConfig { const val gaugeBuildStepStatusKey = "pipeline_step_status_info" } + + @Value("\${metrics.user.bkBizId:9}") + val bkBizId = "9" + /*注册默认的 prometheusMeterRegistry*/ @Bean fun prometheusMeterRegistry( diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt index e8789df5782..61475b629bb 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt @@ -34,6 +34,7 @@ import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.metrics.pojo.po.MetricsUserPO import groovy.util.ObservableMap import java.beans.PropertyChangeEvent +import java.time.LocalDateTime import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -65,7 +66,7 @@ class MetricsCacheService @Autowired constructor( val logger: Logger = LoggerFactory.getLogger(MetricsCacheService::class.java) } - fun init() { + fun init(checkStatusSet: MutableSet) { cache.addPropertyChangeListener { change: PropertyChangeEvent -> when { change is ObservableMap.PropertyAddedEvent && this::addFunction.isInitialized -> { @@ -85,7 +86,14 @@ class MetricsCacheService @Autowired constructor( } } } - Thread(CacheUpdateProcess(metricsHeartBeatService.getPodName(), cache, redisHashOperation)).start() + Thread( + CacheUpdateProcess( + podHashKey = metricsHeartBeatService.getPodName(), + cache = cache, + checkStatusSet = checkStatusSet, + redisOperation = redisHashOperation + ) + ).start() metricsHeartBeatService.init() } @@ -178,6 +186,7 @@ class MetricsCacheService @Autowired constructor( private class CacheUpdateProcess( private val podHashKey: String, private val cache: ObservableMap, + private val checkStatusSet: MutableSet, private val redisOperation: RedisOperation ) : Runnable { @@ -194,6 +203,9 @@ class MetricsCacheService @Autowired constructor( kotlin.runCatching { executeAdd() }.onFailure { logger.error("metrics execute add process find a error|${it.message}", it) } + kotlin.runCatching { executeCheck() }.onFailure { + logger.error("metrics execute check process find a error|${it.message}", it) + } Thread.sleep(SLEEP) } } @@ -228,5 +240,17 @@ class MetricsCacheService @Autowired constructor( } } } + + private fun executeCheck() { + /* 运行超过一个小时的,加入检查队列 */ + val limit = LocalDateTime.now().plusHours(-1) + val snapshot = cache.keys.toList() + snapshot.parallelStream().forEach { key -> + val value = cache[key] as MetricsUserPO? ?: return@forEach + if (value.buildId !in checkStatusSet && value.startTime < limit) { + checkStatusSet.add(value.buildId) + } + } + } } } diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index 9fe4737107a..09478a9c2a0 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -28,12 +28,16 @@ package com.tencent.devops.metrics.service.builds +import com.github.benmanes.caffeine.cache.Caffeine import com.google.common.collect.MapMaker +import com.tencent.devops.common.client.Client import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStatusBroadCastEvent import com.tencent.devops.common.pipeline.event.CallBackEvent import com.tencent.devops.metrics.config.MetricsUserConfig import com.tencent.devops.metrics.pojo.po.MetricsLocalPO import com.tencent.devops.metrics.pojo.po.MetricsUserPO +import com.tencent.devops.process.api.service.ServiceBuildResource +import com.tencent.devops.project.api.service.ServiceProjectResource import io.micrometer.core.instrument.Gauge import io.micrometer.core.instrument.Meter import io.micrometer.prometheus.PrometheusMeterRegistry @@ -41,27 +45,73 @@ import java.time.Duration import java.time.LocalDateTime import java.util.LinkedList import java.util.concurrent.ConcurrentMap +import java.util.concurrent.TimeUnit import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service @Service class MetricsUserService @Autowired constructor( @Qualifier("userPrometheusMeterRegistry") private val registry: PrometheusMeterRegistry, - private val metricsCacheService: MetricsCacheService + private val metricsCacheService: MetricsCacheService, + private val metricsUserConfig: MetricsUserConfig, + private val client: Client ) { private val local = MapMaker() .concurrencyLevel(10) .makeMap() + + /* 延迟删除队列 */ val delayArray: LinkedList>> = LinkedList(MutableList(DELAY_LIMIT) { mutableListOf() }) + /* 疑似构建状态未同步队列,以buildId为单位 */ + val uncheckArray: MutableSet = mutableSetOf() + + private val buildMetricsCache = Caffeine.newBuilder() + .maximumSize(10000) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build { key -> + kotlin.runCatching { + client.get(ServiceProjectResource::class).get( + englishName = key + ).data?.properties?.buildMetrics + }.getOrNull() ?: false + } + + /* 每10分钟检测一次执行状态 */ + @Scheduled(cron = "0 0/10 * * * ?") + fun checkBuildStatusJob() { + logger.info("=========>> check build status job start <<=========") + // 生成快照 + val unchecks = uncheckArray.toList() + val ready2delete = mutableListOf() + unchecks.chunked(CHUNK_SIZE).forEach { chunk -> + val res = kotlin.runCatching { + client.get(ServiceBuildResource::class).batchServiceBasic( + buildIds = chunk.toSet() + ).data + }.getOrNull() ?: return@forEach + ready2delete.addAll(res.filter { it.value.status?.isFinish() == true }.map { it.key }) + } + + // 生成local快照 + val keys = local.keys.toList() + keys.forEach { key -> + val value = local[key] ?: return@forEach + if (value.data.buildId !in ready2delete) return@forEach + metricsCacheService.removeCache(key) + } + } + companion object { val logger: Logger = LoggerFactory.getLogger(MetricsUserService::class.java) const val DELAY_LIMIT = 5 + const val CHUNK_SIZE = 100 } class DeleteDelayProcess( @@ -97,11 +147,16 @@ class MetricsUserService @Autowired constructor( metricsCacheService.addFunction = this::metricsAdd metricsCacheService.removeFunction = this::metricsRemove metricsCacheService.updateFunction = this::metricsUpdate - metricsCacheService.init() + metricsCacheService.init(uncheckArray) Thread(DeleteDelayProcess(delayArray, registry, local)).start() } + private fun check(event: PipelineBuildStatusBroadCastEvent): Boolean { + return buildMetricsCache.get(event.projectId) ?: false + } + fun execute(event: PipelineBuildStatusBroadCastEvent) { + if (!check(event)) return val date = MetricsUserPO(event) when (date.eventType) { CallBackEvent.BUILD_START -> { @@ -176,7 +231,8 @@ class MetricsUserService @Autowired constructor( } } - fun metricsAdd(key: String, value: MetricsUserPO) { + /* 请勿直接调用该方法 */ + private fun metricsAdd(key: String, value: MetricsUserPO) { local[key] = MetricsLocalPO(value) logger.debug("metricsAdd|key={}|value={}|localSize={}", key, value, local.size) with(value) { @@ -244,7 +300,8 @@ class MetricsUserService @Autowired constructor( } } - fun metricsRemove(key: String, value: MetricsUserPO) { + /* 请勿直接调用该方法 */ + private fun metricsRemove(key: String, value: MetricsUserPO) { val metrics = local[key] logger.debug("metricsRemove|key={}|value={}|metrics={}", key, value, metrics) if (metrics != null) { @@ -253,7 +310,8 @@ class MetricsUserService @Autowired constructor( } } - fun metricsUpdate(key: String, oldValue: MetricsUserPO, newValue: MetricsUserPO) { + /* 请勿直接调用该方法 */ + private fun metricsUpdate(key: String, oldValue: MetricsUserPO, newValue: MetricsUserPO) { val metrics = local[key] logger.debug("metricsUpdate|key={}|oldValue={}|newValue={}|metrics={}", key, oldValue, newValue, metrics) if (metrics != null) { @@ -316,9 +374,10 @@ class MetricsUserService @Autowired constructor( local ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } .tags( - "bk_project_id", projectId, + "projectId", projectId, "pipeline_id", pipelineId, - "build_id", buildId + "build_id", buildId, + "bk_biz_id", metricsUserConfig.bkBizId ) .description(description) .register(registry) @@ -335,10 +394,11 @@ class MetricsUserService @Autowired constructor( MetricsUserConfig.gaugeBuildStatusKey ) { 1 } .tags( - "bk_project_id", projectId, + "projectId", projectId, "pipeline_id", pipelineId, "build_id", buildId, - "status", status + "status", status, + "bk_biz_id", metricsUserConfig.bkBizId ) .description(description) .register(registry) @@ -357,10 +417,11 @@ class MetricsUserService @Autowired constructor( local ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } .tags( - "bk_project_id", projectId, + "projectId", projectId, "pipeline_id", pipelineId, "build_id", buildId, - "job_id", jobId + "job_id", jobId, + "bk_biz_id", metricsUserConfig.bkBizId ) .description(description) .register(registry) @@ -382,12 +443,13 @@ class MetricsUserService @Autowired constructor( local ) { cache -> cache[key]?.let { computeStartTime(it) } ?: 0.0 } .tags( - "bk_project_id", projectId, + "projectId", projectId, "pipeline_id", pipelineId, "build_id", buildId, "job_id", jobId, "step_id", stepId, - "plugin_id", atomCode + "plugin_id", atomCode, + "bk_biz_id", metricsUserConfig.bkBizId ) .description(description) .register(registry) @@ -406,12 +468,13 @@ class MetricsUserService @Autowired constructor( MetricsUserConfig.gaugeBuildStepStatusKey ) { 1 } .tags( - "bk_project_id", projectId, + "projectId", projectId, "pipeline_id", pipelineId, "build_id", buildId, "job_id", jobId, "step_id", stepId, - "status", status + "status", status, + "bk_biz_id", metricsUserConfig.bkBizId ) .description(description) .register(registry) diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildBasicInfo.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildBasicInfo.kt index 23a7fdeffaa..e1d646d8c32 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildBasicInfo.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildBasicInfo.kt @@ -27,6 +27,7 @@ package com.tencent.devops.process.pojo +import com.tencent.devops.common.pipeline.enums.BuildStatus import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "构建模型-基础信息") @@ -38,5 +39,7 @@ data class BuildBasicInfo( @get:Schema(title = "流水线ID", required = true) val pipelineId: String, @get:Schema(title = "流水线版本", required = true) - val pipelineVersion: Int + val pipelineVersion: Int, + @get:Schema(title = "构建状态", required = false) + val status: BuildStatus? ) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt index ceeac66c585..86f26e30011 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt @@ -136,15 +136,15 @@ import com.tencent.devops.process.utils.PIPELINE_NAME import com.tencent.devops.process.utils.PIPELINE_RETRY_COUNT import com.tencent.devops.process.utils.PIPELINE_START_TASK_ID import com.tencent.devops.process.utils.PipelineVarUtil +import java.time.LocalDateTime +import java.util.Date +import java.util.concurrent.TimeUnit import org.jooq.DSLContext import org.jooq.Result import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import java.time.LocalDateTime -import java.util.Date -import java.util.concurrent.TimeUnit /** * 流水线运行时相关的服务 @@ -521,11 +521,11 @@ class PipelineRuntimeService @Autowired constructor( } buildIds.forEach { buildId -> - result[buildId] = BuildBasicInfo(buildId, "", "", 0) + result[buildId] = BuildBasicInfo(buildId, "", "", 0, null) } records.forEach { with(it) { - result[it.buildId] = BuildBasicInfo(buildId, projectId, pipelineId, version) + result[it.buildId] = BuildBasicInfo(buildId, projectId, pipelineId, version, status) } } return result @@ -1964,7 +1964,7 @@ class PipelineRuntimeService @Autowired constructor( buildLogPrinter.addYellowLine( buildId = buildId, message = "[concurrency] Canceling since " + - "a higher priority waiting request for group($groupName) exists", + "a higher priority waiting request for group($groupName) exists", tag = taskId, jobId = task["containerId"]?.toString() ?: "", executeCount = task["executeCount"] as? Int ?: 1 @@ -1974,7 +1974,7 @@ class PipelineRuntimeService @Autowired constructor( buildLogPrinter.addRedLine( buildId = buildId, message = "[concurrency] Canceling all since " + - "a higher priority waiting request for group($groupName) exists", + "a higher priority waiting request for group($groupName) exists", tag = "QueueInterceptor", jobId = "", executeCount = 1 diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt index b51e7fc1216..af4646cbcb0 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt @@ -2178,7 +2178,8 @@ class PipelineBuildFacadeService( buildId = buildId, projectId = build.projectId, pipelineId = build.pipelineId, - pipelineVersion = build.version + pipelineVersion = build.version, + status = build.status ) } diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt index 24e56090789..96eaf525284 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt @@ -44,6 +44,8 @@ data class ProjectProperties( val remotedevManager: String? = null, @get:Schema(title = "是否开启流水线模板管理", required = false) var enableTemplatePermissionManage: Boolean? = null, - @get:Schema(title = "数据标签,创建项目时会为该项目分配指定标签的db") - val dataTag: String? = null + @get:Schema(title = "数据标签,创建项目时会为该项目分配指定标签的db", required = false) + val dataTag: String? = null, + @get:Schema(title = "该项目是否开启流水线可观测数据", required = false) + val buildMetrics: Boolean? = null ) From c1f28d0b10d2971effe184b434ae84d49acb85d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yongyiduan=28=E6=AE=B5=E6=B0=B8=E5=84=84=29?= Date: Wed, 15 May 2024 16:21:26 +0800 Subject: [PATCH 0013/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E6=8C=87=E6=A0=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=20#9860?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tencent/devops/metrics/service/builds/MetricsUserService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index 09478a9c2a0..c5a7679abf0 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -105,6 +105,7 @@ class MetricsUserService @Autowired constructor( val value = local[key] ?: return@forEach if (value.data.buildId !in ready2delete) return@forEach metricsCacheService.removeCache(key) + uncheckArray.remove(value.data.buildId) } } From d7e6f0d04bd5898ec9b9a50aed347767c2f53d10 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Wed, 22 May 2024 12:04:48 +0800 Subject: [PATCH 0014/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent/src/pkg/job/do_build.go | 17 ++++++++++++----- src/agent/agent/src/pkg/job/do_build_win.go | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/agent/agent/src/pkg/job/do_build.go b/src/agent/agent/src/pkg/job/do_build.go index a940638689d..aa775ee774c 100644 --- a/src/agent/agent/src/pkg/job/do_build.go +++ b/src/agent/agent/src/pkg/job/do_build.go @@ -109,17 +109,24 @@ func doBuild( // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) msg, _ := fileutil.GetString(msgFile) - logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + if err != nil { + logs.Errorf("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + } else { + logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + } + // #10362 Worker杀掉当前进程父进程导致Agent误报 + // agent 改动后可能会导致业务执行完成但是进程被杀掉导致流水线错误,所以将错误只是作为额外信息添加 + cmdErrMsg := "" if err != nil { - if len(msg) == 0 { - msg = err.Error() - } + cmdErrMsg = "|" + err.Error() } + success := true if len(msg) == 0 { - msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + cmdErrMsg } else { + msg += cmdErrMsg success = false } diff --git a/src/agent/agent/src/pkg/job/do_build_win.go b/src/agent/agent/src/pkg/job/do_build_win.go index e95e79f7ea8..f6e7690d9ef 100644 --- a/src/agent/agent/src/pkg/job/do_build_win.go +++ b/src/agent/agent/src/pkg/job/do_build_win.go @@ -114,17 +114,24 @@ func doBuild( // #5806 从b-xxxx_build_msg.log 读取错误信息,此信息可由worker-agent.jar写入,用于当异常时能够将信息上报给服务器 msgFile := getWorkerErrorMsgFile(buildInfo.BuildId, buildInfo.VmSeqId) msg, _ := fileutil.GetString(msgFile) - logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + if err != nil { + logs.Errorf("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + } else { + logs.Infof("build[%s] pid[%d] finish, state=%v err=%v, msg=%s", buildInfo.BuildId, pid, cmd.ProcessState, err, msg) + } + // #10362 Worker杀掉当前进程父进程导致Agent误报 + // agent 改动后可能会导致业务执行完成但是进程被杀掉导致流水线错误,所以将错误只是作为额外信息添加 + cmdErrMsg := "" if err != nil { - if len(msg) == 0 { - msg = err.Error() - } + cmdErrMsg = "|" + err.Error() } + success := true if len(msg) == 0 { - msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + msg = i18n.Localize("WorkerExit", map[string]interface{}{"pid": pid}) + cmdErrMsg } else { + msg += cmdErrMsg success = false } From f31c26b496449d39d5770647ca8ccd083d49bf47 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Thu, 23 May 2024 14:04:25 +0800 Subject: [PATCH 0015/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/pipeline/pojo/element/Element.kt | 3 +- .../process/dao/record/BuildRecordTaskDao.kt | 22 +++++ .../engine/service/PipelineRuntimeService.kt | 32 +++++++ .../service/record/TaskBuildRecordService.kt | 19 ++++ .../service/SubPipelineStatusService.kt | 92 +++++++++++++++++-- .../run/PipelineBuildStartListener.kt | 3 + 6 files changed, 162 insertions(+), 9 deletions(-) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/Element.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/Element.kt index bc93bdd3912..8ad63964814 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/Element.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/element/Element.kt @@ -146,7 +146,8 @@ abstract class Element( open var progressRate: Double? = null, override var template: String? = null, override var ref: String? = null, - override var variables: Map? = null + override var variables: Map? = null, + var asyncStatus: String? = null ) : IModelTemplate { open fun getAtomCode() = getClassType() diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt index 277aceea870..82ed91fff33 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt @@ -298,6 +298,28 @@ class BuildRecordTaskDao { } } + fun updateAsyncStatus( + dslContext: DSLContext, + projectId: String, + pipelineId: String, + buildId: String, + taskId: String, + executeCount: Int, + asyncStatus: String + ) { + with(TPipelineBuildRecordTask.T_PIPELINE_BUILD_RECORD_TASK) { + dslContext.update(this) + .set(ASYNC_STATUS, asyncStatus) + .where( + BUILD_ID.eq(buildId) + .and(PROJECT_ID.eq(projectId)) + .and(PIPELINE_ID.eq(pipelineId)) + .and(TASK_ID.eq(taskId)) + .and(EXECUTE_COUNT.eq(executeCount)) + ).execute() + } + } + class BuildRecordTaskJooqMapper : RecordMapper { override fun map(record: TPipelineBuildRecordTaskRecord?): BuildRecordTask? { return record?.run { diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt index 5a608921e74..bea3b54e2a1 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt @@ -2010,4 +2010,36 @@ class PipelineRuntimeService @Autowired constructor( redisLock.unlock() } } + + fun updateAsyncStatus( + projectId: String, + pipelineId: String, + buildId: String, + taskId: String, + executeCount: Int, + asyncStatus: String + ) { + taskBuildRecordService.updateAsyncStatus( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + asyncStatus = asyncStatus + ) + } + + fun getBuildVariableService( + projectId: String, + pipelineId: String, + buildId: String, + keys: Set + ): Map { + return buildVariableService.getAllVariable( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + keys = keys + ) + } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt index bb8d2513b58..92334b17a11 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/TaskBuildRecordService.kt @@ -522,6 +522,25 @@ class TaskBuildRecordService( ) } + fun updateAsyncStatus( + projectId: String, + pipelineId: String, + buildId: String, + taskId: String, + executeCount: Int, + asyncStatus: String + ) { + recordTaskDao.updateAsyncStatus( + dslContext = dslContext, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId, + executeCount = executeCount, + asyncStatus = asyncStatus + ) + } + companion object { private val logger = LoggerFactory.getLogger(TaskBuildRecordService::class.java) private const val TASK_PAUSE_TAG_VAR = "taskPause" diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 261ec311afa..0f03caa0faa 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -33,11 +33,18 @@ import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.enums.StartType +import com.tencent.devops.common.pipeline.pojo.element.trigger.enums.CodeEventType import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_NO_BUILD_RECORD_FOR_CORRESPONDING_SUB_PIPELINE +import com.tencent.devops.process.engine.pojo.event.PipelineBuildStartEvent import com.tencent.devops.process.engine.service.PipelineRuntimeService import com.tencent.devops.process.pojo.pipeline.SubPipelineStatus +import com.tencent.devops.process.utils.PIPELINE_START_PARENT_BUILD_ID +import com.tencent.devops.process.utils.PIPELINE_START_PARENT_BUILD_TASK_ID +import com.tencent.devops.process.utils.PIPELINE_START_PARENT_PIPELINE_ID +import com.tencent.devops.process.utils.PIPELINE_START_PARENT_PROJECT_ID +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -52,6 +59,7 @@ class SubPipelineStatusService @Autowired constructor( private const val SUBPIPELINE_STATUS_START_EXPIRED = 54000L // 子流水线完成过期时间10分钟 private const val SUBPIPELINE_STATUS_FINISH_EXPIRED = 600L + private val logger = LoggerFactory.getLogger(SubPipelineStatusService::class.java) } fun onStart(buildId: String) { @@ -64,19 +72,87 @@ class SubPipelineStatusService @Autowired constructor( ) } + /** + * 异步启动子流水线 + */ + fun onAsyncStart(event: PipelineBuildStartEvent) { + with(event) { + try { + updateParentPipelineTaskStatus( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + asyncStatus = "start" + ) + } catch (ignored: Exception) { + logger.warn("fail to update parent pipeline task status", ignored) + } + } + } + fun onFinish(event: PipelineBuildFinishBroadCastEvent) { with(event) { - // 不是子流水线启动或者子流水线是异步启动的,不需要缓存状态 - if (triggerType != StartType.PIPELINE.name || - redisOperation.get(getSubPipelineStatusKey(buildId)) == null - ) { + // 不是流水线启动 + if (triggerType != StartType.PIPELINE.name) { return } + updateParentPipelineTaskStatus( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + asyncStatus = "finish" + ) + // 子流水线是异步启动的,不需要缓存状态 + if (redisOperation.get(getSubPipelineStatusKey(buildId)) != null) { + redisOperation.set( + key = getSubPipelineStatusKey(buildId), + value = JsonUtil.toJson(getSubPipelineStatusFromDB(event.projectId, buildId)), + expiredInSecond = SUBPIPELINE_STATUS_FINISH_EXPIRED + ) + } + } + } - redisOperation.set( - key = getSubPipelineStatusKey(buildId), - value = JsonUtil.toJson(getSubPipelineStatusFromDB(event.projectId, buildId)), - expiredInSecond = SUBPIPELINE_STATUS_FINISH_EXPIRED + private fun updateParentPipelineTaskStatus( + projectId: String, + pipelineId: String, + buildId: String, + asyncStatus: String + ) { + // 父流水线相关信息 + val keys = setOf( + PIPELINE_START_PARENT_PROJECT_ID, + PIPELINE_START_PARENT_PIPELINE_ID, + PIPELINE_START_PARENT_BUILD_ID, + PIPELINE_START_PARENT_BUILD_TASK_ID + ) + val buildVariables = pipelineRuntimeService.getBuildVariableService( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + keys = keys + ).filter { it.value.isNotBlank() } + if (buildVariables.size != keys.size) { + logger.warn( + "The parent pipeline status cannot be updated, " + + "because an abnormal variable exists[$buildVariables]" + ) + return + } + pipelineRuntimeService.getBuildInfo( + projectId = buildVariables[PIPELINE_START_PARENT_PROJECT_ID]!!, + pipelineId = buildVariables[PIPELINE_START_PARENT_PIPELINE_ID]!!, + buildId = buildVariables[PIPELINE_START_PARENT_BUILD_ID]!! + )?.let { + logger.info("start update parent pipeline asyncStatus|${it.projectId}|${it.pipelineId}|" + + "${it.buildId}|${it.executeCount}|") + pipelineRuntimeService.updateAsyncStatus( + projectId = it.projectId, + pipelineId = it.pipelineId, + buildId = it.buildId, + taskId = buildVariables[PIPELINE_START_PARENT_BUILD_TASK_ID]!!, + executeCount = it.executeCount ?: 1, + asyncStatus = asyncStatus ) } } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt index 7aa373e2a59..d844a48b12f 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt @@ -31,6 +31,7 @@ import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatch import com.tencent.devops.common.event.listener.pipeline.BaseListener import com.tencent.devops.process.engine.control.BuildStartControl import com.tencent.devops.process.engine.pojo.event.PipelineBuildStartEvent +import com.tencent.devops.process.service.SubPipelineStatusService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -42,10 +43,12 @@ import org.springframework.stereotype.Component @Component class PipelineBuildStartListener @Autowired constructor( private val buildControl: BuildStartControl, + private val subPipelineStatusService: SubPipelineStatusService, pipelineEventDispatcher: PipelineEventDispatcher ) : BaseListener(pipelineEventDispatcher) { override fun run(event: PipelineBuildStartEvent) { buildControl.handle(event) + subPipelineStatusService.onAsyncStart(event) } } From 5f866fcc7dc27f301e834aa4277ff3479e533c8b Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Mon, 27 May 2024 15:14:38 +0800 Subject: [PATCH 0016/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process/service/SubPipelineStatusService.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 0f03caa0faa..b627a29ac28 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -131,13 +131,16 @@ class SubPipelineStatusService @Autowired constructor( pipelineId = pipelineId, buildId = buildId, keys = keys - ).filter { it.value.isNotBlank() } - if (buildVariables.size != keys.size) { - logger.warn( - "The parent pipeline status cannot be updated, " + - "because an abnormal variable exists[$buildVariables]" - ) - return + ) + keys.forEach { + // 存在异常值,则直接返回 + if (buildVariables[it].isNullOrBlank()){ + logger.warn( + "The parent pipeline status cannot be updated, " + + "because an abnormal variable exists[$buildVariables]" + ) + return + } } pipelineRuntimeService.getBuildInfo( projectId = buildVariables[PIPELINE_START_PARENT_PROJECT_ID]!!, From 5b39caa48d136ed22b0da4302b667272853a6657 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Mon, 27 May 2024 16:38:14 +0800 Subject: [PATCH 0017/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/pipeline/record/BuildRecordTask.kt | 4 +++- .../process/dao/record/BuildRecordTaskDao.kt | 14 ++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt index 79e5e2bbb1e..4ad156dbb90 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/record/BuildRecordTask.kt @@ -74,7 +74,9 @@ data class BuildRecordTask( @get:Schema(title = "结束时间", required = true) var endTime: LocalDateTime? = null, @get:Schema(title = "业务时间戳集合", required = true) - var timestamps: Map + var timestamps: Map, + @get:Schema(title = "异步执行状态", required = true) + var asyncStatus: String? = null ) { companion object { fun MutableList.addRecords( diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt index 82ed91fff33..999b595e561 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt @@ -41,6 +41,7 @@ import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask import org.jooq.Condition import org.jooq.DSLContext import org.jooq.Record18 +import org.jooq.Record19 import org.jooq.RecordMapper import org.jooq.impl.DSL import org.jooq.util.mysql.MySQLDSL @@ -211,7 +212,7 @@ class BuildRecordTaskDao { val result = dslContext.select( BUILD_ID, PROJECT_ID, PIPELINE_ID, RESOURCE_VERSION, STAGE_ID, CONTAINER_ID, TASK_ID, TASK_SEQ, EXECUTE_COUNT, TASK_VAR, CLASS_TYPE, ATOM_CODE, STATUS, ORIGIN_CLASS_TYPE, - START_TIME, END_TIME, TIMESTAMPS, POST_INFO + START_TIME, END_TIME, TIMESTAMPS, POST_INFO, ASYNC_STATUS ).from(this).join(max).on( TASK_ID.eq(max.field(KEY_TASK_ID, String::class.java)) .and(EXECUTE_COUNT.eq(max.field(KEY_EXECUTE_COUNT, Int::class.java))) @@ -238,7 +239,7 @@ class BuildRecordTaskDao { val result = dslContext.select( BUILD_ID, PROJECT_ID, PIPELINE_ID, RESOURCE_VERSION, STAGE_ID, CONTAINER_ID, TASK_ID, TASK_SEQ, EXECUTE_COUNT, TASK_VAR, CLASS_TYPE, ATOM_CODE, STATUS, ORIGIN_CLASS_TYPE, - START_TIME, END_TIME, TIMESTAMPS, POST_INFO + START_TIME, END_TIME, TIMESTAMPS, POST_INFO, ASYNC_STATUS ).from(this).where(conditions).orderBy(TASK_SEQ.asc()).fetch() return result.map { record -> generateBuildRecordTask(record) @@ -247,9 +248,9 @@ class BuildRecordTaskDao { } private fun TPipelineBuildRecordTask.generateBuildRecordTask( - record: Record18 + record: Record19 ) = BuildRecordTask( buildId = record[BUILD_ID], @@ -275,7 +276,8 @@ class BuildRecordTaskDao { } ?: mapOf(), elementPostInfo = record[POST_INFO]?.let { JsonUtil.to(it, object : TypeReference() {}) - } + }, + asyncStatus = record[ASYNC_STATUS] ) fun getRecord( From a0d17bd586a1c2b81c65b63897faefad0a63e260 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Mon, 27 May 2024 17:09:00 +0800 Subject: [PATCH 0018/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tencent/devops/process/dao/record/BuildRecordTaskDao.kt | 1 - .../tencent/devops/process/service/SubPipelineStatusService.kt | 3 +-- .../process/service/record/PipelineRecordModelService.kt | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt index 999b595e561..7104801792a 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/record/BuildRecordTaskDao.kt @@ -40,7 +40,6 @@ import com.tencent.devops.process.pojo.KEY_TASK_ID import com.tencent.devops.process.pojo.pipeline.record.BuildRecordTask import org.jooq.Condition import org.jooq.DSLContext -import org.jooq.Record18 import org.jooq.Record19 import org.jooq.RecordMapper import org.jooq.impl.DSL diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index b627a29ac28..0637372e5f2 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -33,7 +33,6 @@ import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.enums.StartType -import com.tencent.devops.common.pipeline.pojo.element.trigger.enums.CodeEventType import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_NO_BUILD_RECORD_FOR_CORRESPONDING_SUB_PIPELINE @@ -134,7 +133,7 @@ class SubPipelineStatusService @Autowired constructor( ) keys.forEach { // 存在异常值,则直接返回 - if (buildVariables[it].isNullOrBlank()){ + if (buildVariables[it].isNullOrBlank()) { logger.warn( "The parent pipeline status cannot be updated, " + "because an abnormal variable exists[$buildVariables]" diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt index dc87346d51a..992279e1275 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/record/PipelineRecordModelService.kt @@ -330,6 +330,7 @@ class PipelineRecordModelService @Autowired constructor( taskVarMap[Element::id.name] = taskId taskVarMap[Element::status.name] = containerRecordTask.status ?: "" taskVarMap[Element::executeCount.name] = containerRecordTask.executeCount + taskVarMap[Element::asyncStatus.name] = containerRecordTask.asyncStatus ?: "" val elementPostInfo = containerRecordTask.elementPostInfo if (elementPostInfo != null) { // 生成post类型task的变量模型 From 74b9dc6609230c8a0345ecb7c4a346df9739bc65 Mon Sep 17 00:00:00 2001 From: stubenhuang Date: Mon, 27 May 2024 20:06:08 +0800 Subject: [PATCH 0019/1143] =?UTF-8?q?feat:=20=E8=AE=A9worker=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9C=A8JDK17=E4=B8=AD=E8=BF=90=E8=A1=8C=20#10412?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tencent/devops/common/api/util/EnumUtil.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt index f30a88eb446..d60e44c923a 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt @@ -27,10 +27,9 @@ package com.tencent.devops.common.api.util -import sun.reflect.ConstructorAccessor -import sun.reflect.FieldAccessor import sun.reflect.ReflectionFactory import java.lang.reflect.AccessibleObject +import java.lang.reflect.Constructor import java.lang.reflect.Field import java.lang.reflect.Modifier import kotlin.reflect.full.isSubclassOf @@ -131,8 +130,7 @@ object EnumUtil { var modifiers: Int = modifiersField.getInt(field) modifiers = modifiers and Modifier.FINAL.inv() modifiersField.setInt(field, modifiers) - val fieldAccessor: FieldAccessor = reflectionFactory.newFieldAccessor(field, false) - fieldAccessor.set(target, value) + field.set(target, value) } @Throws(NoSuchFieldException::class, IllegalAccessException::class) @@ -155,7 +153,7 @@ object EnumUtil { inline fun getConstructorAccessor( enumClass: Class, additionalParameterTypes: Array> - ): ConstructorAccessor? { + ): Constructor? { val parameterTypes = arrayOfNulls?>(additionalParameterTypes.size + 2) parameterTypes[0] = String::class.java // enum class first field: field name parameterTypes[1] = Int::class.javaPrimitiveType // enum class second field: ordinal @@ -163,14 +161,17 @@ object EnumUtil { enumClass.declaredConstructors.forEach { constructor -> if (compareParameterType(constructor.parameterTypes, parameterTypes)) { try { - return reflectionFactory.newConstructorAccessor(constructor) + constructor.isAccessible = true + return constructor } catch (ignored: IllegalArgumentException) { // skip illegal argument try next one } } } - return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(*parameterTypes)) + val constructor = enumClass.getDeclaredConstructor(*parameterTypes) + constructor.isAccessible = true + return constructor } fun compareParameterType(constructorParameterType: Array>, parameterTypes: Array?>): Boolean { @@ -181,7 +182,8 @@ object EnumUtil { if (constructorParameterType[i] !== parameterTypes[i]) { if (constructorParameterType[i].isPrimitive && parameterTypes[i]!!.isPrimitive) { if (constructorParameterType[i].kotlin.javaPrimitiveType - !== parameterTypes[i]!!.kotlin.javaPrimitiveType) { + !== parameterTypes[i]!!.kotlin.javaPrimitiveType + ) { return false } } From aec3d95a7c3c8783e4af180a6571775983065e7b Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Tue, 28 May 2024 11:42:50 +0800 Subject: [PATCH 0020/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SubPipelineStatusService.kt | 20 ++++++-- .../run/PipelineBuildStartListener.kt | 3 -- .../start/SubPipelineBuildStartListener.kt | 46 +++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 0637372e5f2..35470ba857c 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -30,13 +30,16 @@ package com.tencent.devops.process.service import com.tencent.devops.common.api.pojo.ErrorCode import com.tencent.devops.common.api.pojo.ErrorType import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.enums.StartType import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.common.websocket.enum.RefreshType import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_NO_BUILD_RECORD_FOR_CORRESPONDING_SUB_PIPELINE -import com.tencent.devops.process.engine.pojo.event.PipelineBuildStartEvent +import com.tencent.devops.process.engine.pojo.event.PipelineBuildWebSocketPushEvent import com.tencent.devops.process.engine.service.PipelineRuntimeService import com.tencent.devops.process.pojo.pipeline.SubPipelineStatus import com.tencent.devops.process.utils.PIPELINE_START_PARENT_BUILD_ID @@ -50,7 +53,8 @@ import org.springframework.stereotype.Service @Service class SubPipelineStatusService @Autowired constructor( private val pipelineRuntimeService: PipelineRuntimeService, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + private val pipelineEventDispatcher: PipelineEventDispatcher ) { companion object { @@ -74,7 +78,7 @@ class SubPipelineStatusService @Autowired constructor( /** * 异步启动子流水线 */ - fun onAsyncStart(event: PipelineBuildStartEvent) { + fun onAsyncStart(event: PipelineBuildStartBroadCastEvent) { with(event) { try { updateParentPipelineTaskStatus( @@ -156,6 +160,16 @@ class SubPipelineStatusService @Autowired constructor( executeCount = it.executeCount ?: 1, asyncStatus = asyncStatus ) + pipelineEventDispatcher.dispatch( + PipelineBuildWebSocketPushEvent( + source = "updateTaskStatus", + projectId = projectId, + pipelineId = pipelineId, + userId = it.startUser, + buildId = buildId, + refreshTypes = RefreshType.DETAIL.binary + ) + ) } } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt index d844a48b12f..7aa373e2a59 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/PipelineBuildStartListener.kt @@ -31,7 +31,6 @@ import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatch import com.tencent.devops.common.event.listener.pipeline.BaseListener import com.tencent.devops.process.engine.control.BuildStartControl import com.tencent.devops.process.engine.pojo.event.PipelineBuildStartEvent -import com.tencent.devops.process.service.SubPipelineStatusService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -43,12 +42,10 @@ import org.springframework.stereotype.Component @Component class PipelineBuildStartListener @Autowired constructor( private val buildControl: BuildStartControl, - private val subPipelineStatusService: SubPipelineStatusService, pipelineEventDispatcher: PipelineEventDispatcher ) : BaseListener(pipelineEventDispatcher) { override fun run(event: PipelineBuildStartEvent) { buildControl.handle(event) - subPipelineStatusService.onAsyncStart(event) } } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt new file mode 100644 index 00000000000..5d2d158b792 --- /dev/null +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.process.engine.listener.run.start + +import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher +import com.tencent.devops.common.event.listener.pipeline.BaseListener +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent +import com.tencent.devops.process.service.SubPipelineStatusService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +class SubPipelineBuildStartListener @Autowired constructor( + private val subPipelineStatusService: SubPipelineStatusService, + pipelineEventDispatcher: PipelineEventDispatcher +) : BaseListener(pipelineEventDispatcher) { + + override fun run(event: PipelineBuildStartBroadCastEvent) { + subPipelineStatusService.onAsyncStart(event) + } +} From 9a0bd1f264f07751aa6dff8385c361fd972fea9d Mon Sep 17 00:00:00 2001 From: stubenhuang Date: Tue, 28 May 2024 12:18:08 +0800 Subject: [PATCH 0021/1143] =?UTF-8?q?feat:=20=E8=AE=A9worker=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9C=A8JDK17=E4=B8=AD=E8=BF=90=E8=A1=8C=20#10412?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ci/core/common/common-api/build.gradle.kts | 1 + .../tencent/devops/common/api/util/EnumUtil.kt | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/backend/ci/core/common/common-api/build.gradle.kts b/src/backend/ci/core/common/common-api/build.gradle.kts index da14be40068..5bb6780e936 100644 --- a/src/backend/ci/core/common/common-api/build.gradle.kts +++ b/src/backend/ci/core/common/common-api/build.gradle.kts @@ -53,4 +53,5 @@ dependencies { api("com.github.ben-manes.caffeine:caffeine") api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") api("com.jakewharton:disklrucache") + api("org.apache.commons:commons-lang3") } diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt index d60e44c923a..df20a35ee57 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt @@ -27,6 +27,7 @@ package com.tencent.devops.common.api.util +import org.apache.commons.lang3.reflect.MethodUtils import sun.reflect.ReflectionFactory import java.lang.reflect.AccessibleObject import java.lang.reflect.Constructor @@ -130,7 +131,15 @@ object EnumUtil { var modifiers: Int = modifiersField.getInt(field) modifiers = modifiers and Modifier.FINAL.inv() modifiersField.setInt(field, modifiers) - field.set(target, value) + + val fieldAccessor = MethodUtils.invokeMethod(field, true, "acquireFieldAccessor", false) + MethodUtils.invokeMethod( + fieldAccessor, + true, + "set", + arrayOf(target, value), + arrayOf>(Object::class.java, Object::class.java) + ) } @Throws(NoSuchFieldException::class, IllegalAccessException::class) @@ -150,7 +159,7 @@ object EnumUtil { } @Throws(NoSuchMethodException::class) - inline fun getConstructorAccessor( + inline fun getConstructor( enumClass: Class, additionalParameterTypes: Array> ): Constructor? { @@ -204,7 +213,10 @@ object EnumUtil { params[0] = value params[1] = Integer.valueOf(ordinal) System.arraycopy(additionalValues, 0, params, 2, additionalValues.size) - return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes)!!.newInstance(params)) + val constructor = getConstructor(enumClass, additionalTypes) + val constructorAccessor = MethodUtils.invokeMethod(constructor, true, "acquireConstructorAccessor") + val instance = MethodUtils.invokeMethod(constructorAccessor, true, "newInstance", params) + return enumClass.cast(instance) } val reflectionFactory: ReflectionFactory = ReflectionFactory.getReflectionFactory() From 977c473e2c715c7617763ce79841a8140a88ff5c Mon Sep 17 00:00:00 2001 From: v_yjjiaoyu <1981190393@qq.com> Date: Tue, 28 May 2024 14:24:39 +0800 Subject: [PATCH 0022/1143] =?UTF-8?q?bug:=20=E5=9B=9E=E6=94=B6=E7=AB=99?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E4=B8=8D=E5=8F=AF=E7=94=A8=20#8440=20#=20Rev?= =?UTF-8?q?iewed,=20transaction=20id:=208795?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pipelineList/PipelineTableView.vue | 5 +++++ .../views/PipelineList/PipelineManageList.vue | 22 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/frontend/devops-pipeline/src/components/pipelineList/PipelineTableView.vue b/src/frontend/devops-pipeline/src/components/pipelineList/PipelineTableView.vue index 5e746e82578..057a80210fe 100755 --- a/src/frontend/devops-pipeline/src/components/pipelineList/PipelineTableView.vue +++ b/src/frontend/devops-pipeline/src/components/pipelineList/PipelineTableView.vue @@ -342,6 +342,10 @@ filterParams: { type: Object, default: () => ({}) + }, + filterByPipelineName: { + type: String, + default: '' } }, data () { @@ -608,6 +612,7 @@ page: this.pagination.current, pageSize: this.pagination.limit, viewId: this.$route.params.viewId, + filterByPipelineName: this.filterByPipelineName || null, ...this.filterParams, ...query }) diff --git a/src/frontend/devops-pipeline/src/views/PipelineList/PipelineManageList.vue b/src/frontend/devops-pipeline/src/views/PipelineList/PipelineManageList.vue index f1fc5da26d8..27ab43de5f6 100644 --- a/src/frontend/devops-pipeline/src/views/PipelineList/PipelineManageList.vue +++ b/src/frontend/devops-pipeline/src/views/PipelineList/PipelineManageList.vue @@ -2,7 +2,15 @@
{{$t('restore.recycleBin')}}
- +
+ + diff --git a/src/frontend/devops-manage/src/components/user-group/svg/user-active.svg b/src/frontend/devops-manage/src/components/user-group/svg/user-active.svg new file mode 100644 index 00000000000..13e6fc235da --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/svg/user-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-manage/src/css/svg/close.svg b/src/frontend/devops-manage/src/css/svg/close.svg new file mode 100644 index 00000000000..9910df9f4d7 --- /dev/null +++ b/src/frontend/devops-manage/src/css/svg/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-manage/src/router/index.ts b/src/frontend/devops-manage/src/router/index.ts index 30a36c2d4e2..04fe97d5b8f 100644 --- a/src/frontend/devops-manage/src/router/index.ts +++ b/src/frontend/devops-manage/src/router/index.ts @@ -12,6 +12,8 @@ const ShowProject = () => import(/* webpackChunkName: "ShowProject" */ '../views // 用户组管理 const UserGroup = () => import(/* webpackChunkName: "UserGroup" */ '../views/manage/group/group-entry.vue'); const ExpandManage = () => import(/* webpackChunkName: "ExpandManage" */ '../views/manage/expand/expand-manage.vue'); +// 授权管理 +const Permission = () => import(/* webpackChunkName: "ExpandManage" */ '../views/manage/permission/permission-manage.vue'); const router = createRouter({ history: createWebHistory('manage'), @@ -55,6 +57,11 @@ const router = createRouter({ name: 'group', component: UserGroup, }, + { + path: 'permission', + name: 'permission', + component: Permission, + }, { path: 'expand', name: 'expand', diff --git a/src/frontend/devops-manage/src/views/manage/manage-entry.vue b/src/frontend/devops-manage/src/views/manage/manage-entry.vue index 1a0e72062de..d3750cb21cd 100644 --- a/src/frontend/devops-manage/src/views/manage/manage-entry.vue +++ b/src/frontend/devops-manage/src/views/manage/manage-entry.vue @@ -23,6 +23,10 @@ const manageTabs = ref([ title: t('用户管理'), name: 'group', }, + { + title: t('授权管理'), + name: 'permission', + }, // { // title: t('微扩展管理'), // name: 'expand', diff --git a/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue b/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue new file mode 100644 index 00000000000..b7716003018 --- /dev/null +++ b/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue @@ -0,0 +1,494 @@ + + + + + \ No newline at end of file From 4ea20bebaa7fb2e943f6549cf4244b9ab9b656c8 Mon Sep 17 00:00:00 2001 From: v_yjjiaoyu <1981190393@qq.com> Date: Tue, 4 Jun 2024 15:49:44 +0800 Subject: [PATCH 0044/1143] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E6=88=90?= =?UTF-8?q?=E5=91=98=E7=AE=A1=E7=90=86=20#9620=20#=20Reviewed,=20transacti?= =?UTF-8?q?on=20id:=209239?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../children/permission-manage/manage-all.vue | 209 +++++++++++++++++- .../manage/permission/permission-manage.vue | 1 - 2 files changed, 204 insertions(+), 6 deletions(-) diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue index 985ba5862ec..b32711e1c59 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue @@ -51,10 +51,23 @@ diff --git a/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue b/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue index b7716003018..4586c7a5a03 100644 --- a/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue +++ b/src/frontend/devops-manage/src/views/manage/permission/permission-manage.vue @@ -33,7 +33,6 @@ :columns="columns" height="100%" show-overflow-tooltip - v-bkloading="{ isLoading }" :scroll-loading="isScrollLoading" @select-all="handleSelectAll" @selection-change="handleSelectionChange" From cc5d882df687d1d86d3bfdd4b618d30b7f5dd738 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Wed, 5 Jun 2024 09:12:23 +0800 Subject: [PATCH 0045/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker 特权模式 --- .../process/yaml/v2/parsers/template/YamlObjects.kt | 3 ++- .../pipeline/type/agent/ThirdPartyAgentDockerInfo.kt | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v2/parsers/template/YamlObjects.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v2/parsers/template/YamlObjects.kt index 84035513ccb..41bb828a201 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v2/parsers/template/YamlObjects.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v2/parsers/template/YamlObjects.kt @@ -258,7 +258,8 @@ object YamlObjects { null } else { transValue>(fromPath, "mounts", optionsMap["mounts"]) - } + }, + privileged = getNullValue("privileged", optionsMap)?.toBoolean() ) }, imagePullPolicy = getNullValue(key = "image-pull-policy", map = containerMap) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDockerInfo.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDockerInfo.kt index b04bcbdd286..82802acc32b 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDockerInfo.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDockerInfo.kt @@ -32,6 +32,11 @@ fun ThirdPartyAgentDockerInfo.replaceField(variables: Map) { } else { EnvUtils.parseEnv(options?.gpus, variables) } + options?.privileged = if (options?.privileged == null) { + null + } else { + EnvUtils.parseEnv(options?.privileged.toString(), variables).toBoolean() + } } if (!imagePullPolicy.isNullOrBlank()) { imagePullPolicy = EnvUtils.parseEnv(imagePullPolicy, variables) @@ -54,7 +59,8 @@ data class Credential( data class DockerOptions( var volumes: List?, var mounts: List?, - var gpus: String? + var gpus: String?, + var privileged: Boolean? ) // 第三方构建机docker类型,调度使用,会带有调度相关信息 From 2ac2ef2d44382bc2c9bbe293236965d8f2041c07 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Wed, 5 Jun 2024 09:20:58 +0800 Subject: [PATCH 0046/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker 特权模式 --- .../devops/process/yaml/v3/parsers/template/YamlObjects.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt index 8a69d35562b..e0419c29eac 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt @@ -297,7 +297,8 @@ object YamlObjects { null } else { transValue>(fromPath, "mounts", optionsMap["mounts"]) - } + }, + privileged = getNullValue("privileged", optionsMap)?.toBoolean() ) }, imagePullPolicy = getNullValue(key = "image-pull-policy", map = containerMap) From f2aeebacdfe533c2aa6480c3afaac17cdccb4079 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Wed, 5 Jun 2024 09:53:20 +0800 Subject: [PATCH 0047/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SubPipelineStatusService.kt | 5 +- .../engine/control/BuildStartControl.kt | 7 +-- .../start/SubPipelineBuildStartListener.kt | 47 +++++++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 5a8d0c17da4..62ad5a7112d 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -32,6 +32,7 @@ import com.tencent.devops.common.api.pojo.ErrorType import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.enums.StartType import com.tencent.devops.common.redis.RedisOperation @@ -81,7 +82,7 @@ class SubPipelineStatusService @Autowired constructor( /** * 异步启动子流水线 */ - fun onAsyncStart(event: PipelineBuildStartEvent) { + fun onAsyncStart(event: PipelineBuildStartBroadCastEvent) { with(event) { try { updateParentPipelineTaskStatus( @@ -160,7 +161,7 @@ class SubPipelineStatusService @Autowired constructor( buildId = buildVariables[PIPELINE_START_PARENT_BUILD_ID]!! )?.let { logger.info("start update parent pipeline asyncStatus[$asyncStatus]|${it.projectId}|${it.pipelineId}|" + - "${it.buildId}|${it.executeCount}|") + "${it.buildId}|${it.executeCount}") pipelineRuntimeService.updateAsyncStatus( projectId = it.projectId, pipelineId = it.pipelineId, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt index f4655b1b51e..261134091d6 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt @@ -86,7 +86,6 @@ import com.tencent.devops.process.engine.utils.ContainerUtils import com.tencent.devops.common.pipeline.pojo.setting.PipelineRunLockType import com.tencent.devops.common.pipeline.pojo.setting.PipelineSetting import com.tencent.devops.process.service.BuildVariableService -import com.tencent.devops.process.service.SubPipelineStatusService import com.tencent.devops.process.service.scm.ScmProxyService import com.tencent.devops.process.utils.BUILD_NO import com.tencent.devops.process.utils.PIPELINE_TIME_START @@ -123,8 +122,7 @@ class BuildStartControl @Autowired constructor( private val scmProxyService: ScmProxyService, private val buildLogPrinter: BuildLogPrinter, private val meterRegistry: MeterRegistry, - private val pipelineUrlBean: PipelineUrlBean, - private val subPipelineStatusService: SubPipelineStatusService + private val pipelineUrlBean: PipelineUrlBean ) { companion object { @@ -182,9 +180,6 @@ class BuildStartControl @Autowired constructor( ) buildLogPrinter.stopLog(buildId = buildId, tag = TAG, containerHashId = JOB_ID, executeCount = executeCount) - watcher.start("updateParentPipelineTaskStatus") - subPipelineStatusService.onAsyncStart(this) - watcher.stop() startPipelineCount() } diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt new file mode 100644 index 00000000000..54320d57b88 --- /dev/null +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt @@ -0,0 +1,47 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.process.engine.listener.run.start + +import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher +import com.tencent.devops.common.event.listener.pipeline.BaseListener +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent +import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent +import com.tencent.devops.process.service.SubPipelineStatusService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +class SubPipelineBuildStartListener @Autowired constructor( + private val subPipelineStatusService: SubPipelineStatusService, + pipelineEventDispatcher: PipelineEventDispatcher +) : BaseListener(pipelineEventDispatcher) { + + override fun run(event: PipelineBuildStartBroadCastEvent) { + subPipelineStatusService.onAsyncStart(event) + } +} From 349d00e6a7c4283a1ceb52b4b331937a6c66bf30 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Wed, 5 Jun 2024 10:40:44 +0800 Subject: [PATCH 0048/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/event/dispatcher/pipeline/mq/MQ.kt | 1 + .../init/BuildEngineExtendConfiguration.kt | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt index 0ca613340a9..66594668c35 100644 --- a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt @@ -117,6 +117,7 @@ object MQ { const val QUEUE_PIPELINE_BUILD_FINISH_GITCI = "q.engine.pipeline.build.gitci" const val QUEUE_PIPELINE_BUILD_FINISH_LOG = "q.engine.pipeline.build.log" const val QUEUE_PIPELINE_BUILD_FINISH_SUBPIPEINE = "q.engine.pipeline.build.subpipeline" + const val QUEUE_PIPELINE_BUILD_START_SUBPIPEINE = "q.engine.pipeline.build.start.subpipeline" const val QUEUE_PIPELINE_BUILD_FINISH_WEBHOOK_QUEUE = "q.engine.pipeline.build.finish.webhook.queue" const val QUEUE_PIPELINE_BUILD_FINISH_NOTIFY_QUEUE = "q.engine.pipeline.build.finish.notify.queue" diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt index 456afb58272..56db7340935 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt @@ -30,6 +30,7 @@ package com.tencent.devops.process.engine.init import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ import com.tencent.devops.common.event.dispatcher.pipeline.mq.Tools import com.tencent.devops.process.engine.listener.run.finish.SubPipelineBuildFinishListener +import com.tencent.devops.process.engine.listener.run.start.SubPipelineBuildStartListener import org.springframework.amqp.core.Binding import org.springframework.amqp.core.BindingBuilder import org.springframework.amqp.core.DirectExchange @@ -160,4 +161,38 @@ class BuildEngineExtendConfiguration { maxConcurrency = 10 ) } + + @Bean + fun subPipelineBuildStartQueue(): Queue { + return Queue(MQ.QUEUE_PIPELINE_BUILD_START_SUBPIPEINE) + } + + @Bean + fun subPipelineBuildStartQueueBind( + @Autowired subPipelineBuildStartQueue: Queue, + @Autowired pipelineBuildStartFanoutExchange: FanoutExchange + ): Binding { + return BindingBuilder.bind(subPipelineBuildStartQueue).to(pipelineBuildStartFanoutExchange) + } + + @Bean + fun subPipelineBuildFinishListenerContainer( + @Autowired connectionFactory: ConnectionFactory, + @Autowired subPipelineBuildStartQueue: Queue, + @Autowired rabbitAdmin: RabbitAdmin, + @Autowired buildListener: SubPipelineBuildStartListener, + @Autowired messageConverter: Jackson2JsonMessageConverter + ): SimpleMessageListenerContainer { + return Tools.createSimpleMessageListenerContainer( + connectionFactory = connectionFactory, + queue = subPipelineBuildStartQueue, + rabbitAdmin = rabbitAdmin, + buildListener = buildListener, + messageConverter = messageConverter, + startConsumerMinInterval = 10000, + consecutiveActiveTrigger = 5, + concurrency = 1, + maxConcurrency = 10 + ) + } } From 5311c5e07f49e6b7c66de67e2be3bdb168b851a3 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Wed, 5 Jun 2024 10:53:05 +0800 Subject: [PATCH 0049/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tencent/devops/process/service/SubPipelineStatusService.kt | 1 - .../process/engine/init/BuildEngineExtendConfiguration.kt | 2 +- .../engine/listener/run/start/SubPipelineBuildStartListener.kt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 62ad5a7112d..55262b9d1b4 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -39,7 +39,6 @@ import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.common.websocket.enum.RefreshType import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_NO_BUILD_RECORD_FOR_CORRESPONDING_SUB_PIPELINE -import com.tencent.devops.process.engine.pojo.event.PipelineBuildStartEvent import com.tencent.devops.process.engine.pojo.event.PipelineBuildWebSocketPushEvent import com.tencent.devops.process.engine.service.PipelineRuntimeService import com.tencent.devops.process.pojo.pipeline.SubPipelineStatus diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt index 56db7340935..29df1a5f58d 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/init/BuildEngineExtendConfiguration.kt @@ -176,7 +176,7 @@ class BuildEngineExtendConfiguration { } @Bean - fun subPipelineBuildFinishListenerContainer( + fun subPipelineBuildStartListenerContainer( @Autowired connectionFactory: ConnectionFactory, @Autowired subPipelineBuildStartQueue: Queue, @Autowired rabbitAdmin: RabbitAdmin, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt index 54320d57b88..5d2d158b792 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/listener/run/start/SubPipelineBuildStartListener.kt @@ -29,7 +29,6 @@ package com.tencent.devops.process.engine.listener.run.start import com.tencent.devops.common.event.dispatcher.pipeline.PipelineEventDispatcher import com.tencent.devops.common.event.listener.pipeline.BaseListener -import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildFinishBroadCastEvent import com.tencent.devops.common.event.pojo.pipeline.PipelineBuildStartBroadCastEvent import com.tencent.devops.process.service.SubPipelineStatusService import org.springframework.beans.factory.annotation.Autowired From d2ad47bb7ceeb44fbc8b13b55e7bc7a6c3066f90 Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Wed, 5 Jun 2024 11:03:42 +0800 Subject: [PATCH 0050/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devops/process/service/SubPipelineStatusService.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 55262b9d1b4..5ce8b112c35 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -83,6 +83,10 @@ class SubPipelineStatusService @Autowired constructor( */ fun onAsyncStart(event: PipelineBuildStartBroadCastEvent) { with(event) { + // 不是流水线启动 + if (triggerType != StartType.PIPELINE.name) { + return + } try { updateParentPipelineTaskStatus( projectId = projectId, From 78b85dfae9c0af750363e50d9ce1b697af9fe554 Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Wed, 5 Jun 2024 11:10:23 +0800 Subject: [PATCH 0051/1143] =?UTF-8?q?Agent=E6=B8=85=E7=90=86=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E4=B8=BAworker=E5=85=9C=E5=BA=95=20#10234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker 特权模式 --- src/agent/agent/src/pkg/collector/collector.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/agent/agent/src/pkg/collector/collector.go b/src/agent/agent/src/pkg/collector/collector.go index 7f59d6199c6..28d1635f3e9 100644 --- a/src/agent/agent/src/pkg/collector/collector.go +++ b/src/agent/agent/src/pkg/collector/collector.go @@ -150,6 +150,7 @@ func genTelegrafConfig() (*bytes.Buffer, error) { buildGateway = "http://" + buildGateway } + ip := config.GAgentEnv.GetAgentIp() templateData := map[string]string{ "ProjectType": projectType, "AgentId": config.GAgentConfig.AgentId, @@ -161,6 +162,7 @@ func genTelegrafConfig() (*bytes.Buffer, error) { "BuildType": config.GAgentConfig.BuildType, "TlsCa": tlsCa, } + logs.Debugf("telegraf agentip %s", ip) var content = new(bytes.Buffer) tmpl, err := template.New("tmpl").Parse(telegrafconf.TelegrafConf) From 32906fe80c47dd695b8fb2fbe7e5534b22b863ed Mon Sep 17 00:00:00 2001 From: hejieehe <904696180@qq.com> Date: Wed, 5 Jun 2024 11:11:21 +0800 Subject: [PATCH 0052/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SubPipelineStatusService.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt index 5ce8b112c35..0790045ec58 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStatusService.kt @@ -106,19 +106,23 @@ class SubPipelineStatusService @Autowired constructor( if (triggerType != StartType.PIPELINE.name) { return } - updateParentPipelineTaskStatus( - projectId = projectId, - pipelineId = pipelineId, - buildId = buildId, - asyncStatus = "finish" - ) - // 子流水线是异步启动的,不需要缓存状态 - if (redisOperation.get(getSubPipelineStatusKey(buildId)) != null) { - redisOperation.set( - key = getSubPipelineStatusKey(buildId), - value = JsonUtil.toJson(getSubPipelineStatusFromDB(event.projectId, buildId)), - expiredInSecond = SUBPIPELINE_STATUS_FINISH_EXPIRED + try { + updateParentPipelineTaskStatus( + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + asyncStatus = "finish" ) + // 子流水线是异步启动的,不需要缓存状态 + if (redisOperation.get(getSubPipelineStatusKey(buildId)) != null) { + redisOperation.set( + key = getSubPipelineStatusKey(buildId), + value = JsonUtil.toJson(getSubPipelineStatusFromDB(event.projectId, buildId)), + expiredInSecond = SUBPIPELINE_STATUS_FINISH_EXPIRED + ) + } + } catch (ignored: Exception) { + logger.warn("fail to update parent pipeline task status", ignored) } } } From d1178810893e053b05347a3464f50eb864e9812f Mon Sep 17 00:00:00 2001 From: yjieliang Date: Wed, 5 Jun 2024 15:36:37 +0800 Subject: [PATCH 0053/1143] =?UTF-8?q?pref=EF=BC=9A=E6=8B=89=E5=8F=96?= =?UTF-8?q?=E6=8F=92=E4=BB=B6task.json=E6=96=87=E4=BB=B6=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E6=8F=90=E7=A4=BA=E4=BC=98=E5=8C=96=20#10446?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tencent/devops/store/constant/StoreMessageCode.kt | 4 ++++ .../store/atom/service/impl/AtomReleaseServiceImpl.kt | 9 ++++++++- .../devops/store/atom/service/impl/OpAtomServiceImpl.kt | 2 +- support-files/i18n/store/message_en_US.properties | 2 ++ support-files/i18n/store/message_zh_CN.properties | 2 ++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt index bd5dbed7b37..7ba00848499 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt @@ -93,6 +93,10 @@ object StoreMessageCode { const val TASK_JSON_CONFIG_IS_INVALID = "2120040" // 研发商店:java插件配置文件[task.json]target配置不正确,java插件的target命令配置需要以java开头 const val JAVA_ATOM_TASK_JSON_TARGET_IS_INVALID = "2120041" + // 研发商店: 拉取文件[{0}]失败,失败原因:{1} + const val USER_PULL_FILE_FAIL = "2120042" + // 插件包文件[{0}]不存在,请检查文件所在路径是否正确 + const val ATOM_PACKAGE_FILE_NOT_FOUND = "2120043" const val USER_TEMPLATE_VERSION_IS_NOT_FINISH = "2120201" // 研发商店:模板{0}的{1}版本发布未结束,请稍后再试 const val USER_TEMPLATE_RELEASE_STEPS_ERROR = "2120202" // 研发商店:模板发布流程状态变更顺序不正确 diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt index 2de95c5200f..19867810e8e 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt @@ -40,6 +40,7 @@ import com.tencent.devops.common.api.constant.SECURITY import com.tencent.devops.common.api.constant.TEST import com.tencent.devops.common.api.enums.FrontendTypeEnum import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.exception.RemoteServiceException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.JsonSchemaUtil import com.tencent.devops.common.api.util.JsonUtil @@ -811,6 +812,12 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ repositoryHashId = repositoryHashId, branch = branch ) + } catch (exception: RemoteServiceException) { + logger.error("BKSystemErrorMonitor|getTaskJsonContent|$atomCode|error=${exception.message}", exception) + throw ErrorCodeException( + errorCode = StoreMessageCode.USER_PULL_FILE_FAIL, + params = arrayOf(TASK_JSON_NAME, exception.message ?: "") + ) } catch (ignored: Throwable) { logger.error("BKSystemErrorMonitor|getTaskJsonContent|$atomCode|error=${ignored.message}", ignored) throw ErrorCodeException( @@ -818,7 +825,7 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ params = arrayOf(TASK_JSON_NAME) ) } - if (null == taskJsonStr || !JsonSchemaUtil.validateJson(taskJsonStr)) { + if ((null == taskJsonStr) || !JsonSchemaUtil.validateJson(taskJsonStr)) { throw ErrorCodeException( errorCode = StoreMessageCode.USER_REPOSITORY_PULL_TASK_JSON_FILE_FAIL, params = arrayOf(branch ?: MASTER, TASK_JSON_NAME) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt index e8581e9554a..c2a8f3946c1 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt @@ -394,7 +394,7 @@ class OpAtomServiceImpl @Autowired constructor( val taskJsonFile = File("$atomPath$fileSeparator$TASK_JSON_NAME") if (!taskJsonFile.exists()) { return I18nUtil.generateResponseDataObject( - messageCode = StoreMessageCode.USER_ATOM_CONF_INVALID, + messageCode = StoreMessageCode.ATOM_PACKAGE_FILE_NOT_FOUND, params = arrayOf(TASK_JSON_NAME), language = I18nUtil.getLanguage(userId) ) diff --git a/support-files/i18n/store/message_en_US.properties b/support-files/i18n/store/message_en_US.properties index fc224604fa0..3138ee819d0 100644 --- a/support-files/i18n/store/message_en_US.properties +++ b/support-files/i18n/store/message_en_US.properties @@ -39,6 +39,8 @@ 2120039=Failed to get environment variable information related to atom development language 2120040=store: Atom configuration file [task.json] config format is incorrect.{0} 2120041=store:the configuration file [task.json] target of the java atom configuration file is incorrect, and the target command configuration of the java atom needs to start with java +2120042=store: pull file[{0}]failed,fail cause:{1} +2120043=atom file [{0}] not exist,please check that the file is located in the correct path 2120201=store: the release of {1} version of template {0} is not over. Please try again later. 2120202=store: template release process status change order is incorrect 2120203=store: the visible range of the template is not within the visible range of the atom {0}. If necessary, please contact the publisher of the atom. diff --git a/support-files/i18n/store/message_zh_CN.properties b/support-files/i18n/store/message_zh_CN.properties index d5b0acff5df..142f636c09c 100644 --- a/support-files/i18n/store/message_zh_CN.properties +++ b/support-files/i18n/store/message_zh_CN.properties @@ -39,6 +39,8 @@ 2120039=获取插件开发语言相关的环境变量信息失败 2120040=研发商店:插件配置文件[task.json]config配置格式不正确,{0} 2120041=研发商店:java插件配置文件[task.json]target配置不正确,java插件的target命令配置需要以java开头 +2120042=研发商店: 拉取文件[{0}]失败,失败原因:{1} +2120043=插件包文件[{0}]不存在,请检查文件所在路径是否正确 2120201=研发商店: 模板{0}的{1}版本发布未结束,请稍后再试 2120202=研发商店: 模板发布流程状态变更顺序不正确 2120203=研发商店: 模板的可见范围不在插件{0}的可见范围之内,如有需要请联系插件的发布者 From b10cfdcb724eb6983d37f0c191c04d5d2f94dc2d Mon Sep 17 00:00:00 2001 From: v_yjjiaoyu <1981190393@qq.com> Date: Thu, 6 Jun 2024 11:26:58 +0800 Subject: [PATCH 0054/1143] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E6=88=90?= =?UTF-8?q?=E5=91=98=E7=AE=A1=E7=90=86=20#9620=20#=20Reviewed,=20transacti?= =?UTF-8?q?on=20id:=209378?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../permission-manage/group-table.vue | 437 ++++++++++++ .../children/permission-manage/manage-all.vue | 674 +++++++++--------- .../children/permission-manage/time-limit.vue | 127 ++++ 3 files changed, 888 insertions(+), 350 deletions(-) create mode 100644 src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-table.vue create mode 100644 src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-table.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-table.vue new file mode 100644 index 00000000000..edf430237a0 --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-table.vue @@ -0,0 +1,437 @@ + + + + + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue index b32711e1c59..b5a1bfe041e 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue @@ -37,170 +37,42 @@ 批量移交 批量移出 -
-

项目级用户组

-
- - - - - - -
+
+
-
-

资源级用户组

-
-

- 流水线-流水线组 - 3 -

- -
- - - - - - - - - - -
-
-
-
-

- 流水线-流水线组 - 3 +

+ +

+ 由于该用户仍有部分授权未移交,未能自动移出项目;如有需要,可前往「 + + 授权管理 + + 」处理

- -
- - - - - - - - - - -
-
-
+
- - diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue new file mode 100644 index 00000000000..07cfb35dad1 --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue @@ -0,0 +1,127 @@ + + + + + From 1b519a3d2ab75fe4e72fce1c5ec95e83ee64c93c Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 6 Jun 2024 16:58:09 +0800 Subject: [PATCH 0055/1143] =?UTF-8?q?feat=EF=BC=9A=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E6=9E=84=E5=BB=BA=E6=9C=BA=E6=9E=84=E5=BB=BA=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E9=94=81=E5=AE=9A=E7=AD=96=E7=95=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20#10449?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ThirdPartyDispatchService.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt index 8bfafaf6c9c..baf7935b6e0 100644 --- a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt +++ b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt @@ -206,7 +206,6 @@ class ThirdPartyDispatchService @Autowired constructor( dispatchMessage: DispatchMessage, dispatchType: ThirdPartyAgentIDDispatchType ) { - val agentResult = if (dispatchType.idType()) { client.get(ServiceThirdPartyAgentResource::class) .getAgentById(dispatchMessage.event.projectId, dispatchType.displayName) @@ -303,8 +302,8 @@ class ThirdPartyDispatchService @Autowired constructor( } // #10082 对于复用的机器和被复用的,需要加锁校验看看这台机器能不能使用 + val lockKey = AgentReuseMutex.genAgentReuseMutexLockKey(event.projectId, agent.agentId) if (hasReuseMutex) { - val lockKey = AgentReuseMutex.genAgentReuseMutexLockKey(event.projectId, agent.agentId) val lock = RedisLockByValue( redisOperation = redisOperation, lockKey = lockKey, @@ -348,6 +347,28 @@ class ThirdPartyDispatchService @Autowired constructor( } catch (e: Exception) { logger.error("inQueue|doAgentInQueue|error", e) } + } else if (redisOperation.get(lockKey) != null) { + // 没有复用逻辑的需要检查下如果这个机器剩一个可调度空间且有复用锁那么不能进行调度 + val checkRes = if (dockerInfo != null) { + ((agent.dockerParallelTaskCount ?: 4) - + thirdPartyAgentBuildService.getDockerRunningBuilds(agent.agentId)) <= 1 + } else { + ((agent.parallelTaskCount ?: 4) - + thirdPartyAgentBuildService.getRunningBuilds(agent.agentId)) <= 1 + } + if (checkRes) { + log( + dispatchMessage.event, + I18nUtil.getCodeLanMessage( + messageCode = AGENT_REUSE_MUTEX_REDISPATCH, + language = I18nUtil.getDefaultLocaleLanguage(), + params = arrayOf( + "${agent.agentId}|${agent.hostname}/${agent.ip}", redisOperation.get(lockKey) ?: "" + ) + ) + ) + return false + } } // #5806 入库失败就不再写Redis From a6ab59f74942988f076a7a6d3681ac00b311f55f Mon Sep 17 00:00:00 2001 From: ruotiantang Date: Thu, 6 Jun 2024 18:15:09 +0800 Subject: [PATCH 0056/1143] =?UTF-8?q?feat=EF=BC=9A=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E6=9E=84=E5=BB=BA=E6=9C=BA=E6=9E=84=E5=BB=BA=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E9=94=81=E5=AE=9A=E7=AD=96=E7=95=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20#10449?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/pipeline/type/agent/ThirdPartyAgentDispatch.kt | 5 ++++- .../devops/dispatch/service/ThirdPartyDispatchService.kt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDispatch.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDispatch.kt index 2251e129d8f..ecf25829ff2 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDispatch.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/type/agent/ThirdPartyAgentDispatch.kt @@ -12,7 +12,10 @@ abstract class ThirdPartyAgentDispatch( // 类型为REUSE_JOB时,被复用的job的value,防止同一个stage并发下拿不到agent,启动时填充 open var reusedInfo: ReusedInfo? ) : DispatchType(value) { - fun idType(): Boolean = (agentType == AgentType.ID) || (reusedInfo?.agentType == AgentType.ID) + // 本身是 id,和被复用对象在同一JOB且被复用对象也是 id,是复用但是位于后面的 JOB + fun idType(): Boolean = + (agentType == AgentType.ID) || (reusedInfo?.agentType == AgentType.ID) || + (agentType == AgentType.REUSE_JOB_ID && reusedInfo == null) // 是否在复用锁定链上 fun hasReuseMutex(): Boolean = this.agentType.isReuse() || this.reusedInfo != null diff --git a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt index baf7935b6e0..d0d8d93806c 100644 --- a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt +++ b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt @@ -174,7 +174,7 @@ class ThirdPartyDispatchService @Autowired constructor( ThirdPartyAgentIDDispatchType( displayName = agentId, workspace = dispatchType.workspace, - agentType = AgentType.ID, + agentType = AgentType.REUSE_JOB_ID, dockerInfo = dispatchType.dockerInfo, reusedInfo = dispatchType.reusedInfo ) From 6ab8a6a4b31454c4259ebca0e83dbfbce203fd88 Mon Sep 17 00:00:00 2001 From: lockiechen Date: Fri, 7 Jun 2024 12:25:52 +0800 Subject: [PATCH 0057/1143] =?UTF-8?q?feat=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E6=B5=81=E6=B0=B4=E7=BA=BF=E4=B8=AD=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=BC=82=E6=AD=A5=E6=89=A7=E8=A1=8C=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E7=9A=84=E7=8A=B6=E6=80=81=20#10260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bk-pipeline/dist/bk-pipeline.min.js | 2 +- src/frontend/bk-pipeline/src/Atom.vue | 7 +++++++ src/frontend/bk-pipeline/src/conf.scss | 4 ++-- src/frontend/bk-pipeline/src/icon.js | 2 +- src/frontend/bk-pipeline/src/index.scss | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js index 15df0332388..fe5d48e89e6 100644 --- a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js +++ b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js @@ -1,2 +1,2 @@ /*! For license information please see bk-pipeline.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue"),require("bk-magic-vue")):"function"==typeof define&&define.amd?define(["vue","bk-magic-vue"],t):"object"==typeof exports?exports.bkPipeline=t(require("vue"),require("bk-magic-vue")):e.bkPipeline=t(e.Vue,e.bkMagic)}(self,((e,t)=>(()=>{var i={606:()=>{!function(){const e='';document.body?document.body.insertAdjacentHTML("afterbegin",e):document.addEventListener("DOMContentLoaded",(function(){document.body.insertAdjacentHTML("afterbegin",e)}))}()},688:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n *//*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.connect-line{position:absolute;width:58px;top:19px;stroke:#3c96ff;stroke-width:1;fill:none}.insert-tip{position:absolute;display:block;padding:0 10px;max-width:100px;height:24px;display:flex;align-items:center;border:1px solid #addaff;border-radius:22px;color:#3c96ff;font-size:10px;cursor:pointer;background-color:#fff;box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.insert-tip.direction:after{content:"";position:absolute;height:6px;width:6px;background-color:#fff;transform:rotate(45deg);bottom:-4px;left:20px;border-right:1px solid #addaff;border-bottom:1px solid #addaff}.insert-tip .tip-icon{margin:0 5px 0 0;cursor:pointer;position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip .tip-icon:before,.insert-tip .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#3c96ff}.insert-tip .tip-icon:after{transform:rotate(90deg)}.insert-tip>span{white-space:nowrap}.insert-tip:hover{background-color:#3c96ff;color:#fff;border-color:#3c96ff}.insert-tip:hover.direction:after{background-color:#3c96ff;border-right-color:#3c96ff;border-bottom-color:#3c96ff}.insert-tip:hover .tip-icon{position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip:hover .tip-icon:before,.insert-tip:hover .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#fff}.insert-tip:hover .tip-icon:after{transform:rotate(90deg)}.pointer{cursor:pointer}span.skip-name{text-decoration:line-through;color:#c4cdd6}span.skip-name:hover{color:#c4cdd6}.spin-icon{display:inline-block;animation:loading .8s linear infinite}.add-plus-icon,.minus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #addaff;background-color:#fff;border-radius:50%;transition:all .3s ease}.add-plus-icon:before,.minus-icon:before,.add-plus-icon:after,.minus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#3c96ff}.add-plus-icon:after,.minus-icon:after{transform:rotate(90deg)}.add-plus-icon.active,.active.minus-icon{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon.active:before,.active.minus-icon:before,.add-plus-icon.active:after,.active.minus-icon:after{background-color:#fff}.add-plus-icon:hover,.minus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon:hover:before,.minus-icon:hover:before,.add-plus-icon:hover:after,.minus-icon:hover:after{background-color:#fff}.minus-icon:before{display:none}.un-exec-this-time{opacity:.5}.un-exec-this-time .un-exec-this-time{opacity:1}.readonly .stage-connector{background:#c3cdd7;color:#c3cdd7}.readonly .stage-connector:before{background:#c3cdd7}.readonly .connect-line.left,.readonly .connect-line.right{stroke:#c3cdd7}.readonly .connect-line.left:before,.readonly .connect-line.right:before{stroke:#c3cdd7}.readonly .connect-line.left:after,.readonly .connect-line.right:after{stroke:#c3cdd7;background-color:#c3cdd7}.readonly:after{background:#c3cdd7}.readonly .container-connect-triangle{color:#c3cdd7}.readonly .container-title{cursor:pointer;background-color:#63656e}.readonly .container-title:before,.readonly .container-title:after{border-top-color:#c3cdd7}.readonly .container-title>.container-name span{color:#fff}.editing .stage-connector{background:#3c96ff;color:#3c96ff}.editing .stage-connector:before{background:#3c96ff}.editing .connect-line.left,.editing .connect-line.right{stroke:#3c96ff}.editing .connect-line.left:before,.editing .connect-line.right:before{stroke:#3c96ff}.editing .connect-line.left:after,.editing .connect-line.right:after{stroke:#3c96ff;background-color:#3c96ff}.editing:after{background:#3c96ff}.editing:before{color:#3c96ff}.container-type{font-size:12px;margin-right:12px;font-style:normal}.container-type .devops-icon{font-size:18px}.container-type .devops-icon.icon-exclamation-triangle-shape{font-size:14px}.container-type .devops-icon.icon-exclamation-triangle-shape.is-danger{color:#ff5656}.container-type i{font-style:normal}.bk-pipeline .stage-status:not(.readonly){background-color:#3c96ff}.bk-pipeline .stage-status:not(.readonly).WARNING{background-color:#ffb400;color:#fff}.bk-pipeline .stage-status:not(.readonly).FAILED{background-color:#ff5656;color:#fff}.bk-pipeline .stage-status:not(.readonly).SUCCEED{background-color:#34d97b;color:#fff}.bk-pipeline .stage-status.CANCELED,.bk-pipeline .stage-status.REVIEW_ABORT,.bk-pipeline .stage-status.REVIEWING,.bk-pipeline .stage-name-status-icon.CANCELED,.bk-pipeline .stage-name-status-icon.REVIEW_ABORT,.bk-pipeline .stage-name-status-icon.REVIEWING{color:#ffb400}.bk-pipeline .stage-status.FAILED,.bk-pipeline .stage-status.QUALITY_CHECK_FAIL,.bk-pipeline .stage-status.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-status.QUEUE_TIMEOUT,.bk-pipeline .stage-status.EXEC_TIMEOUT,.bk-pipeline .stage-name-status-icon.FAILED,.bk-pipeline .stage-name-status-icon.QUALITY_CHECK_FAIL,.bk-pipeline .stage-name-status-icon.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-name-status-icon.QUEUE_TIMEOUT,.bk-pipeline .stage-name-status-icon.EXEC_TIMEOUT{color:#ff5656}.bk-pipeline .stage-status.SUCCEED,.bk-pipeline .stage-status.REVIEW_PROCESSED,.bk-pipeline .stage-name-status-icon.SUCCEED,.bk-pipeline .stage-name-status-icon.REVIEW_PROCESSED{color:#34d97b}.bk-pipeline .stage-status.PAUSE,.bk-pipeline .stage-name-status-icon.PAUSE{color:#63656e}.bk-pipeline .container-title .stage-status{color:#fff}.bk-pipeline .container-title.UNEXEC,.bk-pipeline .container-title.SKIP,.bk-pipeline .container-title.DISABLED{color:#fff;background-color:#63656e}.bk-pipeline .container-title.UNEXEC .fold-atom-icon,.bk-pipeline .container-title.SKIP .fold-atom-icon,.bk-pipeline .container-title.DISABLED .fold-atom-icon{color:#63656e}.bk-pipeline .container-title.QUEUE,.bk-pipeline .container-title.RUNNING,.bk-pipeline .container-title.REVIEWING,.bk-pipeline .container-title.PREPARE_ENV,.bk-pipeline .container-title.LOOP_WAITING,.bk-pipeline .container-title.DEPENDENT_WAITING,.bk-pipeline .container-title.CALL_WAITING{background-color:#459fff;color:#fff}.bk-pipeline .container-title.QUEUE .fold-atom-icon,.bk-pipeline .container-title.RUNNING .fold-atom-icon,.bk-pipeline .container-title.REVIEWING .fold-atom-icon,.bk-pipeline .container-title.PREPARE_ENV .fold-atom-icon,.bk-pipeline .container-title.LOOP_WAITING .fold-atom-icon,.bk-pipeline .container-title.DEPENDENT_WAITING .fold-atom-icon,.bk-pipeline .container-title.CALL_WAITING .fold-atom-icon{color:#459fff}.bk-pipeline .container-title.CANCELED,.bk-pipeline .container-title.REVIEW_ABORT,.bk-pipeline .container-title.TRY_FINALLY,.bk-pipeline .container-title.QUEUE_CACHE{background-color:#f6b026}.bk-pipeline .container-title.CANCELED span.skip-name,.bk-pipeline .container-title.REVIEW_ABORT span.skip-name,.bk-pipeline .container-title.TRY_FINALLY span.skip-name,.bk-pipeline .container-title.QUEUE_CACHE span.skip-name{color:#fff}.bk-pipeline .container-title.CANCELED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_ABORT .fold-atom-icon,.bk-pipeline .container-title.TRY_FINALLY .fold-atom-icon,.bk-pipeline .container-title.QUEUE_CACHE .fold-atom-icon{color:#f6b026}.bk-pipeline .container-title.FAILED,.bk-pipeline .container-title.TERMINATE,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT,.bk-pipeline .container-title.QUALITY_CHECK_FAIL,.bk-pipeline .container-title.QUEUE_TIMEOUT,.bk-pipeline .container-title.EXEC_TIMEOUT{color:#fff;background-color:#ff5656}.bk-pipeline .container-title.FAILED .fold-atom-icon,.bk-pipeline .container-title.TERMINATE .fold-atom-icon,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.QUALITY_CHECK_FAIL .fold-atom-icon,.bk-pipeline .container-title.QUEUE_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.EXEC_TIMEOUT .fold-atom-icon{color:#ff5656}.bk-pipeline .container-title.SUCCEED,.bk-pipeline .container-title.REVIEW_PROCESSED,.bk-pipeline .container-title.STAGE_SUCCESS{color:#fff;background-color:#34d97b}.bk-pipeline .container-title.SUCCEED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_PROCESSED .fold-atom-icon,.bk-pipeline .container-title.STAGE_SUCCESS .fold-atom-icon{color:#34d97b}.bk-pipeline .container-title.PAUSE{color:#fff;background-color:#ff9801}.bk-pipeline .container-title.PAUSE .fold-atom-icon{color:#ff9801}.bk-pipeline .bk-pipeline-atom.UNEXEC,.bk-pipeline .bk-pipeline-atom.SKIP,.bk-pipeline .bk-pipeline-atom.DISABLED{color:#63656e}.bk-pipeline .bk-pipeline-atom.CANCELED,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT,.bk-pipeline .bk-pipeline-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:before,.bk-pipeline .bk-pipeline-atom.REVIEWING:before{background-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:after,.bk-pipeline .bk-pipeline-atom.REVIEWING:after{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED .atom-icon,.bk-pipeline .bk-pipeline-atom.CANCELED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.CANCELED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEWING .stage-check-icon{color:#ffb400}.bk-pipeline .bk-pipeline-atom.FAILED,.bk-pipeline .bk-pipeline-atom.TERMINATE,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED:before,.bk-pipeline .bk-pipeline-atom.TERMINATE:before,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:before,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:before{background-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED:after,.bk-pipeline .bk-pipeline-atom.TERMINATE:after,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:after,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:after{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED .atom-icon,.bk-pipeline .bk-pipeline-atom.FAILED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.FAILED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-execute-time,.bk-pipeline .bk-pipeline-atom.TERMINATE .stage-check-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .stage-check-icon{color:#ff5656}.bk-pipeline .bk-pipeline-atom.SUCCEED,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:before{background-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:after{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-icon,.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{color:#34d97b}.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.PAUSE{border-color:#ff9801;color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE:before{background-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE:after{border-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE .atom-icon,.bk-pipeline .bk-pipeline-atom.PAUSE .atom-execute-time{color:#63656e}.bk-pipeline .bk-pipeline-atom.template-compare-atom{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:before{background-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:after{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom{background-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title{color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title>i{border-color:#34d97b}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title{color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i:last-child{border-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title{color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title>i{border-top:2px solid #ff5656}',""]);const s=r},29:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline .bk-pipeline-atom{cursor:pointer;position:relative;display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;border:1px solid #c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-progress{display:inline-flex;width:42px;height:42px;align-items:center;justify-content:center}.bk-pipeline .bk-pipeline-atom .active-atom-location-icon{position:absolute;color:#3c96ff;left:-30px}.bk-pipeline .bk-pipeline-atom.trigger-atom:before,.bk-pipeline .bk-pipeline-atom.trigger-atom:after{display:none}.bk-pipeline .bk-pipeline-atom:first-child:before{top:-16px}.bk-pipeline .bk-pipeline-atom:before{content:"";position:absolute;height:14px;width:2px;background:#c4cdd6;top:-12px;left:22px;z-index:1}.bk-pipeline .bk-pipeline-atom:after{content:"";position:absolute;height:4px;width:4px;border:2px solid #c4cdd6;border-radius:50%;background:#fff !important;top:-5px;left:19px;z-index:2}.bk-pipeline .bk-pipeline-atom.is-intercept{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-intercept:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-error{border-color:#ff5656;color:#ff5656}.bk-pipeline .bk-pipeline-atom.is-error:hover .atom-invalid-icon{display:none}.bk-pipeline .bk-pipeline-atom.is-error .atom-invalid-icon{margin:0 12px}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover{border-color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-name{color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .add-plus-icon.close,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .copy{cursor:pointer;display:block}.bk-pipeline .bk-pipeline-atom .atom-icon{text-align:center;margin:0 14.5px;font-size:18px;width:18px;color:#63656e;fill:currentColor}.bk-pipeline .bk-pipeline-atom .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name:hover{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .pause-button{margin-right:8px;color:#3c96ff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close{position:relative;display:block;width:16px;height:16px;border:1px solid #fff;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;display:none;margin-right:10px;border:none;transform:rotate(45deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{transform:rotate(90deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover{border-color:#ff5656;background-color:#ff5656}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:after{background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{left:7px;top:4px}.bk-pipeline .bk-pipeline-atom .copy{display:none;margin-right:10px;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .copy:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom>.atom-name{flex:1;color:#63656e;display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:188px;margin-right:2px}.bk-pipeline .bk-pipeline-atom>.atom-name span:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom .disabled{cursor:not-allowed;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-execounter{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-operate-area{margin:0 8px 0 0;color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-reviewing-tips[disabled]{cursor:not-allowed;color:#c3cdd7}.bk-pipeline .bk-pipeline-atom .atom-review-diasbled-tips{color:#c3cdd7;margin:0 8px 0 2px}.bk-pipeline .bk-pipeline-atom .atom-canskip-checkbox{margin-right:6px}.bk-pipeline .bk-pipeline-atom.quality-atom{display:flex;justify-content:center;border-color:rgba(0,0,0,0);height:24px;background:rgba(0,0,0,0);border-color:rgba(0,0,0,0) !important;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom:before{height:40px;z-index:8}.bk-pipeline .bk-pipeline-atom.quality-atom:after{display:none}.bk-pipeline .bk-pipeline-atom.quality-atom.last-quality-atom:before{height:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title{display:flex;width:100%;align-items:center;justify-content:center;margin-left:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>span{border-radius:12px;font-weight:bold;border:1px solid #c4cdd6;padding:0 12px;margin:0 4px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>i{height:0;flex:1;border-top:2px dashed #c4cdd6}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list{position:absolute;right:0}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span:first-child{margin-right:5px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job{position:absolute;top:6px;right:42px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job:before{display:inline-block;animation:rotating infinite .6s ease-in-out}.bk-pipeline .bk-pipeline-atom.quality-atom .disabled-review span{color:#c4cdd6;cursor:default}.bk-pipeline .bk-pipeline-atom.readonly{background-color:#fff}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover span{color:#63656e}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover .skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom.readonly.quality-prev-atom:before{height:24px;top:-23px}',""]);const s=r},259:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.container-atom-list{position:relative;z-index:3}.container-atom-list .sortable-ghost-atom{opacity:.5}.container-atom-list .sortable-chosen-atom{transform:scale(1)}.container-atom-list .add-atom-entry{position:absolute;bottom:-10px;left:calc(50% - 9px);cursor:pointer;z-index:3}.container-atom-list .add-atom-entry .add-plus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #c4cdd6;background-color:#fff;border-radius:50%;transition:all .3s ease}.container-atom-list .add-atom-entry .add-plus-icon:before,.container-atom-list .add-atom-entry .add-plus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#c4cdd6}.container-atom-list .add-atom-entry .add-plus-icon:after{transform:rotate(90deg)}.container-atom-list .add-atom-entry .add-plus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.container-atom-list .add-atom-entry .add-plus-icon:hover:before,.container-atom-list .add-atom-entry .add-plus-icon:hover:after{background-color:#fff}.container-atom-list .add-atom-entry.block-add-entry{display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;position:static;padding-right:12px;border-style:dashed;color:#ff5656;border-color:#ff5656;border-width:1px}.container-atom-list .add-atom-entry.block-add-entry .add-atom-label{flex:1;color:#dde4eb}.container-atom-list .add-atom-entry.block-add-entry .add-plus-icon{margin:12px 13px}.container-atom-list .add-atom-entry.block-add-entry:before,.container-atom-list .add-atom-entry.block-add-entry:after{display:none}.container-atom-list .add-atom-entry:hover{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow{position:relativecd;display:flex;align-items:center;justify-content:center;position:absolute;height:14px;width:14px;border:1px solid #c4cdd6;color:#c4cdd6;border-radius:50%;background:#fff !important;top:-7px;left:17px;z-index:3;font-weight:bold}.container-atom-list .post-action-arrow .toggle-post-action-icon{display:block;transition:all .5s ease}.container-atom-list .post-action-arrow.post-action-arrow-show .toggle-post-action-icon{transform:rotate(180deg)}.container-atom-list .post-action-arrow::after{content:"";position:absolute;width:2px;height:6px;background-color:#c4cdd6;left:5px;top:-6px}.container-atom-list .post-action-arrow.FAILED{border-color:#ff5656;color:#ff5656}.container-atom-list .post-action-arrow.FAILED::after{background-color:#ff5656}.container-atom-list .post-action-arrow.CANCELED{border-color:#ffb400;color:#ffb400}.container-atom-list .post-action-arrow.CANCELED::after{background-color:#ffb400}.container-atom-list .post-action-arrow.SUCCEED{border-color:#34d97b;color:#34d97b}.container-atom-list .post-action-arrow.SUCCEED::after{background-color:#34d97b}.container-atom-list .post-action-arrow.RUNNING{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow.RUNNING::after{background-color:#3c96ff}',""]);const s=r},162:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,".is-danger[data-v-8b5d140e]{color:#ff5656}",""]);const s=r},662:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container .container-title{display:flex;height:42px;background:#33333f;cursor:pointer;color:#fff;font-size:14px;align-items:center;position:relative;margin:0 0 16px 0;z-index:3}.devops-stage-container .container-title>.container-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;padding:0 6px}.devops-stage-container .container-title .atom-canskip-checkbox{margin-right:6px}.devops-stage-container .container-title .atom-canskip-checkbox.is-disabled .bk-checkbox{background-color:rgba(0,0,0,0);border-color:#979ba4}.devops-stage-container .container-title input[type=checkbox]{border-radius:3px}.devops-stage-container .container-title .matrix-flag-icon{position:absolute;top:0px;font-size:16px}.devops-stage-container .container-title .fold-atom-icon{position:absolute;background:#fff;border-radius:50%;bottom:-10px;left:calc(50% - 9px);color:#c4cdd6;transition:all .3s ease}.devops-stage-container .container-title .fold-atom-icon.open{transform:rotate(-180deg)}.devops-stage-container .container-title .copyJob{display:none;margin-right:10px;color:#c4cdd6;cursor:pointer}.devops-stage-container .container-title .copyJob:hover{color:#3c96ff}.devops-stage-container .container-title .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;border:none;display:none;margin-right:10px;transform:rotate(45deg);cursor:pointer}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.devops-stage-container .container-title .close:after{transform:rotate(90deg)}.devops-stage-container .container-title .close:hover{border-color:#ff5656;background-color:#ff5656}.devops-stage-container .container-title .close:hover:before,.devops-stage-container .container-title .close:hover:after{background-color:#fff}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{left:7px;top:4px}.devops-stage-container .container-title .debug-btn{position:absolute;height:100%;right:0}.devops-stage-container .container-title .container-locate-icon{position:absolute;left:-30px;top:13px;color:#3c96ff}.devops-stage-container .container-title:hover .copyJob,.devops-stage-container .container-title:hover .close{display:block}.devops-stage-container .container-title:hover .hover-hide{display:none}',""]);const s=r},637:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline-matrix-group{border:1px solid #b5c0d5;padding:10px;background:#fff}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header{display:flex;align-items:center;cursor:pointer;justify-content:space-between;height:20px}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name{display:flex;align-items:center;font-size:14px;color:#222;min-width:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon{display:block;margin-right:10px;transition:all .3s ease;flex-shrink:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon.open{transform:rotate(-180deg)}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name>span{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status{color:#3c96ff;display:flex;align-items:center}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status .status-desc{font-size:12px;display:inline-block;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .matrix-body{margin-top:12px}.bk-pipeline-matrix-group .matrix-body>div{margin-bottom:34px}',""]);const s=r},533:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.pipeline-drag{cursor:grab,default}.pipeline-stage{position:relative;width:280px;border-radius:2px;padding:0;background:#f5f5f5;margin:0 80px 0 0}.pipeline-stage .pipeline-stage-entry{position:relative;cursor:pointer;display:flex;width:100%;height:50px;align-items:center;min-width:0;font-size:14px;background-color:#eff5ff;border:1px solid #d4e8ff;color:#3c96ff}.pipeline-stage .pipeline-stage-entry:hover{border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage .pipeline-stage-entry .check-in-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon{position:absolute;left:-14px;top:11px}.pipeline-stage .pipeline-stage-entry .check-in-icon.check-out-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon.check-out-icon{left:auto;right:-14px}.pipeline-stage .pipeline-stage-entry .stage-entry-name{flex:1;display:flex;align-items:center;justify-content:center;margin:0 80px;overflow:hidden}.pipeline-stage .pipeline-stage-entry .stage-entry-name .stage-title-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-left:6px}.pipeline-stage .pipeline-stage-entry .stage-single-retry{cursor:pointer;position:absolute;right:6%;color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage{position:absolute;right:27px}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon.stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage.stage-entry-error-icon{top:16px;right:8px;color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns{position:absolute;right:0;top:16px;display:none;width:80px;align-items:center;justify-content:flex-end;color:#fff;fill:#fff;z-index:2}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .copy-stage:hover{color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#fff;border-radius:50%;transition:all .3s ease;border:none;margin:0 10px 0 8px;transform:rotate(45deg);cursor:pointer}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{transform:rotate(90deg)}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover{border-color:#ff5656;background-color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:after{background-color:#fff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{left:7px;top:4px}.pipeline-stage.editable:not(.readonly) .pipeline-stage-entry:hover{color:#000;border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-btns{display:flex}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-error-icon{display:none}.pipeline-stage.readonly.SKIP .pipeline-stage-entry{color:#c3cdd7;fill:#c3cdd7}.pipeline-stage.readonly.RUNNING .pipeline-stage-entry{background-color:#eff5ff;border-color:#d4e8ff;color:#3c96ff}.pipeline-stage.readonly.REVIEWING .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly.FAILED .pipeline-stage-entry{border-color:#ffd4d4;background-color:#fff9f9;color:#000}.pipeline-stage.readonly.SUCCEED .pipeline-stage-entry{background-color:#f3fff6;border-color:#bbefc9;color:#000}.pipeline-stage.readonly .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly .pipeline-stage-entry .skip-icon{vertical-align:middle}.pipeline-stage .add-connector{stroke-dasharray:4,4;top:7px;left:10px}.pipeline-stage .append-stage{position:absolute;top:16px;right:-44px;z-index:3}.pipeline-stage .append-stage .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .append-stage .line-add{top:-46px;left:-16px}.pipeline-stage .append-stage .add-plus-connector{position:absolute;width:40px;height:2px;left:-26px;top:8px;background-color:#3c96ff}.pipeline-stage .add-menu{position:absolute;top:16px;left:-53px;cursor:pointer;z-index:3}.pipeline-stage .add-menu .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .add-menu .minus-icon{z-index:4}.pipeline-stage .add-menu .line-add{top:-46px;left:-16px}.pipeline-stage .add-menu .parallel-add{left:50px}.pipeline-stage .add-menu .add-plus-connector{position:absolute;width:24px;height:2px;left:17px;top:8px;background-color:#3c96ff}.pipeline-stage:first-child .stage-connector{display:none}.pipeline-stage.is-final-stage .stage-connector{width:80px}.pipeline-stage .stage-connector{position:absolute;width:66px;height:2px;left:-80px;top:24px;color:#3c96ff;background-color:#3c96ff}.pipeline-stage .stage-connector:before{content:"";width:8px;height:8px;position:absolute;left:-4px;top:-3px;background-color:#3c96ff;border-radius:50%}.pipeline-stage .stage-connector .connector-angle{position:absolute;right:-3px;top:-6px}.pipeline-stage .insert-stage{position:absolute;display:block;width:160px;background-color:#fff;border:1px solid #dcdee5}.pipeline-stage .insert-stage .click-item{padding:0 15px;font-size:12px;line-height:32px}.pipeline-stage .insert-stage .click-item:hover,.pipeline-stage .insert-stage .click-item :hover{color:#3c96ff;background-color:#eaf3ff}.pipeline-stage .insert-stage .disabled-item{cursor:not-allowed;color:#c4cdd6}.pipeline-stage .insert-stage .disabled-item:hover,.pipeline-stage .insert-stage .disabled-item :hover{color:#c4cdd6;background-color:#fff}.stage-retry-dialog .bk-form-radio{display:block;margin-top:15px}.stage-retry-dialog .bk-form-radio .bk-radio-text{font-size:14px}',""]);const s=r},935:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-check-icon{border-radius:100px;border:1px solid #d0d8ea;display:flex;align-items:center;background:#fff;font-size:12px;z-index:3;color:#63656e}.stage-check-icon.is-readonly-check-icon{filter:grayscale(100%)}.stage-check-icon.reviewing{color:#3c96ff;border-color:#3c96ff}.stage-check-icon.quality-check{color:#34d97b;border-color:#34d97b}.stage-check-icon.quality-check-error,.stage-check-icon.review-error{color:#ff5656;border-color:#ff5656}.stage-check-icon .stage-check-txt{padding-right:10px}',""]);const s=r},49:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container{text-align:left;margin:16px 20px 24px 20px;position:relative}.devops-stage-container:not(.last-stage-container):after{content:"";width:6px;height:6px;position:absolute;right:-3px;top:19px;border-radius:50%}.devops-stage-container:not(.last-stage-container):after:not(.readonly){background:#3c96ff}.devops-stage-container .container-connect-triangle{position:absolute;color:#3c96ff;left:-9px;top:15.5px;z-index:2}.devops-stage-container .connect-line{position:absolute;top:1px;stroke:#3c96ff;stroke-width:1;fill:none;z-index:0}.devops-stage-container .connect-line.left{left:-54px}.devops-stage-container .connect-line.right{right:-46px}.devops-stage-container .connect-line.first-connect-line{height:76px;width:58px;top:-43px}.devops-stage-container .connect-line.first-connect-line.left{left:-63px}.devops-stage-container .connect-line.first-connect-line.right{left:auto;right:-55px}',""]);const s=r},82:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-status{position:relative;text-align:center;overflow:hidden;font-size:14px;width:42px;height:42px;box-sizing:border-box}.stage-status>span{position:absolute;width:100%;height:100%;display:flex;align-items:center;justify-content:center;left:0;top:0;transition:all .3s cubic-bezier(1, 0.5, 0.8, 1)}.stage-status.matrix{width:20px;height:20px}.stage-status .status-logo{position:absolute;left:15px;top:15px}.stage-status .slide-top-enter,.stage-status .slide-top-leave-to{transform:translateY(42px)}.stage-status .slide-down-enter,.stage-status .slide-down-leave-to{transform:translateY(-42px)}.stage-status .slide-left-enter,.stage-status .slide-left-leave-to{transform:translateX(42px)}.stage-status .slide-right-enter,.stage-status .slide-right-leave-to{transform:translateX(-42px)}.stage-status.readonly{font-size:12px;font-weight:normal;background-color:rgba(0,0,0,0)}.stage-status.readonly.container{color:#fff}',""]);const s=r},157:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(441),o=i.n(n),a=i(562),r=i.n(a)()(o());r.push([e.id,".bk-pipeline{display:flex;padding-right:120px;width:fit-content;position:relative;align-items:flex-start}.bk-pipeline ul,.bk-pipeline li{margin:0;padding:0}.list-item{transition:transform .2s ease-out}.list-enter,.list-leave-to{opacity:0;transform:translateY(36px) scale(0, 1)}.list-leave-active{position:absolute !important}",""]);const s=r},562:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i="",n=void 0!==t[5];return t[4]&&(i+="@supports (".concat(t[4],") {")),t[2]&&(i+="@media ".concat(t[2]," {")),n&&(i+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),i+=e(t),n&&(i+="}"),t[2]&&(i+="}"),t[4]&&(i+="}"),i})).join("")},t.i=function(e,i,n,o,a){"string"==typeof e&&(e=[[null,e,void 0]]);var r={};if(n)for(var s=0;s0?" ".concat(p[5]):""," {").concat(p[1],"}")),p[5]=a),i&&(p[2]?(p[1]="@media ".concat(p[2]," {").concat(p[1],"}"),p[2]=i):p[2]=i),o&&(p[4]?(p[1]="@supports (".concat(p[4],") {").concat(p[1],"}"),p[4]=o):p[4]="".concat(o)),t.push(p))}},t}},441:e=>{"use strict";e.exports=function(e){return e[1]}},713:(e,t,i)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function o(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(){return a=Object.assign||function(e){for(var t=1;tgt,Sortable:()=>ze,Swap:()=>at,default:()=>yt});var l=s(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),c=s(/Edge/i),p=s(/firefox/i),d=s(/safari/i)&&!s(/chrome/i)&&!s(/android/i),u=s(/iP(ad|od|hone)/i),h=s(/chrome/i)&&s(/android/i),f={capture:!1,passive:!1};function m(e,t,i){e.addEventListener(t,i,!l&&f)}function g(e,t,i){e.removeEventListener(t,i,!l&&f)}function b(e,t){if(t){if(">"===t[0]&&(t=t.substring(1)),e)try{if(e.matches)return e.matches(t);if(e.msMatchesSelector)return e.msMatchesSelector(t);if(e.webkitMatchesSelector)return e.webkitMatchesSelector(t)}catch(e){return!1}return!1}}function v(e){return e.host&&e!==document&&e.host.nodeType?e.host:e.parentNode}function y(e,t,i,n){if(e){i=i||document;do{if(null!=t&&(">"===t[0]?e.parentNode===i&&b(e,t):b(e,t))||n&&e===i)return e;if(e===i)break}while(e=v(e))}return null}var E,x=/\s+/g;function k(e,t,i){if(e&&t)if(e.classList)e.classList[i?"add":"remove"](t);else{var n=(" "+e.className+" ").replace(x," ").replace(" "+t+" "," ");e.className=(n+(i?" "+t:"")).replace(x," ")}}function I(e,t,i){var n=e&&e.style;if(n){if(void 0===i)return document.defaultView&&document.defaultView.getComputedStyle?i=document.defaultView.getComputedStyle(e,""):e.currentStyle&&(i=e.currentStyle),void 0===t?i:i[t];t in n||-1!==t.indexOf("webkit")||(t="-webkit-"+t),n[t]=i+("string"==typeof i?"":"px")}}function T(e,t){var i="";if("string"==typeof e)i=e;else do{var n=I(e,"transform");n&&"none"!==n&&(i=n+" "+i)}while(!t&&(e=e.parentNode));var o=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return o&&new o(i)}function S(e,t,i){if(e){var n=e.getElementsByTagName(t),o=0,a=n.length;if(i)for(;o=a:o<=a))return n;if(n===C())break;n=M(n,!1)}return!1}function O(e,t,i){for(var n=0,o=0,a=e.children;o2&&void 0!==arguments[2]?arguments[2]:{},n=i.evt,o=function(e,t){if(null==e)return{};var i,n,o=function(e,t){if(null==e)return{};var i,n,o={},a=Object.keys(e);for(n=0;n=0||(o[i]=e[i]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(o[i]=e[i])}return o}(i,["evt"]);W.pluginEvent.bind(ze)(e,t,r({dragEl:Y,parentEl:q,ghostEl:K,rootEl:X,nextEl:Q,lastDownEl:J,cloneEl:Z,cloneHidden:ee,dragStarted:he,putSortable:re,activeSortable:ze.active,originalEvent:n,oldIndex:te,oldDraggableIndex:ne,newIndex:ie,newDraggableIndex:oe,hideGhostForTarget:Me,unhideGhostForTarget:Le,cloneNowHidden:function(){ee=!0},cloneNowShown:function(){ee=!1},dispatchSortableEvent:function(e){$({sortable:t,name:e,originalEvent:n})}},o))};function $(e){j(r({putSortable:re,cloneEl:Z,targetEl:Y,rootEl:X,oldIndex:te,oldDraggableIndex:ne,newIndex:ie,newDraggableIndex:oe},e))}var Y,q,K,X,Q,J,Z,ee,te,ie,ne,oe,ae,re,se,le,ce,pe,de,ue,he,fe,me,ge,be,ve=!1,ye=!1,Ee=[],xe=!1,ke=!1,Ie=[],Te=!1,Se=[],Ce="undefined"!=typeof document,Ae=u,we=c||l?"cssFloat":"float",Oe=Ce&&!h&&!u&&"draggable"in document.createElement("div"),_e=function(){if(Ce){if(l)return!1;var e=document.createElement("x");return e.style.cssText="pointer-events:auto","auto"===e.style.pointerEvents}}(),Ne=function(e,t){var i=I(e),n=parseInt(i.width)-parseInt(i.paddingLeft)-parseInt(i.paddingRight)-parseInt(i.borderLeftWidth)-parseInt(i.borderRightWidth),o=O(e,0,t),a=O(e,1,t),r=o&&I(o),s=a&&I(a),l=r&&parseInt(r.marginLeft)+parseInt(r.marginRight)+A(o).width,c=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+A(a).width;if("flex"===i.display)return"column"===i.flexDirection||"column-reverse"===i.flexDirection?"vertical":"horizontal";if("grid"===i.display)return i.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&r.float&&"none"!==r.float){var p="left"===r.float?"left":"right";return!a||"both"!==s.clear&&s.clear!==p?"horizontal":"vertical"}return o&&("block"===r.display||"flex"===r.display||"table"===r.display||"grid"===r.display||l>=n&&"none"===i[we]||a&&"none"===i[we]&&l+c>n)?"vertical":"horizontal"},Re=function(e){function t(e,i){return function(n,o,a,r){var s=n.options.group.name&&o.options.group.name&&n.options.group.name===o.options.group.name;if(null==e&&(i||s))return!0;if(null==e||!1===e)return!1;if(i&&"clone"===e)return e;if("function"==typeof e)return t(e(n,o,a,r),i)(n,o,a,r);var l=(i?n:o).options.group.name;return!0===e||"string"==typeof e&&e===l||e.join&&e.indexOf(l)>-1}}var i={},o=e.group;o&&"object"==n(o)||(o={name:o}),i.name=o.name,i.checkPull=t(o.pull,!0),i.checkPut=t(o.put),i.revertClone=o.revertClone,e.group=i},Me=function(){!_e&&K&&I(K,"display","none")},Le=function(){!_e&&K&&I(K,"display","")};Ce&&document.addEventListener("click",(function(e){if(ye)return e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.stopImmediatePropagation&&e.stopImmediatePropagation(),ye=!1,!1}),!0);var De=function(e){if(Y){e=e.touches?e.touches[0]:e;var t=(o=e.clientX,a=e.clientY,Ee.some((function(e){if(!_(e)){var t=A(e),i=e[U].options.emptyInsertThreshold,n=o>=t.left-i&&o<=t.right+i,s=a>=t.top-i&&a<=t.bottom+i;return i&&n&&s?r=e:void 0}})),r);if(t){var i={};for(var n in e)e.hasOwnProperty(n)&&(i[n]=e[n]);i.target=i.rootEl=t,i.preventDefault=void 0,i.stopPropagation=void 0,t[U]._onDragOver(i)}}var o,a,r},He=function(e){Y&&Y.parentNode[U]._isOutsideThisEl(e.target)};function ze(e,t){if(!e||!e.nodeType||1!==e.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(e));this.el=e,this.options=t=a({},t),e[U]=this;var i,n,o={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(e.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Ne(e,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(e,t){e.setData("Text",t.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==ze.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var s in W.initializePlugins(this,e,o),o)!(s in t)&&(t[s]=o[s]);for(var l in Re(t),this)"_"===l.charAt(0)&&"function"==typeof this[l]&&(this[l]=this[l].bind(this));this.nativeDraggable=!t.forceFallback&&Oe,this.nativeDraggable&&(this.options.touchStartThreshold=1),t.supportPointer?m(e,"pointerdown",this._onTapStart):(m(e,"mousedown",this._onTapStart),m(e,"touchstart",this._onTapStart)),this.nativeDraggable&&(m(e,"dragover",this),m(e,"dragenter",this)),Ee.push(this.el),t.store&&t.store.get&&this.sort(t.store.get(this)||[]),a(this,(n=[],{captureAnimationState:function(){n=[],this.options.animation&&[].slice.call(this.el.children).forEach((function(e){if("none"!==I(e,"display")&&e!==ze.ghost){n.push({target:e,rect:A(e)});var t=r({},n[n.length-1].rect);if(e.thisAnimationDuration){var i=T(e,!0);i&&(t.top-=i.f,t.left-=i.e)}e.fromRect=t}}))},addAnimationState:function(e){n.push(e)},removeAnimationState:function(e){n.splice(function(e,t){for(var i in e)if(e.hasOwnProperty(i))for(var n in t)if(t.hasOwnProperty(n)&&t[n]===e[i][n])return Number(i);return-1}(n,{target:e}),1)},animateAll:function(e){var t=this;if(!this.options.animation)return clearTimeout(i),void("function"==typeof e&&e());var o=!1,a=0;n.forEach((function(e){var i=0,n=e.target,r=n.fromRect,s=A(n),l=n.prevFromRect,c=n.prevToRect,p=e.rect,d=T(n,!0);d&&(s.top-=d.f,s.left-=d.e),n.toRect=s,n.thisAnimationDuration&&L(l,s)&&!L(r,s)&&(p.top-s.top)/(p.left-s.left)==(r.top-s.top)/(r.left-s.left)&&(i=function(e,t,i,n){return Math.sqrt(Math.pow(t.top-e.top,2)+Math.pow(t.left-e.left,2))/Math.sqrt(Math.pow(t.top-i.top,2)+Math.pow(t.left-i.left,2))*n.animation}(p,l,c,t.options)),L(s,r)||(n.prevFromRect=r,n.prevToRect=s,i||(i=t.options.animation),t.animate(n,p,s,i)),i&&(o=!0,a=Math.max(a,i),clearTimeout(n.animationResetTimer),n.animationResetTimer=setTimeout((function(){n.animationTime=0,n.prevFromRect=null,n.fromRect=null,n.prevToRect=null,n.thisAnimationDuration=null}),i),n.thisAnimationDuration=i)})),clearTimeout(i),o?i=setTimeout((function(){"function"==typeof e&&e()}),a):"function"==typeof e&&e(),n=[]},animate:function(e,t,i,n){if(n){I(e,"transition",""),I(e,"transform","");var o=T(this.el),a=o&&o.a,r=o&&o.d,s=(t.left-i.left)/(a||1),l=(t.top-i.top)/(r||1);e.animatingX=!!s,e.animatingY=!!l,I(e,"transform","translate3d("+s+"px,"+l+"px,0)"),function(e){e.offsetWidth}(e),I(e,"transition","transform "+n+"ms"+(this.options.easing?" "+this.options.easing:"")),I(e,"transform","translate3d(0,0,0)"),"number"==typeof e.animated&&clearTimeout(e.animated),e.animated=setTimeout((function(){I(e,"transition",""),I(e,"transform",""),e.animated=!1,e.animatingX=!1,e.animatingY=!1}),n)}}}))}function Pe(e,t,i,n,o,a,r,s){var p,d,u=e[U],h=u.options.onMove;return!window.CustomEvent||l||c?(p=document.createEvent("Event")).initEvent("move",!0,!0):p=new CustomEvent("move",{bubbles:!0,cancelable:!0}),p.to=t,p.from=e,p.dragged=i,p.draggedRect=n,p.related=o||t,p.relatedRect=a||A(t),p.willInsertAfter=s,p.originalEvent=r,e.dispatchEvent(p),h&&(d=h.call(u,p,r)),d}function Fe(e){e.draggable=!1}function Ue(){Te=!1}function Be(e){for(var t=e.tagName+e.className+e.src+e.href+e.textContent,i=t.length,n=0;i--;)n+=t.charCodeAt(i);return n.toString(36)}function Ve(e){return setTimeout(e,0)}function We(e){return clearTimeout(e)}ze.prototype={constructor:ze,_isOutsideThisEl:function(e){this.el.contains(e)||e===this.el||(fe=null)},_getDirection:function(e,t){return"function"==typeof this.options.direction?this.options.direction.call(this,e,t,Y):this.options.direction},_onTapStart:function(e){if(e.cancelable){var t=this,i=this.el,n=this.options,o=n.preventOnFilter,a=e.type,r=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,s=(r||e).target,l=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||s,c=n.filter;if(function(e){Se.length=0;for(var t=e.getElementsByTagName("input"),i=t.length;i--;){var n=t[i];n.checked&&Se.push(n)}}(i),!Y&&!(/mousedown|pointerdown/.test(a)&&0!==e.button||n.disabled||l.isContentEditable||(s=y(s,n.draggable,i,!1))&&s.animated||J===s)){if(te=N(s),ne=N(s,n.draggable),"function"==typeof c){if(c.call(this,e,s,this))return $({sortable:t,rootEl:l,name:"filter",targetEl:s,toEl:i,fromEl:i}),G("filter",t,{evt:e}),void(o&&e.cancelable&&e.preventDefault())}else if(c&&(c=c.split(",").some((function(n){if(n=y(l,n.trim(),i,!1))return $({sortable:t,rootEl:n,name:"filter",targetEl:s,fromEl:i,toEl:i}),G("filter",t,{evt:e}),!0}))))return void(o&&e.cancelable&&e.preventDefault());n.handle&&!y(l,n.handle,i,!1)||this._prepareDragStart(e,r,s)}}},_prepareDragStart:function(e,t,i){var n,o=this,a=o.el,r=o.options,s=a.ownerDocument;if(i&&!Y&&i.parentNode===a){var d=A(i);if(X=a,q=(Y=i).parentNode,Q=Y.nextSibling,J=i,ae=r.group,ze.dragged=Y,se={target:Y,clientX:(t||e).clientX,clientY:(t||e).clientY},de=se.clientX-d.left,ue=se.clientY-d.top,this._lastX=(t||e).clientX,this._lastY=(t||e).clientY,Y.style["will-change"]="all",n=function(){G("delayEnded",o,{evt:e}),ze.eventCanceled?o._onDrop():(o._disableDelayedDragEvents(),!p&&o.nativeDraggable&&(Y.draggable=!0),o._triggerDragStart(e,t),$({sortable:o,name:"choose",originalEvent:e}),k(Y,r.chosenClass,!0))},r.ignore.split(",").forEach((function(e){S(Y,e.trim(),Fe)})),m(s,"dragover",De),m(s,"mousemove",De),m(s,"touchmove",De),m(s,"mouseup",o._onDrop),m(s,"touchend",o._onDrop),m(s,"touchcancel",o._onDrop),p&&this.nativeDraggable&&(this.options.touchStartThreshold=4,Y.draggable=!0),G("delayStart",this,{evt:e}),!r.delay||r.delayOnTouchOnly&&!t||this.nativeDraggable&&(c||l))n();else{if(ze.eventCanceled)return void this._onDrop();m(s,"mouseup",o._disableDelayedDrag),m(s,"touchend",o._disableDelayedDrag),m(s,"touchcancel",o._disableDelayedDrag),m(s,"mousemove",o._delayedDragTouchMoveHandler),m(s,"touchmove",o._delayedDragTouchMoveHandler),r.supportPointer&&m(s,"pointermove",o._delayedDragTouchMoveHandler),o._dragStartTimer=setTimeout(n,r.delay)}}},_delayedDragTouchMoveHandler:function(e){var t=e.touches?e.touches[0]:e;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){Y&&Fe(Y),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var e=this.el.ownerDocument;g(e,"mouseup",this._disableDelayedDrag),g(e,"touchend",this._disableDelayedDrag),g(e,"touchcancel",this._disableDelayedDrag),g(e,"mousemove",this._delayedDragTouchMoveHandler),g(e,"touchmove",this._delayedDragTouchMoveHandler),g(e,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(e,t){t=t||"touch"==e.pointerType&&e,!this.nativeDraggable||t?this.options.supportPointer?m(document,"pointermove",this._onTouchMove):m(document,t?"touchmove":"mousemove",this._onTouchMove):(m(Y,"dragend",this),m(X,"dragstart",this._onDragStart));try{document.selection?Ve((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(e){}},_dragStarted:function(e,t){if(ve=!1,X&&Y){G("dragStarted",this,{evt:t}),this.nativeDraggable&&m(document,"dragover",He);var i=this.options;!e&&k(Y,i.dragClass,!1),k(Y,i.ghostClass,!0),ze.active=this,e&&this._appendGhost(),$({sortable:this,name:"start",originalEvent:t})}else this._nulling()},_emulateDragOver:function(){if(le){this._lastX=le.clientX,this._lastY=le.clientY,Me();for(var e=document.elementFromPoint(le.clientX,le.clientY),t=e;e&&e.shadowRoot&&(e=e.shadowRoot.elementFromPoint(le.clientX,le.clientY))!==t;)t=e;if(Y.parentNode[U]._isOutsideThisEl(e),t)do{if(t[U]&&t[U]._onDragOver({clientX:le.clientX,clientY:le.clientY,target:e,rootEl:t})&&!this.options.dragoverBubble)break;e=t}while(t=t.parentNode);Le()}},_onTouchMove:function(e){if(se){var t=this.options,i=t.fallbackTolerance,n=t.fallbackOffset,o=e.touches?e.touches[0]:e,a=K&&T(K,!0),r=K&&a&&a.a,s=K&&a&&a.d,l=Ae&&be&&R(be),c=(o.clientX-se.clientX+n.x)/(r||1)+(l?l[0]-Ie[0]:0)/(r||1),p=(o.clientY-se.clientY+n.y)/(s||1)+(l?l[1]-Ie[1]:0)/(s||1);if(!ze.active&&!ve){if(i&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))n.right+10||e.clientX<=n.right&&e.clientY>n.bottom&&e.clientX>=n.left:e.clientX>n.right&&e.clientY>n.top||e.clientX<=n.right&&e.clientY>n.bottom+10}(e,o,this)&&!g.animated){if(g===Y)return F(!1);if(g&&a===e.target&&(s=g),s&&(i=A(s)),!1!==Pe(X,a,Y,t,s,i,e,!!s))return P(),a.appendChild(Y),q=a,B(),F(!0)}else if(s.parentNode===a){i=A(s);var b,v,E,x=Y.parentNode!==a,T=!function(e,t,i){var n=i?e.left:e.top,o=i?e.right:e.bottom,a=i?e.width:e.height,r=i?t.left:t.top,s=i?t.right:t.bottom,l=i?t.width:t.height;return n===r||o===s||n+a/2===r+l/2}(Y.animated&&Y.toRect||t,s.animated&&s.toRect||i,o),S=o?"top":"left",C=w(s,"top","top")||w(Y,"top","top"),O=C?C.scrollTop:void 0;if(fe!==s&&(v=i[S],xe=!1,ke=!T&&l.invertSwap||x),b=function(e,t,i,n,o,a,r,s){var l=n?e.clientY:e.clientX,c=n?i.height:i.width,p=n?i.top:i.left,d=n?i.bottom:i.right,u=!1;if(!r)if(s&&gep+c*a/2:ld-ge)return-me}else if(l>p+c*(1-o)/2&&ld-c*a/2)?l>p+c/2?1:-1:0}(e,s,i,o,T?1:l.swapThreshold,null==l.invertedSwapThreshold?l.swapThreshold:l.invertedSwapThreshold,ke,fe===s),0!==b){var R=N(Y);do{R-=b,E=q.children[R]}while(E&&("none"===I(E,"display")||E===K))}if(0===b||E===s)return F(!1);fe=s,me=b;var M=s.nextElementSibling,L=!1,D=Pe(X,a,Y,t,s,i,e,L=1===b);if(!1!==D)return 1!==D&&-1!==D||(L=1===D),Te=!0,setTimeout(Ue,30),P(),L&&!M?a.appendChild(Y):s.parentNode.insertBefore(Y,L?M:s),C&&H(C,0,O-C.scrollTop),q=Y.parentNode,void 0===v||ke||(ge=Math.abs(v-A(s)[S])),B(),F(!0)}if(a.contains(Y))return F(!1)}return!1}function z(l,c){G(l,f,r({evt:e,isOwner:d,axis:o?"vertical":"horizontal",revert:n,dragRect:t,targetRect:i,canSort:u,fromSortable:h,target:s,completed:F,onMove:function(i,n){return Pe(X,a,Y,t,i,A(i),e,n)},changed:B},c))}function P(){z("dragOverAnimationCapture"),f.captureAnimationState(),f!==h&&h.captureAnimationState()}function F(t){return z("dragOverCompleted",{insertion:t}),t&&(d?p._hideClone():p._showClone(f),f!==h&&(k(Y,re?re.options.ghostClass:p.options.ghostClass,!1),k(Y,l.ghostClass,!0)),re!==f&&f!==ze.active?re=f:f===ze.active&&re&&(re=null),h===f&&(f._ignoreWhileAnimating=s),f.animateAll((function(){z("dragOverAnimationComplete"),f._ignoreWhileAnimating=null})),f!==h&&(h.animateAll(),h._ignoreWhileAnimating=null)),(s===Y&&!Y.animated||s===a&&!s.animated)&&(fe=null),l.dragoverBubble||e.rootEl||s===document||(Y.parentNode[U]._isOutsideThisEl(e.target),!t&&De(e)),!l.dragoverBubble&&e.stopPropagation&&e.stopPropagation(),m=!0}function B(){ie=N(Y),oe=N(Y,l.draggable),$({sortable:f,name:"change",toEl:a,newIndex:ie,newDraggableIndex:oe,originalEvent:e})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){g(document,"mousemove",this._onTouchMove),g(document,"touchmove",this._onTouchMove),g(document,"pointermove",this._onTouchMove),g(document,"dragover",De),g(document,"mousemove",De),g(document,"touchmove",De)},_offUpEvents:function(){var e=this.el.ownerDocument;g(e,"mouseup",this._onDrop),g(e,"touchend",this._onDrop),g(e,"pointerup",this._onDrop),g(e,"touchcancel",this._onDrop),g(document,"selectstart",this)},_onDrop:function(e){var t=this.el,i=this.options;ie=N(Y),oe=N(Y,i.draggable),G("drop",this,{evt:e}),q=Y&&Y.parentNode,ie=N(Y),oe=N(Y,i.draggable),ze.eventCanceled||(ve=!1,ke=!1,xe=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),We(this.cloneId),We(this._dragStartId),this.nativeDraggable&&(g(document,"drop",this),g(t,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),d&&I(document.body,"user-select",""),I(Y,"transform",""),e&&(he&&(e.cancelable&&e.preventDefault(),!i.dropBubble&&e.stopPropagation()),K&&K.parentNode&&K.parentNode.removeChild(K),(X===q||re&&"clone"!==re.lastPutMode)&&Z&&Z.parentNode&&Z.parentNode.removeChild(Z),Y&&(this.nativeDraggable&&g(Y,"dragend",this),Fe(Y),Y.style["will-change"]="",he&&!ve&&k(Y,re?re.options.ghostClass:this.options.ghostClass,!1),k(Y,this.options.chosenClass,!1),$({sortable:this,name:"unchoose",toEl:q,newIndex:null,newDraggableIndex:null,originalEvent:e}),X!==q?(ie>=0&&($({rootEl:q,name:"add",toEl:q,fromEl:X,originalEvent:e}),$({sortable:this,name:"remove",toEl:q,originalEvent:e}),$({rootEl:q,name:"sort",toEl:q,fromEl:X,originalEvent:e}),$({sortable:this,name:"sort",toEl:q,originalEvent:e})),re&&re.save()):ie!==te&&ie>=0&&($({sortable:this,name:"update",toEl:q,originalEvent:e}),$({sortable:this,name:"sort",toEl:q,originalEvent:e})),ze.active&&(null!=ie&&-1!==ie||(ie=te,oe=ne),$({sortable:this,name:"end",toEl:q,originalEvent:e}),this.save())))),this._nulling()},_nulling:function(){G("nulling",this),X=Y=q=K=Q=Z=J=ee=se=le=he=ie=oe=te=ne=fe=me=re=ae=ze.dragged=ze.ghost=ze.clone=ze.active=null,Se.forEach((function(e){e.checked=!0})),Se.length=ce=pe=0},handleEvent:function(e){switch(e.type){case"drop":case"dragend":this._onDrop(e);break;case"dragenter":case"dragover":Y&&(this._onDragOver(e),function(e){e.dataTransfer&&(e.dataTransfer.dropEffect="move"),e.cancelable&&e.preventDefault()}(e));break;case"selectstart":e.preventDefault()}},toArray:function(){for(var e,t=[],i=this.el.children,n=0,o=i.length,a=this.options;n1&&(dt.forEach((function(e){n.addAnimationState({target:e,rect:ft?A(e):o}),F(e),e.fromRect=o,t.removeAnimationState(e)})),ft=!1,function(e,t){dt.forEach((function(i,n){var o=t.children[i.sortableIndex+(e?Number(n):0)];o?t.insertBefore(i,o):t.appendChild(i)}))}(!this.options.removeCloneOnHide,i))},dragOverCompleted:function(e){var t=e.sortable,i=e.isOwner,n=e.insertion,o=e.activeSortable,a=e.parentEl,r=e.putSortable,s=this.options;if(n){if(i&&o._hideClone(),ht=!1,s.animation&&dt.length>1&&(ft||!i&&!o.options.sort&&!r)){var l=A(lt,!1,!0,!0);dt.forEach((function(e){e!==lt&&(P(e,l),a.appendChild(e))})),ft=!0}if(!i)if(ft||vt(),dt.length>1){var c=pt;o._showClone(t),o.options.animation&&!pt&&c&&ut.forEach((function(e){o.addAnimationState({target:e,rect:ct}),e.fromRect=ct,e.thisAnimationDuration=null}))}else o._showClone(t)}},dragOverAnimationCapture:function(e){var t=e.dragRect,i=e.isOwner,n=e.activeSortable;if(dt.forEach((function(e){e.thisAnimationDuration=null})),n.options.animation&&!i&&n.multiDrag.isMultiDrag){ct=a({},t);var o=T(lt,!0);ct.top-=o.f,ct.left-=o.e}},dragOverAnimationComplete:function(){ft&&(ft=!1,vt())},drop:function(e){var t=e.originalEvent,i=e.rootEl,n=e.parentEl,o=e.sortable,a=e.dispatchSortableEvent,r=e.oldIndex,s=e.putSortable,l=s||this.sortable;if(t){var c=this.options,p=n.children;if(!mt)if(c.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),k(lt,c.selectedClass,!~dt.indexOf(lt)),~dt.indexOf(lt))dt.splice(dt.indexOf(lt),1),rt=null,j({sortable:o,rootEl:i,name:"deselect",targetEl:lt,originalEvt:t});else{if(dt.push(lt),j({sortable:o,rootEl:i,name:"select",targetEl:lt,originalEvt:t}),t.shiftKey&&rt&&o.el.contains(rt)){var d,u,h=N(rt),f=N(lt);if(~h&&~f&&h!==f)for(f>h?(u=h,d=f):(u=f,d=h+1);u1){var m=A(lt),g=N(lt,":not(."+this.options.selectedClass+")");if(!ht&&c.animation&&(lt.thisAnimationDuration=null),l.captureAnimationState(),!ht&&(c.animation&&(lt.fromRect=m,dt.forEach((function(e){if(e.thisAnimationDuration=null,e!==lt){var t=ft?A(e):m;e.fromRect=t,l.addAnimationState({target:e,rect:t})}}))),vt(),dt.forEach((function(e){p[g]?n.insertBefore(e,p[g]):n.appendChild(e),g++})),r===N(lt))){var b=!1;dt.forEach((function(e){e.sortableIndex===N(e)||(b=!0)})),b&&a("update")}dt.forEach((function(e){F(e)})),l.animateAll()}st=l}(i===n||s&&"clone"!==s.lastPutMode)&&ut.forEach((function(e){e.parentNode&&e.parentNode.removeChild(e)}))}},nullingGlobal:function(){this.isMultiDrag=mt=!1,ut.length=0},destroyGlobal:function(){this._deselectMultiDrag(),g(document,"pointerup",this._deselectMultiDrag),g(document,"mouseup",this._deselectMultiDrag),g(document,"touchend",this._deselectMultiDrag),g(document,"keydown",this._checkKeyDown),g(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(e){if(!(void 0!==mt&&mt||st!==this.sortable||e&&y(e.target,this.options.draggable,this.sortable.el,!1)||e&&0!==e.button))for(;dt.length;){var t=dt[0];k(t,this.options.selectedClass,!1),dt.shift(),j({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:t,originalEvt:e})}},_checkKeyDown:function(e){e.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(e){e.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},a(e,{pluginName:"multiDrag",utils:{select:function(e){var t=e.parentNode[U];t&&t.options.multiDrag&&!~dt.indexOf(e)&&(st&&st!==t&&(st.multiDrag._deselectMultiDrag(),st=t),k(e,t.options.selectedClass,!0),dt.push(e))},deselect:function(e){var t=e.parentNode[U],i=dt.indexOf(e);t&&t.options.multiDrag&&~i&&(k(e,t.options.selectedClass,!1),dt.splice(i,1))}},eventProperties:function(){var e,t=this,i=[],n=[];return dt.forEach((function(e){var o;i.push({multiDragElement:e,index:e.sortableIndex}),o=ft&&e!==lt?-1:ft?N(e,":not(."+t.options.selectedClass+")"):N(e),n.push({multiDragElement:e,index:o})})),{items:(e=dt,function(e){if(Array.isArray(e)){for(var t=0,i=new Array(e.length);t1&&(e=e.charAt(0).toUpperCase()+e.substr(1)),e}}})}function bt(e,t){ut.forEach((function(i,n){var o=t.children[i.sortableIndex+(e?Number(n):0)];o?t.insertBefore(i,o):t.appendChild(i)}))}function vt(){dt.forEach((function(e){e!==lt&&e.parentNode&&e.parentNode.removeChild(e)}))}ze.mount(new function(){function e(){for(var e in this.defaults={scroll:!0,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0},this)"_"===e.charAt(0)&&"function"==typeof this[e]&&(this[e]=this[e].bind(this))}return e.prototype={dragStarted:function(e){var t=e.originalEvent;this.sortable.nativeDraggable?m(document,"dragover",this._handleAutoScroll):this.options.supportPointer?m(document,"pointermove",this._handleFallbackAutoScroll):t.touches?m(document,"touchmove",this._handleFallbackAutoScroll):m(document,"mousemove",this._handleFallbackAutoScroll)},dragOverCompleted:function(e){var t=e.originalEvent;this.options.dragOverBubble||t.rootEl||this._handleAutoScroll(t)},drop:function(){this.sortable.nativeDraggable?g(document,"dragover",this._handleAutoScroll):(g(document,"pointermove",this._handleFallbackAutoScroll),g(document,"touchmove",this._handleFallbackAutoScroll),g(document,"mousemove",this._handleFallbackAutoScroll)),Ze(),Je(),clearTimeout(E),E=void 0},nulling:function(){qe=Ge=je=Qe=Ke=$e=Ye=null,Xe.length=0},_handleFallbackAutoScroll:function(e){this._handleAutoScroll(e,!0)},_handleAutoScroll:function(e,t){var i=this,n=(e.touches?e.touches[0]:e).clientX,o=(e.touches?e.touches[0]:e).clientY,a=document.elementFromPoint(n,o);if(qe=e,t||c||l||d){tt(e,this.options,a,t);var r=M(a,!0);!Qe||Ke&&n===$e&&o===Ye||(Ke&&Ze(),Ke=setInterval((function(){var a=M(document.elementFromPoint(n,o),!0);a!==r&&(r=a,Je()),tt(e,i.options,a,t)}),10),$e=n,Ye=o)}else{if(!this.options.bubbleScroll||M(a,!0)===C())return void Je();tt(e,this.options,M(a,!1),!1)}}},a(e,{pluginName:"scroll",initializeByDefault:!0})}),ze.mount(ot,nt);const yt=ze},946:e=>{"use strict";var t=[];function i(e){for(var i=-1,n=0;n{"use strict";var t={};e.exports=function(e,i){var n=function(e){if(void 0===t[e]){var i=document.querySelector(e);if(window.HTMLIFrameElement&&i instanceof window.HTMLIFrameElement)try{i=i.contentDocument.head}catch(e){i=null}t[e]=i}return t[e]}(e);if(!n)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");n.appendChild(i)}},430:e=>{"use strict";e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},714:(e,t,i)=>{"use strict";e.exports=function(e){var t=i.nc;t&&e.setAttribute("nonce",t)}},571:e=>{"use strict";e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(i){!function(e,t,i){var n="";i.supports&&(n+="@supports (".concat(i.supports,") {")),i.media&&(n+="@media ".concat(i.media," {"));var o=void 0!==i.layer;o&&(n+="@layer".concat(i.layer.length>0?" ".concat(i.layer):""," {")),n+=i.css,o&&(n+="}"),i.media&&(n+="}"),i.supports&&(n+="}");var a=i.sourceMap;a&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),t.styleTagTransform(n,e,t.options)}(t,e,i)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},307:e=>{"use strict";e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}},102:function(e,t,i){var n;"undefined"!=typeof self&&self,n=function(e){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,i),o.l=!0,o.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)i.d(n,o,function(t){return e[t]}.bind(null,o));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s="fb15")}({"01f9":function(e,t,i){"use strict";var n=i("2d00"),o=i("5ca1"),a=i("2aba"),r=i("32e9"),s=i("84f2"),l=i("41a0"),c=i("7f20"),p=i("38fd"),d=i("2b4c")("iterator"),u=!([].keys&&"next"in[].keys()),h="keys",f="values",m=function(){return this};e.exports=function(e,t,i,g,b,v,y){l(i,t,g);var E,x,k,I=function(e){if(!u&&e in A)return A[e];switch(e){case h:case f:return function(){return new i(this,e)}}return function(){return new i(this,e)}},T=t+" Iterator",S=b==f,C=!1,A=e.prototype,w=A[d]||A["@@iterator"]||b&&A[b],O=w||I(b),_=b?S?I("entries"):O:void 0,N="Array"==t&&A.entries||w;if(N&&(k=p(N.call(new e)))!==Object.prototype&&k.next&&(c(k,T,!0),n||"function"==typeof k[d]||r(k,d,m)),S&&w&&w.name!==f&&(C=!0,O=function(){return w.call(this)}),n&&!y||!u&&!C&&A[d]||r(A,d,O),s[t]=O,s[T]=m,b)if(E={values:S?O:I(f),keys:v?O:I(h),entries:_},y)for(x in E)x in A||a(A,x,E[x]);else o(o.P+o.F*(u||C),t,E);return E}},"02f4":function(e,t,i){var n=i("4588"),o=i("be13");e.exports=function(e){return function(t,i){var a,r,s=String(o(t)),l=n(i),c=s.length;return l<0||l>=c?e?"":void 0:(a=s.charCodeAt(l))<55296||a>56319||l+1===c||(r=s.charCodeAt(l+1))<56320||r>57343?e?s.charAt(l):a:e?s.slice(l,l+2):r-56320+(a-55296<<10)+65536}}},"0390":function(e,t,i){"use strict";var n=i("02f4")(!0);e.exports=function(e,t,i){return t+(i?n(e,t).length:1)}},"0bfb":function(e,t,i){"use strict";var n=i("cb7c");e.exports=function(){var e=n(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},"0d58":function(e,t,i){var n=i("ce10"),o=i("e11e");e.exports=Object.keys||function(e){return n(e,o)}},1495:function(e,t,i){var n=i("86cc"),o=i("cb7c"),a=i("0d58");e.exports=i("9e1e")?Object.defineProperties:function(e,t){o(e);for(var i,r=a(t),s=r.length,l=0;s>l;)n.f(e,i=r[l++],t[i]);return e}},"214f":function(e,t,i){"use strict";i("b0c5");var n=i("2aba"),o=i("32e9"),a=i("79e5"),r=i("be13"),s=i("2b4c"),l=i("520a"),c=s("species"),p=!a((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")})),d=function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var i="ab".split(e);return 2===i.length&&"a"===i[0]&&"b"===i[1]}();e.exports=function(e,t,i){var u=s(e),h=!a((function(){var t={};return t[u]=function(){return 7},7!=""[e](t)})),f=h?!a((function(){var t=!1,i=/a/;return i.exec=function(){return t=!0,null},"split"===e&&(i.constructor={},i.constructor[c]=function(){return i}),i[u](""),!t})):void 0;if(!h||!f||"replace"===e&&!p||"split"===e&&!d){var m=/./[u],g=i(r,u,""[e],(function(e,t,i,n,o){return t.exec===l?h&&!o?{done:!0,value:m.call(t,i,n)}:{done:!0,value:e.call(i,t,n)}:{done:!1}})),b=g[0],v=g[1];n(String.prototype,e,b),o(RegExp.prototype,u,2==t?function(e,t){return v.call(e,this,t)}:function(e){return v.call(e,this)})}}},"230e":function(e,t,i){var n=i("d3f4"),o=i("7726").document,a=n(o)&&n(o.createElement);e.exports=function(e){return a?o.createElement(e):{}}},"23c6":function(e,t,i){var n=i("2d95"),o=i("2b4c")("toStringTag"),a="Arguments"==n(function(){return arguments}());e.exports=function(e){var t,i,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?i:a?n(t):"Object"==(r=n(t))&&"function"==typeof t.callee?"Arguments":r}},2621:function(e,t){t.f=Object.getOwnPropertySymbols},"2aba":function(e,t,i){var n=i("7726"),o=i("32e9"),a=i("69a8"),r=i("ca5a")("src"),s=i("fa5b"),l="toString",c=(""+s).split(l);i("8378").inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,i,s){var l="function"==typeof i;l&&(a(i,"name")||o(i,"name",t)),e[t]!==i&&(l&&(a(i,r)||o(i,r,e[t]?""+e[t]:c.join(String(t)))),e===n?e[t]=i:s?e[t]?e[t]=i:o(e,t,i):(delete e[t],o(e,t,i)))})(Function.prototype,l,(function(){return"function"==typeof this&&this[r]||s.call(this)}))},"2aeb":function(e,t,i){var n=i("cb7c"),o=i("1495"),a=i("e11e"),r=i("613b")("IE_PROTO"),s=function(){},l="prototype",c=function(){var e,t=i("230e")("iframe"),n=a.length;for(t.style.display="none",i("fab2").appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-aside.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-aside.vue index ad8c94ae1f4..2e568b3d8d3 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-aside.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-aside.vue @@ -1,66 +1,40 @@