diff --git a/src/backend/ci/core/dispatch/api-dispatch-kubernetes/build.gradle.kts b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/build.gradle.kts index d6787a1f571..d696b7475a9 100644 --- a/src/backend/ci/core/dispatch/api-dispatch-kubernetes/build.gradle.kts +++ b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { api(project(":core:common:common-api")) api(project(":core:common:common-web")) api(project(":core:buildless:api-buildless")) + api("io.fabric8:kubernetes-client") } plugins { diff --git a/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/base/KubernetesRepo.kt b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/base/KubernetesRepo.kt new file mode 100644 index 00000000000..83c5a60f1ba --- /dev/null +++ b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/base/KubernetesRepo.kt @@ -0,0 +1,42 @@ +/* + * 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.dispatch.kubernetes.pojo.base + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "k8s仓库信息") +data class KubernetesRepo( + @get:Schema(title = "仓库地址", required = true) + val registryUrl: String, + @get:Schema(title = "用户名", required = true) + val username: String, + @get:Schema(title = "密码", required = true) + val password: String, + @get:Schema(title = "邮箱", required = false) + val email: String? +) diff --git a/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/common/ErrorCodeEnum.kt b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/common/ErrorCodeEnum.kt index 5f7b282b3fd..ca84eb0f29f 100644 --- a/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/common/ErrorCodeEnum.kt +++ b/src/backend/ci/core/dispatch/api-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/pojo/common/ErrorCodeEnum.kt @@ -303,6 +303,11 @@ enum class ErrorCodeEnum( ErrorType.USER, 2126054, "已超过DevCloud创建Job环境上限." + ), + KUBERNETES_CREATE_RESOURCE_FAIL( + ErrorType.SYSTEM, + 2126055, + "kubernetes创建{0}资源失败,原因:{1}" ); fun getErrorMessage(): String { diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/DeploymentClient.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/DeploymentClient.kt new file mode 100644 index 00000000000..6eb1cf4505c --- /dev/null +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/DeploymentClient.kt @@ -0,0 +1,116 @@ +/* + * 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.dispatch.kubernetes.client + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.api.util.OkhttpUtils +import com.tencent.devops.common.dispatch.sdk.BuildFailureException +import com.tencent.devops.dispatch.kubernetes.pojo.KubernetesResult +import com.tencent.devops.dispatch.kubernetes.pojo.common.ErrorCodeEnum +import io.fabric8.kubernetes.api.model.apps.Deployment +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +class DeploymentClient @Autowired constructor( + private val objectMapper: ObjectMapper, + private val clientCommon: KubernetesClientCommon +) { + + companion object { + private val logger = LoggerFactory.getLogger(DeploymentClient::class.java) + } + + fun createDeployment( + userId: String, + namespace: String, + deployment: Deployment + ): KubernetesResult { + val url = "/api/namespace/$namespace/deployments" + val body = JsonUtil.toJson(deployment) + logger.info("Create deployment request url: $url, body: $body") + val request = clientCommon.microBaseRequest(url).post( + RequestBody.create( + "application/json; charset=utf-8".toMediaTypeOrNull(), + body + ) + ).build() + val responseBody = OkhttpUtils.doHttp(request).body!!.string() + logger.info("Create deployment response: ${JsonUtil.toJson(responseBody)}") + return JsonUtil.getObjectMapper().readValue(responseBody) + } + + fun getDeploymentByName( + userId: String, + namespace: String, + deploymentName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/deployments/$deploymentName" + val request = clientCommon.microBaseRequest(url).get().build() + logger.info("Get deployment: $deploymentName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Get deployment: $deploymentName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to get deployment,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } + + fun deleteDeploymentByName( + userId: String, + namespace: String, + deploymentName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/deployments/$deploymentName" + val request = clientCommon.microBaseRequest(url).delete().build() + logger.info("Delete deployment: $deploymentName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Delete deployment: $deploymentName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to delete deployment,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } +} diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/IngressClient.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/IngressClient.kt new file mode 100644 index 00000000000..9cf45037004 --- /dev/null +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/IngressClient.kt @@ -0,0 +1,116 @@ +/* + * 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.dispatch.kubernetes.client + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.api.util.OkhttpUtils +import com.tencent.devops.common.dispatch.sdk.BuildFailureException +import com.tencent.devops.dispatch.kubernetes.pojo.KubernetesResult +import com.tencent.devops.dispatch.kubernetes.pojo.common.ErrorCodeEnum +import io.fabric8.kubernetes.api.model.networking.v1.Ingress +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +class IngressClient @Autowired constructor( + private val objectMapper: ObjectMapper, + private val clientCommon: KubernetesClientCommon +) { + + companion object { + private val logger = LoggerFactory.getLogger(IngressClient::class.java) + } + + fun createIngress( + userId: String, + namespace: String, + ingress: Ingress + ): KubernetesResult { + val url = "/api/namespace/$namespace/ingress" + val body = JsonUtil.toJson(ingress) + logger.info("Create ingress request url: $url, body: $body") + val request = clientCommon.microBaseRequest(url).post( + RequestBody.create( + "application/json; charset=utf-8".toMediaTypeOrNull(), + body + ) + ).build() + val responseBody = OkhttpUtils.doHttp(request).body!!.string() + logger.info("Create ingress response: ${JsonUtil.toJson(responseBody)}") + return JsonUtil.getObjectMapper().readValue(responseBody) + } + + fun getIngressByName( + userId: String, + namespace: String, + ingressName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/ingress/$ingressName" + val request = clientCommon.microBaseRequest(url).get().build() + logger.info("Get ingress: $ingressName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Get ingress: $ingressName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to get ingress,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } + + fun deleteIngressByName( + userId: String, + namespace: String, + ingressName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/ingress/$ingressName" + val request = clientCommon.microBaseRequest(url).delete().build() + logger.info("Delete ingress: $ingressName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Delete ingress: $ingressName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to delete ingress,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } +} diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/KubernetesClientCommon.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/KubernetesClientCommon.kt index 9ccfc7b3f22..65ae68fe873 100644 --- a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/KubernetesClientCommon.kt +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/KubernetesClientCommon.kt @@ -52,6 +52,10 @@ class KubernetesClientCommon @Autowired constructor( return Request.Builder().url(commonService.getProxyUrl(kubernetesApiUrl + url)).headers(headers(headers)) } + fun microBaseRequest(url: String, headers: Map? = null): Request.Builder { + return Request.Builder().url(kubernetesApiUrl + url).headers(headers(headers)) + } + fun headers(otherHeaders: Map? = null): Headers { val result = mutableMapOf() diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/SecretClient.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/SecretClient.kt new file mode 100644 index 00000000000..17780a2c779 --- /dev/null +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/SecretClient.kt @@ -0,0 +1,170 @@ +/* + * 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.dispatch.kubernetes.client + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.api.util.OkhttpUtils +import com.tencent.devops.common.dispatch.sdk.BuildFailureException +import com.tencent.devops.dispatch.kubernetes.pojo.KubernetesResult +import com.tencent.devops.dispatch.kubernetes.pojo.base.KubernetesRepo +import com.tencent.devops.dispatch.kubernetes.pojo.common.ErrorCodeEnum +import io.fabric8.kubernetes.api.model.Secret +import io.fabric8.kubernetes.api.model.SecretBuilder +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import org.apache.commons.codec.binary.Base64 +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +class SecretClient @Autowired constructor( + private val objectMapper: ObjectMapper, + private val clientCommon: KubernetesClientCommon +) { + + companion object { + private val logger = LoggerFactory.getLogger(SecretClient::class.java) + } + + fun createSecret( + userId: String, + namespace: String, + secret: Secret + ): KubernetesResult { + val url = "/api/namespace/$namespace/secrets" + val body = JsonUtil.toJson(secret) + logger.info("Create secret request url: $url, body: $body") + val request = clientCommon.microBaseRequest(url).post( + RequestBody.create( + "application/json; charset=utf-8".toMediaTypeOrNull(), + body + ) + ).build() + val responseBody = OkhttpUtils.doHttp(request).body!!.string() + logger.info("Create secret response: ${JsonUtil.toJson(responseBody)}") + return JsonUtil.getObjectMapper().readValue(responseBody) + } + + fun getSecretByName( + userId: String, + namespace: String, + secretName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/secrets/$secretName" + val request = clientCommon.microBaseRequest(url).get().build() + logger.info("Get secret: $secretName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Get secret: $secretName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to get secret,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } + + fun deleteSecretByName( + userId: String, + namespace: String, + secretName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/secrets/$secretName" + val request = clientCommon.microBaseRequest(url).delete().build() + logger.info("Delete secret: $secretName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Delete secret: $secretName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to delete secret,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } + + /** + * 创建k8s拉取镜像secret + * @param namespaceName 命名空间名称 + * @param secretName 秘钥名称 + * @param kubernetesRepoInfo k8s仓库信息 + */ + fun createImagePullSecret( + userId: String, + secretName: String, + namespaceName: String, + kubernetesRepoInfo: KubernetesRepo + ) { + var secret = getSecretByName(userId, namespaceName, secretName).data + logger.info("the secret is: $secret") + if (secret == null) { + val secretData: HashMap = HashMap(1) + val basicAuth = String( + Base64.encodeBase64("${kubernetesRepoInfo.username}:${kubernetesRepoInfo.password}".toByteArray()) + ) + var dockerCfg = String.format( + "{ " + + " \"auths\": { " + + " \"%s\": { " + + " \"username\": \"%s\", " + + " \"password\": \"%s\", " + + " \"email\": \"%s\", " + + " \"auth\": \"%s\" " + + " } " + + " } " + + "}", + kubernetesRepoInfo.registryUrl, + kubernetesRepoInfo.username, + kubernetesRepoInfo.password, + kubernetesRepoInfo.email, + basicAuth + ) + dockerCfg = String(Base64.encodeBase64(dockerCfg.toByteArray(Charsets.UTF_8)), Charsets.UTF_8) + secretData[".dockerconfigjson"] = dockerCfg + secret = SecretBuilder() + .withNewMetadata() + .withName(secretName) + .withNamespace(namespaceName) + .endMetadata() + .addToData(secretData) + .withType("kubernetes.io/dockerconfigjson") + .build() + createSecret(userId, namespaceName, secret) + logger.info("create new secret: $secret") + } + } +} diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/ServiceClient.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/ServiceClient.kt new file mode 100644 index 00000000000..826da1da8a8 --- /dev/null +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/client/ServiceClient.kt @@ -0,0 +1,116 @@ +/* + * 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.dispatch.kubernetes.client + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.api.util.OkhttpUtils +import com.tencent.devops.common.dispatch.sdk.BuildFailureException +import com.tencent.devops.dispatch.kubernetes.pojo.KubernetesResult +import com.tencent.devops.dispatch.kubernetes.pojo.common.ErrorCodeEnum +import io.fabric8.kubernetes.api.model.Service +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +class ServiceClient @Autowired constructor( + private val objectMapper: ObjectMapper, + private val clientCommon: KubernetesClientCommon +) { + + companion object { + private val logger = LoggerFactory.getLogger(ServiceClient::class.java) + } + + fun createService( + userId: String, + namespace: String, + service: Service + ): KubernetesResult { + val url = "/api/namespace/$namespace/services" + val body = JsonUtil.toJson(service) + logger.info("$userId Create service request url: $url, body: $body") + val request = clientCommon.microBaseRequest(url).post( + RequestBody.create( + "application/json; charset=utf-8".toMediaTypeOrNull(), + body + ) + ).build() + val responseBody = OkhttpUtils.doHttp(request).body!!.string() + logger.info("Create service response: ${JsonUtil.toJson(responseBody)}") + return JsonUtil.getObjectMapper().readValue(responseBody) + } + + fun getServiceByName( + userId: String, + namespace: String, + serviceName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/services/$serviceName" + val request = clientCommon.microBaseRequest(url).get().build() + logger.info("Get service: $serviceName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Get service: $serviceName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to get service,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } + + fun deleteServiceByName( + userId: String, + namespace: String, + serviceName: String + ): KubernetesResult { + val url = "/api/namespace/$namespace/services/$serviceName" + val request = clientCommon.microBaseRequest(url).delete().build() + logger.info("Delete service: $serviceName request url: $url, userId: $userId") + OkhttpUtils.doHttp(request).use { response -> + val responseContent = response.body!!.string() + logger.info("Delete service: $serviceName response: $responseContent") + if (!response.isSuccessful) { + throw BuildFailureException( + errorType = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorType, + errorCode = ErrorCodeEnum.BCS_SYSTEM_ERROR.errorCode, + formatErrorMessage = ErrorCodeEnum.BCS_SYSTEM_ERROR.getErrorMessage(), + errorMessage = "Fail to delete service,http response code: ${response.code}" + ) + } + return objectMapper.readValue(responseContent) + } + } +} diff --git a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/config/KubernetesBeanConfiguration.kt b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/config/KubernetesBeanConfiguration.kt index 9d28886d8d8..fed7be9143e 100644 --- a/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/config/KubernetesBeanConfiguration.kt +++ b/src/backend/ci/core/dispatch/biz-dispatch-kubernetes/src/main/kotlin/com/tencent/devops/dispatch/kubernetes/config/KubernetesBeanConfiguration.kt @@ -35,10 +35,14 @@ import com.tencent.devops.dispatch.kubernetes.bcs.client.BcsJobClient import com.tencent.devops.dispatch.kubernetes.bcs.client.BcsTaskClient import com.tencent.devops.dispatch.kubernetes.bcs.service.BcsContainerService import com.tencent.devops.dispatch.kubernetes.bcs.service.BcsJobService +import com.tencent.devops.dispatch.kubernetes.client.DeploymentClient +import com.tencent.devops.dispatch.kubernetes.client.IngressClient import com.tencent.devops.dispatch.kubernetes.client.KubernetesBuilderClient import com.tencent.devops.dispatch.kubernetes.client.KubernetesClientCommon import com.tencent.devops.dispatch.kubernetes.client.KubernetesJobClient import com.tencent.devops.dispatch.kubernetes.client.KubernetesTaskClient +import com.tencent.devops.dispatch.kubernetes.client.SecretClient +import com.tencent.devops.dispatch.kubernetes.client.ServiceClient import com.tencent.devops.dispatch.kubernetes.components.LogsPrinter import com.tencent.devops.dispatch.kubernetes.interfaces.CommonService import com.tencent.devops.dispatch.kubernetes.service.CoreCommonService @@ -165,4 +169,36 @@ class KubernetesBeanConfiguration { fun bcsJobService( bcsJobClient: BcsJobClient ) = BcsJobService(bcsJobClient) + + @Bean + @Primary + @ConditionalOnProperty(prefix = "kubernetes", name = ["enable"], havingValue = "true") + fun deploymentClient( + objectMapper: ObjectMapper, + clientCommon: KubernetesClientCommon + ) = DeploymentClient(objectMapper, clientCommon) + + @Bean + @Primary + @ConditionalOnProperty(prefix = "kubernetes", name = ["enable"], havingValue = "true") + fun ingressClient( + objectMapper: ObjectMapper, + clientCommon: KubernetesClientCommon + ) = IngressClient(objectMapper, clientCommon) + + @Bean + @Primary + @ConditionalOnProperty(prefix = "kubernetes", name = ["enable"], havingValue = "true") + fun secretClient( + objectMapper: ObjectMapper, + clientCommon: KubernetesClientCommon + ) = SecretClient(objectMapper, clientCommon) + + @Bean + @Primary + @ConditionalOnProperty(prefix = "kubernetes", name = ["enable"], havingValue = "true") + fun serviceClient( + objectMapper: ObjectMapper, + clientCommon: KubernetesClientCommon + ) = ServiceClient(objectMapper, clientCommon) } 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..5addf12b76b 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 @@ -127,6 +127,7 @@ object StoreMessageCode { const val USER_SERVICE_PROJECT_NOT_PERMISSION = "2120408" // 研发商店:选中调试项目无创建流水线权限 const val USER_SERVICE_NOT_EXIST = "2120409" // 研发商店:扩展服务不存在{0} const val USER_ITEM_SERVICE_USED_IS_NOT_ALLOW_DELETE = "2120410" // 研发商店:扩展点下还有可用的扩展服务,不能删除 + const val USER_SERVICE_NOT_DEPLOY = "2120411" // 研发商店:用户扩展服务未部署 const val USER_PRAISE_IS_INVALID = "2120901" // 研发商店:你已点赞过 const val USER_PROJECT_IS_NOT_ALLOW_INSTALL = "2120902" // 研发商店:你没有权限将组件安装到项目:{0} 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 90dea83bb4d..48a1f63af11 100644 --- a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/apis.go @@ -37,6 +37,10 @@ func InitApis(r *gin.Engine, handlers ...gin.HandlerFunc) { initBuilderApis(apis) initTasksApis(apis) initBuildLessApis(apis) + initDeploymentApis(apis) + initServiceApis(apis) + initIngressApis(apis) + initSecretApis(apis) } func ok(c *gin.Context, data interface{}) { diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_deployment.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_deployment.go new file mode 100644 index 00000000000..f52b38fa388 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_deployment.go @@ -0,0 +1,121 @@ +package apis + +import ( + "disaptch-k8s-manager/pkg/kubeclient" + "disaptch-k8s-manager/pkg/logs" + "fmt" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" + "net/http" +) + +const ( + deploymentPrefix = "/namespace/:namespace/deployments" +) + +func initDeploymentApis(r *gin.RouterGroup) { + deployment := r.Group(deploymentPrefix) + { + deployment.GET("/:deploymentName", getDeployment) + deployment.POST("", createDeployment) + deployment.DELETE("/:deploymentName", deleteDeployment) + } +} + +// @Tags deployment +// @Summary 获取deployment状态 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param deploymentName path string true "deployment名称" +// @Success 200 {object} types.Result{data=appsv1.Deployment} "deployment详情" +// @Router /deployment/{deploymentName} [get] +func getDeployment(c *gin.Context) { + namespace := c.Param("namespace") + deploymentName := c.Param("deploymentName") + + if !checkDeploymentParams(c, deploymentName) { + return + } + + deployment, err := kubeclient.GetNativeDeployment(namespace, deploymentName) + if err != nil { + okFail(c, http.StatusInternalServerError, err) + return + } + + ok(c, deployment) +} + +// @Tags deployment +// @Summary 创建deployment负载资源 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param deployment body appsv1.Deployment true "deployment负载信息" +// @Success 200 {object} "" +// @Router /deployment [post] +func createDeployment(c *gin.Context) { + namespace := c.Param("namespace") + + deployment := &appsv1.Deployment{} + + if err := c.BindJSON(deployment); err != nil { + fail(c, http.StatusBadRequest, err) + return + } + + deploymentInfo, _ := kubeclient.GetNativeDeployment(namespace, deployment.Name) + if deploymentInfo != nil { + logs.Info(fmt.Sprintf("Deployment: %s exist, update.", deployment.Name)) + updateErr := kubeclient.UpdateNativeDeployment(namespace, deployment) + if updateErr != nil { + fail(c, http.StatusInternalServerError, updateErr) + return + } + } else { + logs.Info(fmt.Sprintf("Deployment: %s not exist, create.", deployment.Name)) + createErr := kubeclient.CreateNativeDeployment(namespace, deployment) + if createErr != nil { + fail(c, http.StatusInternalServerError, createErr) + return + } + } + + ok(c, "") +} + +// @Tags deployment +// @Summary 删除deployment +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param deploymentName path string true "deployment名称" +// @Success 200 {object} types.Result{data=""} "" +// @Router /deployment/{deploymentName} [delete] +func deleteDeployment(c *gin.Context) { + namespace := c.Param("namespace") + deploymentName := c.Param("deploymentName") + + if !checkDeploymentParams(c, deploymentName) { + return + } + + err := kubeclient.DeleteNativeDeployment(namespace, deploymentName) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, "") +} + +func checkDeploymentParams(c *gin.Context, deploymentName string) bool { + if deploymentName == "" { + fail(c, http.StatusBadRequest, errors.New("deployment名称不能为空")) + return false + } + + return true +} diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_ingress.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_ingress.go new file mode 100644 index 00000000000..055e5bf9343 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_ingress.go @@ -0,0 +1,116 @@ +package apis + +import ( + "disaptch-k8s-manager/pkg/kubeclient" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + networkv1 "k8s.io/api/networking/v1" + "net/http" +) + +const ( + ingressPrefix = "/namespace/:namespace/ingress" +) + +func initIngressApis(r *gin.RouterGroup) { + ingress := r.Group(ingressPrefix) + { + ingress.GET("/:ingressName", getIngress) + ingress.POST("", createIngress) + ingress.DELETE("/:ingressName", deleteIngress) + } +} + +// @Tags ingress +// @Summary 获取ingress详情 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param ingressName path string true "ingress名称" +// @Success 200 {object} types.Result{data=networkv1.ingress} "ingress详情" +// @Router /ingress/{ingressName} [get] +func getIngress(c *gin.Context) { + namespace := c.Param("namespace") + ingressName := c.Param("ingressName") + + if !checkIngressName(c, ingressName) { + return + } + + ingress, err := kubeclient.GetIngress(namespace, ingressName) + if err != nil { + okFail(c, http.StatusInternalServerError, err) + return + } + + ok(c, ingress) +} + +// @Tags ingress +// @Summary 创建ingress负载资源 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param ingress body networkv1.ingress true "ingress负载信息" +// @Success 200 {object} "" +// @Router /ingress [post] +func createIngress(c *gin.Context) { + namespace := c.Param("namespace") + ingress := &networkv1.Ingress{} + + if err := c.BindJSON(ingress); err != nil { + fail(c, http.StatusBadRequest, err) + return + } + + ingressInfo, _ := kubeclient.GetIngress(namespace, ingress.Name) + if ingressInfo != nil { + err := kubeclient.UpdateIngress(namespace, ingress) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + } else { + err := kubeclient.CreateIngress(namespace, ingress) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + } + + ok(c, "") +} + +// @Tags ingress +// @Summary 删除ingress +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param ingressName path string true "ingress名称" +// @Success 200 {object} types.Result{data=""} "" +// @Router /ingress/{ingressName} [delete] +func deleteIngress(c *gin.Context) { + namespace := c.Param("namespace") + ingressName := c.Param("ingressName") + + if !checkIngressName(c, ingressName) { + return + } + + err := kubeclient.DeleteIngress(namespace, ingressName) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, "") +} + +func checkIngressName(c *gin.Context, deploymentName string) bool { + if deploymentName == "" { + fail(c, http.StatusBadRequest, errors.New("ingress名称不能为空")) + return false + } + + return true +} diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_secret.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_secret.go new file mode 100644 index 00000000000..9a3369d1106 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_secret.go @@ -0,0 +1,107 @@ +package apis + +import ( + "disaptch-k8s-manager/pkg/kubeclient" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "net/http" +) + +const ( + secretPrefix = "/namespace/:namespace/secrets" +) + +func initSecretApis(r *gin.RouterGroup) { + secret := r.Group(secretPrefix) + { + secret.GET("/:secretName", getSecret) + secret.POST("", createSecret) + secret.DELETE("/:secretName", deleteSecret) + } +} + +// @Tags secret +// @Summary 获取secret详情 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param secretName path string true "secret名称" +// @Success 200 {object} types.Result{data=corev1.secret} "secret详情" +// @Router /secret/{secretName} [get] +func getSecret(c *gin.Context) { + namespace := c.Param("namespace") + secretName := c.Param("secretName") + + if !checkSecretName(c, secretName) { + return + } + + secret, err := kubeclient.GetNativeSecret(namespace, secretName) + if err != nil { + okFail(c, http.StatusInternalServerError, err) + return + } + + ok(c, secret) +} + +// @Tags secret +// @Summary 创建secret负载资源 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param secret body corev1.secret true "secret信息" +// @Success 200 {object} "" +// @Router /secret [post] +func createSecret(c *gin.Context) { + namespace := c.Param("namespace") + secret := &corev1.Secret{} + + if err := c.BindJSON(secret); err != nil { + fail(c, http.StatusBadRequest, err) + return + } + + err := kubeclient.CreateNativeSecret(namespace, secret) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, "") +} + +// @Tags secret +// @Summary 删除secret +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param secretName path string true "secret名称" +// @Success 200 {object} types.Result{data=""} "" +// @Router /secret/{secretName} [delete] +func deleteSecret(c *gin.Context) { + namespace := c.Param("namespace") + secretName := c.Param("secretName") + + if !checkSecretName(c, secretName) { + return + } + + err := kubeclient.DeleteNativeSecret(namespace, secretName) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, "") +} + +func checkSecretName(c *gin.Context, secretName string) bool { + if secretName == "" { + fail(c, http.StatusBadRequest, errors.New("secret名称不能为空")) + return false + } + + return true +} diff --git a/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_service.go b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_service.go new file mode 100644 index 00000000000..289df57b985 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/apiserver/apis/open_service.go @@ -0,0 +1,116 @@ +package apis + +import ( + "disaptch-k8s-manager/pkg/kubeclient" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "net/http" +) + +const ( + servicePrefix = "/namespace/:namespace/services" +) + +func initServiceApis(r *gin.RouterGroup) { + service := r.Group(servicePrefix) + { + service.GET("/:serviceName", getService) + service.POST("", createService) + service.DELETE("/:serviceName", deleteService) + } +} + +// @Tags service +// @Summary 获取service详情 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param serviceName path string true "deployment名称" +// @Success 200 {object} types.Result{data=appsv1.service} "service详情" +// @Router /service/{serviceName} [get] +func getService(c *gin.Context) { + namespace := c.Param("namespace") + serviceName := c.Param("serviceName") + + if !checkServiceName(c, serviceName) { + return + } + + service, err := kubeclient.GetService(namespace, serviceName) + if err != nil { + okFail(c, http.StatusInternalServerError, err) + return + } + + ok(c, service) +} + +// @Tags service +// @Summary 创建service负载资源 +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param service body corev1.Service true "service负载信息" +// @Success 200 {object} "" +// @Router /service [post] +func createService(c *gin.Context) { + namespace := c.Param("namespace") + service := &corev1.Service{} + + if err := c.BindJSON(service); err != nil { + fail(c, http.StatusBadRequest, err) + return + } + + serviceInfo, _ := kubeclient.GetService(namespace, service.Name) + if serviceInfo != nil { + err := kubeclient.UpdateService(namespace, service) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + } else { + err := kubeclient.CreateService(namespace, service) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + } + + ok(c, "") +} + +// @Tags service +// @Summary 删除service +// @Accept json +// @Product json +// @Param Devops-Token header string true "凭证信息" +// @Param serviceName path string true "service名称" +// @Success 200 {object} types.Result{data=""} "" +// @Router /service/{serviceName} [delete] +func deleteService(c *gin.Context) { + namespace := c.Param("namespace") + serviceName := c.Param("serviceName") + + if !checkServiceName(c, serviceName) { + return + } + + err := kubeclient.DeleteService(namespace, serviceName) + if err != nil { + fail(c, http.StatusInternalServerError, err) + return + } + + ok(c, "") +} + +func checkServiceName(c *gin.Context, deploymentName string) bool { + if deploymentName == "" { + fail(c, http.StatusBadRequest, errors.New("service名称不能为空")) + return false + } + + return true +} diff --git a/src/backend/dispatch-k8s-manager/pkg/kubeclient/deployment.go b/src/backend/dispatch-k8s-manager/pkg/kubeclient/deployment.go index 6ac30b81abe..804c81dfcde 100644 --- a/src/backend/dispatch-k8s-manager/pkg/kubeclient/deployment.go +++ b/src/backend/dispatch-k8s-manager/pkg/kubeclient/deployment.go @@ -96,6 +96,32 @@ func CreateDeployment(dep *Deployment) error { return nil } +func CreateNativeDeployment(namespace string, deployment *appsv1.Deployment) error { + _, err := kubeClient.AppsV1().Deployments(namespace).Create( + context.TODO(), + deployment, + metav1.CreateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + +func UpdateNativeDeployment(namespace string, deployment *appsv1.Deployment) error { + _, err := kubeClient.AppsV1().Deployments(namespace).Update( + context.TODO(), + deployment, + metav1.UpdateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + func PatchDeployment(deploymentName string, jsonPatch []byte) error { _, err := kubeClient.AppsV1().Deployments(config.Config.Kubernetes.NameSpace).Patch( context.TODO(), @@ -141,3 +167,25 @@ func ListDeployment(workloadCoreLabel string) ([]*appsv1.Deployment, error) { return list, nil } + +func GetNativeDeployment(namespace string, deploymentName string) (*appsv1.Deployment, error) { + deployment, err := kubeClient.AppsV1().Deployments(namespace).Get( + context.TODO(), + deploymentName, + metav1.GetOptions{}, + ) + + if err != nil { + return nil, err + } + + return deployment, nil +} + +func DeleteNativeDeployment(namespace string, deploymentName string) error { + return kubeClient.AppsV1().Deployments(namespace).Delete( + context.TODO(), + deploymentName, + metav1.DeleteOptions{}, + ) +} diff --git a/src/backend/dispatch-k8s-manager/pkg/kubeclient/ingress.go b/src/backend/dispatch-k8s-manager/pkg/kubeclient/ingress.go new file mode 100644 index 00000000000..0668fc223a2 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/kubeclient/ingress.go @@ -0,0 +1,55 @@ +package kubeclient + +import ( + "context" + networkv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreateIngress(namespace string, ingress *networkv1.Ingress) error { + _, err := kubeClient.NetworkingV1().Ingresses(namespace).Create( + context.TODO(), + ingress, + metav1.CreateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + +func UpdateIngress(namespace string, ingress *networkv1.Ingress) error { + _, err := kubeClient.NetworkingV1().Ingresses(namespace).Update( + context.TODO(), + ingress, + metav1.UpdateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + +func DeleteIngress(namespace string, ingressName string) error { + return kubeClient.NetworkingV1().Ingresses(namespace).Delete( + context.TODO(), + ingressName, + metav1.DeleteOptions{}, + ) +} + +func GetIngress(namespace string, ingressName string) (*networkv1.Ingress, error) { + ingress, err := kubeClient.NetworkingV1().Ingresses(namespace).Get( + context.TODO(), + ingressName, + metav1.GetOptions{}, + ) + + if err != nil { + return nil, err + } + + return ingress, err +} diff --git a/src/backend/dispatch-k8s-manager/pkg/kubeclient/secret.go b/src/backend/dispatch-k8s-manager/pkg/kubeclient/secret.go index d38ea5c4e17..0ef18fe7ecc 100644 --- a/src/backend/dispatch-k8s-manager/pkg/kubeclient/secret.go +++ b/src/backend/dispatch-k8s-manager/pkg/kubeclient/secret.go @@ -44,6 +44,17 @@ func CreateDockerRegistry(dockerSecret *DockerSecret) (*corev1.Secret, error) { return s, nil } +func CreateNativeSecret(namespace string, secret *corev1.Secret) error { + _, err := kubeClient.CoreV1(). + Secrets(namespace). + Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return err + } + + return nil +} + // DockerConfigJSON represents a local docker auth config file // for pulling images. type DockerConfigJSON struct { @@ -96,6 +107,28 @@ func ListSecret(workloadCoreLabel string) ([]corev1.Secret, error) { return l.Items, nil } +func GetNativeSecret(namespace string, secretName string) (*corev1.Secret, error) { + l, err := kubeClient.CoreV1().Secrets(namespace).Get( + context.TODO(), + secretName, + metav1.GetOptions{}, + ) + if err != nil { + return nil, err + } + return l, nil +} + +func DeleteNativeSecret(namespace string, secretName string) error { + if err := kubeClient.CoreV1(). + Secrets(namespace). + Delete(context.TODO(), secretName, metav1.DeleteOptions{}); err != nil { + return err + } + + return nil +} + func DeleteSecret(secretName string) error { if err := kubeClient.CoreV1(). Secrets(config.Config.Kubernetes.NameSpace). diff --git a/src/backend/dispatch-k8s-manager/pkg/kubeclient/service.go b/src/backend/dispatch-k8s-manager/pkg/kubeclient/service.go new file mode 100644 index 00000000000..37ab318d9e7 --- /dev/null +++ b/src/backend/dispatch-k8s-manager/pkg/kubeclient/service.go @@ -0,0 +1,55 @@ +package kubeclient + +import ( + "context" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreateService(namespace string, service *corev1.Service) error { + _, err := kubeClient.CoreV1().Services(namespace).Create( + context.TODO(), + service, + metav1.CreateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + +func UpdateService(namespace string, service *corev1.Service) error { + _, err := kubeClient.CoreV1().Services(namespace).Update( + context.TODO(), + service, + metav1.UpdateOptions{}, + ) + + if err != nil { + return err + } + return nil +} + +func DeleteService(namespace string, serviceName string) error { + return kubeClient.CoreV1().Services(namespace).Delete( + context.TODO(), + serviceName, + metav1.DeleteOptions{}, + ) +} + +func GetService(namespace string, serviceName string) (*corev1.Service, error) { + service, err := kubeClient.CoreV1().Services(namespace).Get( + context.TODO(), + serviceName, + metav1.GetOptions{}, + ) + + if err != nil { + return nil, err + } + + return service, err +} diff --git a/support-files/i18n/dispatch/message_en_US.properties b/support-files/i18n/dispatch/message_en_US.properties index 1897caa230f..24caf3c1986 100644 --- a/support-files/i18n/dispatch/message_en_US.properties +++ b/support-files/i18n/dispatch/message_en_US.properties @@ -111,6 +111,7 @@ bkEnvWorkerErrorIgnore=The {0} node in the build machine environment failed to s 2126052=THIRD_PARTY_SERVICES_DEVCLOUD_EXCEPTIONS, EXCEPTION_INFORMATION: API request timed out 2126053=THIRD_PARTY_SERVICES_DEVCLOUD_EXCEPTIONS, EXCEPTION_INFORMATION: User operation exception 2126054=The upper limit of DevCloud creating Job environment has been exceeded. +2126055=failed to create {0} kubernetes resource,cause:{1} bcsBuilderContainerStatus.running=Container running bcsBuilderContainerStatus.terminated=The container has finished executing bcsBuilderContainerStatus.waiting=The container is pulling up diff --git a/support-files/i18n/dispatch/message_zh_CN.properties b/support-files/i18n/dispatch/message_zh_CN.properties index a2bdf9880f2..e395a005674 100644 --- a/support-files/i18n/dispatch/message_zh_CN.properties +++ b/support-files/i18n/dispatch/message_zh_CN.properties @@ -113,6 +113,7 @@ bkThirdJobNodeCurr=单节点上的并发限制,当前环境下每个节点运 2126052=第三方服务-DEVCLOUD 异常,异常信息 - 接口请求超时 2126053=第三方服务-DEVCLOUD 异常,异常信息 - 用户操作异常 2126054=已超过DevCloud创建Job环境上限. +2126055=kubernetes创建{0}资源失败,原因:{1} bcsBuilderContainerStatus.running=容器运行中 bcsBuilderContainerStatus.terminated=容器已经执行完 bcsBuilderContainerStatus.waiting=容器拉起中 diff --git a/support-files/i18n/store/message_en_US.properties b/support-files/i18n/store/message_en_US.properties index fc224604fa0..48d1ef67c06 100644 --- a/support-files/i18n/store/message_en_US.properties +++ b/support-files/i18n/store/message_en_US.properties @@ -70,6 +70,7 @@ 2120408=store: selected debugging project does not have the permission to create assembly line 2120409=store: extension service does not exist {0} 2120410=store: extension services are available under the extension point and cannot be deleted +2120411=Store: User Extension service is not deployed 2120901=store: you have already liked it. 2120902=store: you do not have permission to install components to the project: {0} 2120903=Store: you have already commented, so you can no longer add comments. But you can modify the original comments. diff --git a/support-files/i18n/store/message_zh_CN.properties b/support-files/i18n/store/message_zh_CN.properties index d5b0acff5df..4d5429c24ef 100644 --- a/support-files/i18n/store/message_zh_CN.properties +++ b/support-files/i18n/store/message_zh_CN.properties @@ -70,6 +70,7 @@ 2120408=研发商店: 选中调试项目无创建流水线权限 2120409=研发商店: 微扩展不存在{0} 2120410=研发商店: 扩展点下还有可用的微扩展,不能删除 +2120411=研发商店: 用户扩展服务未部署 2120901=研发商店: 你已点赞过 2120902=研发商店: 你没有权限将组件安装到项目: {0} 2120903=研发商店: 你已评论过,无法继续添加评论。但可以修改原有评论