Skip to content

Commit 3353313

Browse files
authored
Merge pull request #55 from ci-plugins/issue_6725
feat: 提供统一的云原生调度接入层 #6725
2 parents d34119b + b5a3f04 commit 3353313

File tree

11 files changed

+420
-6
lines changed

11 files changed

+420
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 插件市场-插件开发 Java SDK(v1.1.4
1+
# 插件市场-插件开发 Java SDK(v1.1.5
22

33

44
使用方式(How to use)
@@ -36,6 +36,7 @@
3636
| v1.1.2 | 修复Java市场插件默认输出至错误流
3737
| v1.1.3 | 获取插件私有配置优化
3838
| v1.1.4 | 升级jackson开源组件漏洞版本
39+
| v1.1.5 | 新增kubernetes构建资源相关api
3940

4041
[TOC]
4142

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.tencent.devops.ci-plugins</groupId>
88
<artifactId>java-plugin-sdk</artifactId>
9-
<version>1.1.4</version>
9+
<version>1.1.5</version>
1010

1111
<inceptionYear>2018-2118</inceptionYear>
1212
<description>bk-ci pipeline plugins sdk for java</description>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.tencent.bk.devops.plugin.api.impl
2+
3+
import com.fasterxml.jackson.core.type.TypeReference
4+
import com.tencent.bk.devops.atom.api.BaseApi
5+
import com.tencent.bk.devops.atom.utils.http.SdkUtils
6+
import com.tencent.bk.devops.atom.utils.json.JsonUtil
7+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchBuildImageReq
8+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchBuildStatusResp
9+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchJobLogResp
10+
import com.tencent.bk.devops.plugin.docker.utils.EnvUtils
11+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchJobReq
12+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchTaskResp
13+
import com.tencent.bk.devops.plugin.pojo.Result
14+
import okhttp3.RequestBody
15+
import org.apache.commons.io.FileUtils
16+
import org.slf4j.LoggerFactory
17+
import java.io.File
18+
import java.io.IOException
19+
import java.nio.charset.Charset
20+
21+
/**
22+
* BCS接口类
23+
*/
24+
class KubernetesBuildApi : BaseApi() {
25+
26+
fun createJob(dispatchJobReq: DispatchJobReq): Result<DispatchTaskResp?> {
27+
val path = "/dispatch-kubernetes/api/build/job/create"
28+
dispatchJobReq.copy(podNameSelector = EnvUtils.getHostName())
29+
val requestBody = RequestBody.create(JSON_CONTENT_TYPE, JsonUtil.toJson(dispatchJobReq))
30+
31+
val request = buildPost(path, requestBody, mutableMapOf("X-DEVOPS-UID" to getUserId()))
32+
val responseContent = request(request, "kubernetes job失败")
33+
logger.debug("create kubernetes job response: $responseContent")
34+
35+
return JsonUtil.fromJson(responseContent, object : TypeReference<Result<DispatchTaskResp?>>() {})
36+
}
37+
38+
fun getJobStatus(jobName: String): Result<DispatchBuildStatusResp> {
39+
val path = "/dispatch-kubernetes/api/build/job/" + jobName + "/status"
40+
val request = buildGet(path, mutableMapOf("X-DEVOPS-UID" to getUserId()))
41+
val responseContent = request(request, "获取job状态失败")
42+
logger.debug("get kubernetes job status response: $responseContent")
43+
return JsonUtil.fromJson(responseContent, object : TypeReference<Result<DispatchBuildStatusResp>>() {})
44+
}
45+
46+
fun getJobLogs(jobName: String, sinceTime: Int): Result<DispatchJobLogResp> {
47+
val path = "/dispatch-kubernetes/api/build/job/" + jobName + "/logs?sinceTime=" + sinceTime
48+
val request = buildGet(path, mutableMapOf("X-DEVOPS-UID" to getUserId()))
49+
val responseContent = request(request, "获取job日志失败")
50+
logger.debug("get kubernetes job logs response: $responseContent")
51+
return JsonUtil.fromJson(responseContent, object : TypeReference<Result<DispatchJobLogResp>>() {})
52+
}
53+
54+
fun getTask(taskId: String): Result<DispatchBuildStatusResp> {
55+
val path = "/dispatch-kubernetes/api/build/task/status?taskId=" + taskId
56+
val request = buildGet(path, mutableMapOf("X-DEVOPS-UID" to getUserId()))
57+
val responseContent = request(request, "获取task信息失败")
58+
logger.debug("get kubernetes task response: $responseContent")
59+
return JsonUtil.fromJson(responseContent, object : TypeReference<Result<DispatchBuildStatusResp>>() {})
60+
}
61+
62+
fun dockerBuildAndPush(dispatchBuildImageReq: DispatchBuildImageReq): Result<DispatchTaskResp?> {
63+
val path = "/dispatch-kubernetes/api/build/image/buildPush"
64+
dispatchBuildImageReq.copy(podName = EnvUtils.getHostName())
65+
val requestBody = RequestBody.create(JSON_CONTENT_TYPE, JsonUtil.toJson(dispatchBuildImageReq))
66+
67+
val request = buildPost(path, requestBody, mutableMapOf("X-DEVOPS-UID" to getUserId()))
68+
val responseContent = request(request, "kubernetes docker build失败")
69+
logger.debug("docker build response: $responseContent")
70+
71+
return JsonUtil.fromJson(responseContent, object : TypeReference<Result<DispatchTaskResp?>>() {})
72+
}
73+
74+
private fun getUserId(): String {
75+
val inputJson: String?
76+
try {
77+
inputJson = FileUtils.readFileToString(
78+
File(SdkUtils.getDataDir() + "/" + SdkUtils.getInputFile()),
79+
Charset.defaultCharset()
80+
)
81+
} catch (e: IOException) {
82+
logger.error("parse inputJson throw Exception", e)
83+
return ""
84+
}
85+
86+
val inputMap: Map<String, Any> = JsonUtil.fromJson(inputJson,
87+
object : TypeReference<MutableMap<String, Any>>() {})
88+
return inputMap["pipeline.start.user.name"] as String
89+
}
90+
91+
companion object {
92+
private val logger = LoggerFactory.getLogger(KubernetesBuildApi::class.java)
93+
}
94+
}

src/main/kotlin/com/tencent/bk/devops/plugin/docker/DockerApi.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ open class DockerApi : BaseApi() {
2929
): Result<DockerRunResponse> {
3030
try {
3131
val property = System.getenv("devops_slave_model")
32+
val jobPoolType = System.getenv("JOB_POOL")
3233

3334
var response = dockerRunCustomize(projectId, pipelineId, buildId, param)
3435

3536
if (response == null) {
36-
response = when (property) {
37-
"docker" -> CommonExecutor.execute(projectId, pipelineId, buildId, param, taskId)
37+
response = when {
38+
"docker" == property -> CommonExecutor.execute(projectId, pipelineId, buildId, param, taskId)
39+
"KUBERNETES" == jobPoolType -> KubernetesExecutor.execute(param)
3840
else -> ThirdPartExecutor.execute(param)
3941
}
4042
}
@@ -61,12 +63,14 @@ open class DockerApi : BaseApi() {
6163
): Result<DockerRunLogResponse> {
6264
try {
6365
val property = System.getenv("devops_slave_model")
66+
val jobPoolType = System.getenv("JOB_POOL")
6467

6568
var response = dockerRunGetLogCustomize(projectId, pipelineId, buildId, param)
6669

6770
if (response == null) {
68-
response = when (property) {
69-
"docker" -> CommonExecutor.getLogs(projectId, pipelineId, buildId, param)
71+
response = when {
72+
"docker" == property -> CommonExecutor.getLogs(projectId, pipelineId, buildId, param)
73+
"KUBERNETES" == jobPoolType -> KubernetesExecutor.getLogs(param)
7074
else -> ThirdPartExecutor.getLogs(param)
7175
}
7276
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package com.tencent.bk.devops.plugin.docker
2+
3+
import com.tencent.bk.devops.plugin.api.impl.KubernetesBuildApi
4+
import com.tencent.bk.devops.plugin.docker.pojo.DockerRunLogRequest
5+
import com.tencent.bk.devops.plugin.docker.pojo.DockerRunLogResponse
6+
import com.tencent.bk.devops.plugin.docker.pojo.DockerRunRequest
7+
import com.tencent.bk.devops.plugin.docker.pojo.DockerRunResponse
8+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchBuildStatusResp
9+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DispatchJobReq
10+
import com.tencent.bk.devops.plugin.pojo.kubernetes.DockerRegistry
11+
import com.tencent.bk.devops.plugin.pojo.kubernetes.JobParam
12+
import com.tencent.bk.devops.plugin.docker.pojo.common.DockerStatus
13+
import com.tencent.bk.devops.plugin.docker.utils.EnvUtils
14+
import org.apache.commons.lang3.RandomStringUtils
15+
import org.apache.tools.ant.types.Commandline
16+
import org.slf4j.LoggerFactory
17+
18+
object KubernetesExecutor {
19+
private const val VOLUME_SERVER = "volume_server"
20+
private const val VOLUME_PATH = "volume_path"
21+
private const val VOLUME_MOUNT_PATH = "volume_mount_path"
22+
23+
private val logger = LoggerFactory.getLogger(KubernetesExecutor::class.java)
24+
25+
fun execute(request: DockerRunRequest): DockerRunResponse {
26+
val startTimeStamp = System.currentTimeMillis() / 1000
27+
val jobRequest = getJobRequest(request)
28+
val task = KubernetesBuildApi().createJob(jobRequest).data
29+
30+
val extraOptionMap = mapOf(
31+
"kubernetesTaskId" to task?.taskId.toString(),
32+
"bcsJobName" to jobRequest.alias,
33+
"startTimeStamp" to startTimeStamp.toString()
34+
)
35+
36+
return DockerRunResponse(
37+
extraOptions = request.extraOptions?.plus(extraOptionMap) ?: extraOptionMap
38+
)
39+
}
40+
41+
fun getLogs(param: DockerRunLogRequest): DockerRunLogResponse {
42+
val extraOptions = param.extraOptions.toMutableMap()
43+
44+
// get task status
45+
val taskId = param.extraOptions["kubernetesTaskId"] ?: throw RuntimeException("kubernetesTaskId is null")
46+
val taskStatusFlag = param.extraOptions["taskStatusFlag"]
47+
if (taskStatusFlag.isNullOrBlank() || taskStatusFlag == DockerStatus.running) {
48+
val taskStatus = KubernetesBuildApi().getTask(taskId).data
49+
taskStatus.let {
50+
if (taskStatus!!.status == "failed") {
51+
return DockerRunLogResponse(
52+
status = DockerStatus.failure,
53+
message = "get task status fail",
54+
extraOptions = extraOptions
55+
)
56+
}
57+
if (taskStatus.status != "succeeded") {
58+
return DockerRunLogResponse(
59+
status = DockerStatus.running,
60+
message = "get task status...",
61+
extraOptions = extraOptions
62+
)
63+
}
64+
}
65+
}
66+
extraOptions["taskStatusFlag"] = DockerStatus.success
67+
68+
// get job status
69+
val jobStatusFlag = param.extraOptions["jobStatusFlag"]
70+
val jobName = param.extraOptions["bcsJobName"] ?: throw RuntimeException("bcsJobName is null")
71+
var jobStatusResp: DispatchBuildStatusResp? = null
72+
if (jobStatusFlag.isNullOrBlank() || jobStatusFlag == DockerStatus.running) {
73+
jobStatusResp = KubernetesBuildApi().getJobStatus(jobName).data!!
74+
val jobStatus = jobStatusResp.status
75+
if ("failed" != jobStatus && "succeeded" != jobStatus && "running" != jobStatus) {
76+
return DockerRunLogResponse(
77+
status = DockerStatus.running,
78+
message = "get job status...",
79+
extraOptions = extraOptions
80+
)
81+
}
82+
}
83+
extraOptions["jobStatusFlag"] = DockerStatus.success
84+
85+
// actual get log logic
86+
val startTimeStamp = extraOptions["startTimeStamp"]?.toInt() ?: (System.currentTimeMillis() / 1000).toInt()
87+
val logs = mutableListOf<String>()
88+
89+
val logResult = KubernetesBuildApi().getJobLogs(jobName, startTimeStamp).data!!
90+
91+
if ((logResult.log != null && logResult.log.isNotEmpty()) || !logResult.errorMsg.isNullOrBlank()) {
92+
extraOptions["startTimeStamp"] = (startTimeStamp + param.timeGap).toString()
93+
logResult.log.let {
94+
logs.addAll(logResult.log ?: emptyList())
95+
}
96+
97+
logResult.errorMsg?.let {
98+
logs.add(logResult.errorMsg)
99+
}
100+
}
101+
102+
if (jobStatusResp == null) {
103+
jobStatusResp = KubernetesBuildApi().getJobStatus(jobName).data
104+
}
105+
val finalStatus = jobStatusResp
106+
107+
if (finalStatus!!.status in listOf("failed", "succeeded")) {
108+
logger.info("final job status data: $jobStatusResp")
109+
Thread.sleep(6000)
110+
val finalLogs = KubernetesBuildApi().getJobLogs(jobName, startTimeStamp + 6).data!!
111+
if (finalStatus.status == "failed") {
112+
return DockerRunLogResponse(
113+
log = logs.plus(finalLogs.errorMsg ?: ""),
114+
status = DockerStatus.failure,
115+
message = "docker run fail...",
116+
extraOptions = extraOptions
117+
)
118+
}
119+
return DockerRunLogResponse(
120+
log = logs.plus(finalLogs?.log ?: emptyList()),
121+
status = DockerStatus.success,
122+
message = "docker run success...",
123+
extraOptions = extraOptions
124+
)
125+
}
126+
127+
return DockerRunLogResponse(
128+
log = logs,
129+
status = DockerStatus.running,
130+
message = "get log...",
131+
extraOptions = extraOptions
132+
)
133+
}
134+
135+
private fun getJobRequest(param: DockerRunRequest): DispatchJobReq {
136+
with(param) {
137+
// get job param
138+
val cmdTmp = mutableListOf<String>()
139+
command.forEach {
140+
cmdTmp.add(it.removePrefix("\"").removeSuffix("\"").removePrefix("\'")
141+
.removeSuffix("\'"))
142+
}
143+
val cmd = if (cmdTmp.size == 1) {
144+
Commandline.translateCommandline(cmdTmp.first()).toList()
145+
} else {
146+
cmdTmp
147+
}
148+
val jobParam = JobParam(
149+
env = envMap,
150+
command = cmd,
151+
labels = labels,
152+
ipEnabled = ipEnabled
153+
)
154+
155+
if (jobParam.nfsVolume == null) {
156+
val volumeServer = System.getenv(VOLUME_SERVER)
157+
if (!volumeServer.isNullOrBlank()) {
158+
jobParam.nfsVolume = listOf(
159+
JobParam.NfsVolume(
160+
System.getenv(VOLUME_SERVER),
161+
System.getenv(VOLUME_PATH),
162+
System.getenv(VOLUME_MOUNT_PATH)
163+
)
164+
)
165+
}
166+
}
167+
168+
// get docker image host & path
169+
val imagePair = getImagePair(param.imageName)
170+
171+
// get user pass param
172+
val registry = DockerRegistry(
173+
host = imagePair.first,
174+
username = param.dockerLoginUsername,
175+
password = param.dockerLoginPassword
176+
)
177+
178+
return DispatchJobReq(
179+
alias = "job-${System.currentTimeMillis()}-${RandomStringUtils.randomAlphabetic(8).toLowerCase()}",
180+
activeDeadlineSeconds = 86400,
181+
image = imageName,
182+
registry = registry,
183+
params = jobParam,
184+
podNameSelector = EnvUtils.getHostName()
185+
)
186+
}
187+
}
188+
189+
private fun getImagePair(imageName: String): Pair<String, String> {
190+
val targetImageRepo = imageName.split("/").first()
191+
val targetImageName = imageName.removePrefix(targetImageRepo).removeSuffix("/")
192+
return Pair(targetImageRepo, targetImageName)
193+
}
194+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.
3+
*
4+
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
5+
*
6+
* BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.
7+
*
8+
* A copy of the MIT License is included in this file.
9+
*
10+
*
11+
* Terms of the MIT License:
12+
* ---------------------------------------------------
13+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
14+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
15+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
16+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
17+
*
18+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
19+
* the Software.
20+
*
21+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
22+
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
23+
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26+
*/
27+
28+
package com.tencent.bk.devops.plugin.pojo.kubernetes
29+
30+
data class DispatchBuildImageReq(
31+
val jobName: String,
32+
val imageName: List<String>,
33+
val registry: List<Registry>,
34+
val fromRegistry: List<Registry>,
35+
val buildArgs: Map<String, Any>,
36+
val workPath: String,
37+
val dockerFile: String,
38+
val podName: String
39+
)
40+
41+
data class Registry(
42+
val host: String,
43+
val username: String,
44+
val password: String
45+
)

0 commit comments

Comments
 (0)