diff --git a/README.md b/README.md index 1fddba0c860..3c61a5ca5f9 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ bk-ci提供了流水线、代码库、凭证管理、环境管理、研发商店 ## Support 1. [GitHub讨论区](https://github.com/Tencent/bk-ci/discussions) -2. QQ群:744672165 +2. QQ群:495299374 ## BlueKing Community - [BK-BCS](https://github.com/Tencent/bk-bcs):蓝鲸容器管理平台是以容器技术为基础,为微服务业务提供编排管理的基础服务平台。 diff --git a/README_EN.md b/README_EN.md index 07442812a3f..4ccb818aab9 100644 --- a/README_EN.md +++ b/README_EN.md @@ -49,7 +49,7 @@ bk-ci provides five core services, namely Process, Repository, Ticket, Environme ## Support 1. [GitHub Discussions](https://github.com/Tencent/bk-ci/discussions) -2. QQ Group: 744672165 +2. QQ Group: 495299374 ## BlueKing Community diff --git a/src/backend/ci/build.gradle.kts b/src/backend/ci/build.gradle.kts index 2e4e8463841..1cdc5c252bd 100644 --- a/src/backend/ci/build.gradle.kts +++ b/src/backend/ci/build.gradle.kts @@ -1,9 +1,8 @@ plugins { kotlin("jvm") + detektCheck } -apply("$rootDir/detekt.gradle.kts") - allprojects { // 包路径 group = "com.tencent.bk.devops.ci" diff --git a/src/backend/ci/detekt.gradle.kts b/src/backend/ci/buildSrc/src/main/kotlin/detektCheck.gradle.kts similarity index 100% rename from src/backend/ci/detekt.gradle.kts rename to src/backend/ci/buildSrc/src/main/kotlin/detektCheck.gradle.kts diff --git a/src/backend/ci/core/log/biz-log/src/main/kotlin/com/tencent/devops/log/resources/BuildLogPrintResourceImpl.kt b/src/backend/ci/core/log/biz-log/src/main/kotlin/com/tencent/devops/log/resources/BuildLogPrintResourceImpl.kt index e9c327dde81..5f3b6abc9df 100644 --- a/src/backend/ci/core/log/biz-log/src/main/kotlin/com/tencent/devops/log/resources/BuildLogPrintResourceImpl.kt +++ b/src/backend/ci/core/log/biz-log/src/main/kotlin/com/tencent/devops/log/resources/BuildLogPrintResourceImpl.kt @@ -56,7 +56,8 @@ class BuildLogPrintResourceImpl @Autowired constructor( logger.error("Invalid build ID[$buildId]") return Result(false) } - return buildLogPrintService.asyncDispatchEvent(LogEvent(buildId, listOf(logMessage))) + buildLogPrintService.dispatchEvent(LogEvent(buildId, listOf(logMessage))) + return Result(true) } override fun addRedLogLine(buildId: String, logMessage: LogMessage): Result { @@ -64,10 +65,11 @@ class BuildLogPrintResourceImpl @Autowired constructor( logger.error("Invalid build ID[$buildId]") return Result(false) } - return buildLogPrintService.asyncDispatchEvent(LogEvent( + buildLogPrintService.dispatchEvent(LogEvent( buildId = buildId, logs = listOf(logMessage.copy(message = Ansi().bold().fgRed().a(logMessage.message).reset().toString())) )) + return Result(true) } override fun addYellowLogLine(buildId: String, logMessage: LogMessage): Result { @@ -75,10 +77,11 @@ class BuildLogPrintResourceImpl @Autowired constructor( logger.error("Invalid build ID[$buildId]") return Result(false) } - return buildLogPrintService.asyncDispatchEvent(LogEvent( + buildLogPrintService.dispatchEvent(LogEvent( buildId = buildId, logs = listOf(logMessage.copy(message = Ansi().bold().fgYellow().a(logMessage.message).reset().toString())) )) + return Result(true) } override fun addLogMultiLine(buildId: String, logMessages: List): Result { @@ -86,7 +89,8 @@ class BuildLogPrintResourceImpl @Autowired constructor( logger.error("Invalid build ID[$buildId]") return Result(false) } - return buildLogPrintService.asyncDispatchEvent(LogEvent(buildId, logMessages)) + buildLogPrintService.dispatchEvent(LogEvent(buildId, logMessages)) + return Result(true) } override fun addLogStatus( diff --git a/src/backend/ci/core/misc/biz-misc/src/main/kotlin/com/tencent/devops/misc/cron/process/PipelineBuildHistoryDataClearJob.kt b/src/backend/ci/core/misc/biz-misc/src/main/kotlin/com/tencent/devops/misc/cron/process/PipelineBuildHistoryDataClearJob.kt index 97ac618db2c..26d9678d15e 100644 --- a/src/backend/ci/core/misc/biz-misc/src/main/kotlin/com/tencent/devops/misc/cron/process/PipelineBuildHistoryDataClearJob.kt +++ b/src/backend/ci/core/misc/biz-misc/src/main/kotlin/com/tencent/devops/misc/cron/process/PipelineBuildHistoryDataClearJob.kt @@ -50,6 +50,9 @@ import java.time.LocalDateTime import java.util.concurrent.Callable import java.util.concurrent.Executors import java.util.concurrent.Future +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit @Component @Suppress("ALL") @@ -73,13 +76,14 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( "pipeline:build:history:data:clear:project:id" private const val PIPELINE_BUILD_HISTORY_DATA_CLEAR_PROJECT_LIST_KEY = "pipeline:build:history:data:clear:project:list" + private const val PIPELINE_BUILD_HISTORY_DATA_CLEAR_THREAD_SET_KEY = + "pipeline:build:history:data:clear:thread:set" + private var executor: ThreadPoolExecutor? = null } @Value("\${process.deletedPipelineStoreDays:30}") private val deletedPipelineStoreDays: Long = 30 // 回收站已删除流水线保存天数 - private val executor = Executors.newFixedThreadPool(miscBuildDataClearConfig.maxThreadHandleProjectNum) - @Scheduled(initialDelay = 10000, fixedDelay = 12000) fun pipelineBuildHistoryDataClear() { if (!miscBuildDataClearConfig.switch.toBoolean()) { @@ -87,8 +91,23 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( return } logger.info("pipelineBuildHistoryDataClear start") - val lock = RedisLock(redisOperation, - LOCK_KEY, 3000) + if (executor == null) { + // 创建带有边界队列的线程池,防止内存爆掉 + logger.info("pipelineBuildHistoryDataClear create executor") + executor = ThreadPoolExecutor( + miscBuildDataClearConfig.maxThreadHandleProjectNum, + miscBuildDataClearConfig.maxThreadHandleProjectNum, + 0L, + TimeUnit.MILLISECONDS, + LinkedBlockingQueue(10), + Executors.defaultThreadFactory(), + ThreadPoolExecutor.DiscardPolicy() + ) + } + val lock = RedisLock( + redisOperation, + LOCK_KEY, 3000 + ) try { if (!lock.tryLock()) { logger.info("get lock failed, skip") @@ -108,7 +127,6 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( } // 获取清理项目构建数据的线程数量 val maxThreadHandleProjectNum = miscBuildDataClearConfig.maxThreadHandleProjectNum - val futureList = mutableListOf>() val avgProjectNum = maxProjectNum / maxThreadHandleProjectNum for (index in 1..maxThreadHandleProjectNum) { // 计算线程能处理的最大项目主键ID @@ -117,17 +135,15 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( } else { index * avgProjectNum + maxProjectNum % maxThreadHandleProjectNum } - futureList.add( + // 判断线程是否正在处理任务,如正在处理则不分配新任务(定时任务12秒执行一次,线程启动到往set集合设置编号耗费时间很短,故不加锁) + if (!redisOperation.isMember(PIPELINE_BUILD_HISTORY_DATA_CLEAR_THREAD_SET_KEY, index.toString())) { doClearBus( threadNo = index, projectIdList = projectIdList, minThreadProjectPrimaryId = (index - 1) * avgProjectNum, maxThreadProjectPrimaryId = maxThreadProjectPrimaryId ) - ) - } - futureList.forEachIndexed { index, future -> - logger.info("future-$index doClearBus result:${future.get()}") + } } } catch (t: Throwable) { logger.warn("pipelineBuildHistoryDataClear failed", t) @@ -143,7 +159,7 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( maxThreadProjectPrimaryId: Long ): Future { val threadName = "Thread-$threadNo" - return executor.submit(Callable { + return executor!!.submit(Callable { var handleProjectPrimaryId = redisOperation.get("$threadName:$PIPELINE_BUILD_HISTORY_DATA_CLEAR_PROJECT_ID_KEY")?.toLong() if (handleProjectPrimaryId == null) { @@ -152,43 +168,52 @@ class PipelineBuildHistoryDataClearJob @Autowired constructor( if (handleProjectPrimaryId >= maxThreadProjectPrimaryId) { // 已经清理完全部项目的流水线的过期构建记录,再重新开始清理 redisOperation.delete("$threadName:$PIPELINE_BUILD_HISTORY_DATA_CLEAR_PROJECT_ID_KEY") - logger.info("pipelineBuildHistoryDataClear reStart") + logger.info("pipelineBuildHistoryDataClear $threadName reStart") return@Callable true } } - val maxEveryProjectHandleNum = miscBuildDataClearConfig.maxEveryProjectHandleNum - var maxHandleProjectPrimaryId = handleProjectPrimaryId ?: 0L - val projectInfoList = if (projectIdList.isNullOrEmpty()) { - val channelCodeList = miscBuildDataClearConfig.clearChannelCodes.split(",") - maxHandleProjectPrimaryId = handleProjectPrimaryId + maxEveryProjectHandleNum - projectMiscService.getProjectInfoList( - minId = handleProjectPrimaryId, - maxId = maxHandleProjectPrimaryId, - channelCodeList = channelCodeList - ) - } else { - projectMiscService.getProjectInfoList(projectIdList = projectIdList) - } - // 根据项目依次查询T_PIPELINE_INFO表中的流水线数据处理 - projectInfoList?.forEach { projectInfo -> - val channel = projectInfo.channel - // 获取项目对应的流水线数据清理配置类,如果不存在说明无需清理该项目下的构建记录 - val projectDataClearConfigService = - ProjectDataClearConfigFactory.getProjectDataClearConfigService(channel) ?: return@forEach - val projectPrimaryId = projectInfo.id - if (projectPrimaryId > maxHandleProjectPrimaryId) { - maxHandleProjectPrimaryId = projectPrimaryId + // 将线程编号存入redis集合 + redisOperation.sadd(PIPELINE_BUILD_HISTORY_DATA_CLEAR_THREAD_SET_KEY, threadNo.toString()) + try { + val maxEveryProjectHandleNum = miscBuildDataClearConfig.maxEveryProjectHandleNum + var maxHandleProjectPrimaryId = handleProjectPrimaryId ?: 0L + val projectInfoList = if (projectIdList.isNullOrEmpty()) { + val channelCodeList = miscBuildDataClearConfig.clearChannelCodes.split(",") + maxHandleProjectPrimaryId = handleProjectPrimaryId + maxEveryProjectHandleNum + projectMiscService.getProjectInfoList( + minId = handleProjectPrimaryId, + maxId = maxHandleProjectPrimaryId, + channelCodeList = channelCodeList + ) + } else { + projectMiscService.getProjectInfoList(projectIdList = projectIdList) } - val projectId = projectInfo.projectId - // 清理流水线构建数据 - clearPipelineBuildData(projectId, projectDataClearConfigService) + // 根据项目依次查询T_PIPELINE_INFO表中的流水线数据处理 + projectInfoList?.forEach { projectInfo -> + val channel = projectInfo.channel + // 获取项目对应的流水线数据清理配置类,如果不存在说明无需清理该项目下的构建记录 + val projectDataClearConfigService = + ProjectDataClearConfigFactory.getProjectDataClearConfigService(channel) ?: return@forEach + val projectPrimaryId = projectInfo.id + if (projectPrimaryId > maxHandleProjectPrimaryId) { + maxHandleProjectPrimaryId = projectPrimaryId + } + val projectId = projectInfo.projectId + // 清理流水线构建数据 + clearPipelineBuildData(projectId, projectDataClearConfigService) + } + // 将当前已处理完的最大项目Id存入redis + redisOperation.set( + key = "$threadName:$PIPELINE_BUILD_HISTORY_DATA_CLEAR_PROJECT_ID_KEY", + value = maxHandleProjectPrimaryId.toString(), + expired = false + ) + } catch (ignore: Exception) { + logger.warn("pipelineBuildHistoryDataClear doClearBus failed", ignore) + } finally { + // 释放redis集合中的线程编号 + redisOperation.sremove(PIPELINE_BUILD_HISTORY_DATA_CLEAR_THREAD_SET_KEY, threadNo.toString()) } - // 将当前已处理完的最大项目Id存入redis - redisOperation.set( - key = "$threadName:$PIPELINE_BUILD_HISTORY_DATA_CLEAR_PROJECT_ID_KEY", - value = maxHandleProjectPrimaryId.toString(), - expired = false - ) return@Callable true }) } diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v3/ApigwBuildResourceV3.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v3/ApigwBuildResourceV3.kt index 19aa1422ede..822990d890a 100644 --- a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v3/ApigwBuildResourceV3.kt +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v3/ApigwBuildResourceV3.kt @@ -30,6 +30,8 @@ import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE import com.tencent.devops.common.api.pojo.BuildHistoryPage import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.pipeline.pojo.StageReviewRequest @@ -37,6 +39,7 @@ import com.tencent.devops.process.pojo.BuildHistory import com.tencent.devops.process.pojo.BuildHistoryWithVars import com.tencent.devops.process.pojo.BuildId import com.tencent.devops.process.pojo.BuildManualStartupInfo +import com.tencent.devops.process.pojo.BuildTaskPauseInfo import com.tencent.devops.process.pojo.pipeline.ModelDetail import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation @@ -294,4 +297,23 @@ interface ApigwBuildResourceV3 { @ApiParam("变量名列表", required = true) variableNames: List ): Result> + + @ApiOperation("操作暂停插件") + @POST + @Path("/{buildId}/execute/pause") + fun executionPauseAtom( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID", required = true) + @PathParam("projectId") + projectId: String, + @ApiParam("流水线ID", required = true) + @PathParam("pipelineId") + pipelineId: String, + @ApiParam("构建ID", required = true) + @PathParam("buildId") + buildId: String, + taskPauseExecute: BuildTaskPauseInfo + ): Result } diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v3/ApigwBuildResourceV3Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v3/ApigwBuildResourceV3Impl.kt index 820eb839af8..4c394a0d8d8 100644 --- a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v3/ApigwBuildResourceV3Impl.kt +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v3/ApigwBuildResourceV3Impl.kt @@ -38,6 +38,7 @@ import com.tencent.devops.process.pojo.BuildHistory import com.tencent.devops.process.pojo.BuildHistoryWithVars import com.tencent.devops.process.pojo.BuildId import com.tencent.devops.process.pojo.BuildManualStartupInfo +import com.tencent.devops.process.pojo.BuildTaskPauseInfo import com.tencent.devops.process.pojo.pipeline.ModelDetail import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -222,6 +223,23 @@ class ApigwBuildResourceV3Impl @Autowired constructor( ) } + override fun executionPauseAtom( + userId: String, + projectId: String, + pipelineId: String, + buildId: String, + taskPauseExecute: BuildTaskPauseInfo + ): Result { + logger.info("$pipelineId| $buildId| $userId |executionPauseAtom $taskPauseExecute") + return client.get(ServiceBuildResource::class).executionPauseAtom( + userId = userId, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + taskPauseExecute = taskPauseExecute + ) + } + companion object { private val logger = LoggerFactory.getLogger(ApigwBuildResourceV3Impl::class.java) } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt index c75c6250f2c..3ae56e16782 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt @@ -42,6 +42,7 @@ import com.tencent.devops.process.pojo.BuildHistoryVariables import com.tencent.devops.process.pojo.BuildHistoryWithVars import com.tencent.devops.process.pojo.BuildId import com.tencent.devops.process.pojo.BuildManualStartupInfo +import com.tencent.devops.process.pojo.BuildTaskPauseInfo import com.tencent.devops.process.pojo.ReviewParam import com.tencent.devops.process.pojo.VmInfo import com.tencent.devops.process.pojo.pipeline.ModelDetail @@ -550,4 +551,23 @@ interface ServiceBuildResource { @ApiParam("审核请求体", required = false) reviewRequest: StageReviewRequest? = null ): Result + + @ApiOperation("操作暂停插件") + @POST + @Path("/projects/{projectId}/pipelines/{pipelineId}/builds/{buildId}/execution/pause") + fun executionPauseAtom( + @ApiParam(value = "用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID", required = true) + @PathParam("projectId") + projectId: String, + @ApiParam("流水线ID", required = true) + @PathParam("pipelineId") + pipelineId: String, + @ApiParam("构建ID", required = true) + @PathParam("buildId") + buildId: String, + taskPauseExecute: BuildTaskPauseInfo + ): Result } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt index 47f90dc3723..91009ebc05f 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt @@ -505,8 +505,8 @@ interface UserBuildResource { @ApiParam("任务ID", required = true) @PathParam("taskId") taskId: String, - @ApiParam("待执行插件元素", required = true) - element: Element, + @ApiParam("待执行插件元素", required = false) + element: Element?, @ApiParam("执行类型, true 继续, false 停止", required = true) @QueryParam("isContinue") isContinue: Boolean, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildTaskPauseInfo.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildTaskPauseInfo.kt new file mode 100644 index 00000000000..c2a1e818007 --- /dev/null +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/BuildTaskPauseInfo.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.pojo + +import com.tencent.devops.common.pipeline.pojo.element.Element +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("流水线暂停操作实体类") +data class BuildTaskPauseInfo( + @ApiModelProperty("任务ID") + val taskId: String, + @ApiModelProperty("element信息,若插件内有变量变更需给出变更后的element") + val element: Element?, + @ApiModelProperty("是否继续 true:继续构建 false:停止构建") + val isContinue: Boolean, + @ApiModelProperty("当前stageId") + val stageId: String, + @ApiModelProperty("当前containerId") + val containerId: String +) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildStageDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildStageDao.kt index 06df530423d..2eb446a621b 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildStageDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildStageDao.kt @@ -264,7 +264,7 @@ class PipelineBuildStageDao { with(T_PIPELINE_BUILD_STAGE) { val data = dslContext.selectFrom(this) .where(BUILD_ID.eq(buildId)).and(STATUS.eq(status.ordinal)) - .orderBy(SEQ.desc()).limit(1).fetchAny() + .orderBy(SEQ.asc()).limit(1).fetchAny() return convert(data) } } 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 19e97a17301..b5e8bed07f9 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 @@ -311,7 +311,7 @@ class PipelineBuildTaskDao @Autowired constructor(private val objectMapper: Obje val totalTime = if (record.startTime == null || record.endTime == null) { 0 } else { - Duration.between(record.startTime, record.endTime).toMillis() / 1000 + Duration.between(record.startTime, record.endTime).toMillis() } dslContext.update(this) .set(TOTAL_TIME, totalTime) diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt index 533ae696304..8e733bc1434 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt @@ -47,10 +47,12 @@ import com.tencent.devops.process.pojo.BuildHistoryVariables import com.tencent.devops.process.pojo.BuildHistoryWithVars import com.tencent.devops.process.pojo.BuildId import com.tencent.devops.process.pojo.BuildManualStartupInfo +import com.tencent.devops.process.pojo.BuildTaskPauseInfo import com.tencent.devops.process.pojo.ReviewParam import com.tencent.devops.process.pojo.VmInfo import com.tencent.devops.process.pojo.pipeline.ModelDetail import com.tencent.devops.process.pojo.pipeline.PipelineLatestBuild +import com.tencent.devops.process.service.builds.PipelinePauseBuildFacadeService import org.springframework.beans.factory.annotation.Autowired @Suppress("ALL") @@ -58,7 +60,8 @@ import org.springframework.beans.factory.annotation.Autowired class ServiceBuildResourceImpl @Autowired constructor( private val pipelineBuildFacadeService: PipelineBuildFacadeService, private val engineVMBuildService: EngineVMBuildService, - private val vmBuildService: PipelineVMBuildService + private val vmBuildService: PipelineVMBuildService, + private val pipelinePauseBuildFacadeService: PipelinePauseBuildFacadeService ) : ServiceBuildResource { override fun setVMStatus( @@ -510,6 +513,30 @@ class ServiceBuildResourceImpl @Autowired constructor( return Result(true) } + override fun executionPauseAtom( + userId: String, + projectId: String, + pipelineId: String, + buildId: String, + taskPauseExecute: BuildTaskPauseInfo + ): Result { + checkParam(projectId, pipelineId) + checkUserId(userId) + return Result( + pipelinePauseBuildFacadeService.executePauseAtom( + userId = userId, + projectId = projectId, + pipelineId = pipelineId, + buildId = buildId, + isContinue = taskPauseExecute.isContinue, + taskId = taskPauseExecute.taskId, + element = taskPauseExecute.element, + stageId = taskPauseExecute.stageId, + containerId = taskPauseExecute.containerId + ) + ) + } + private fun checkParam(projectId: String, pipelineId: String) { if (pipelineId.isBlank()) { throw ParamBlankException("Invalid pipelineId") diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt index ed69d2239cf..8ac1f0169de 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt @@ -408,7 +408,7 @@ class UserBuildResourceImpl @Autowired constructor( pipelineId: String, buildId: String, taskId: String, - element: Element, + element: Element?, isContinue: Boolean, stageId: String, containerId: String diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelinePauseBuildFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelinePauseBuildFacadeService.kt index d073cce25e4..f813662340e 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelinePauseBuildFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelinePauseBuildFacadeService.kt @@ -75,7 +75,7 @@ class PipelinePauseBuildFacadeService( stageId: String, containerId: String, isContinue: Boolean, - element: Element, + element: Element?, checkPermission: Boolean? = true ): Boolean { logger.info("executePauseAtom| $userId| $pipelineId|$buildId| $stageId| $containerId| $taskId| $isContinue") @@ -89,17 +89,6 @@ class PipelinePauseBuildFacadeService( ) } - val newElementStr = ParameterUtils.element2Str(element, objectMapper) - if (newElementStr.isNullOrEmpty()) { - logger.warn("executePauseAtom element is too long") - throw ErrorCodeException( - statusCode = Response.Status.INTERNAL_SERVER_ERROR.statusCode, - errorCode = ProcessMessageCode.ERROR_ELEMENT_TOO_LONG, - defaultMessage = "${buildId}element大小越界", - params = arrayOf(buildId) - ) - } - val buildInfo = pipelineRuntimeService.getBuildInfo(buildId) ?: throw ErrorCodeException( statusCode = Response.Status.NOT_FOUND.statusCode, @@ -128,21 +117,14 @@ class PipelinePauseBuildFacadeService( actionType = ActionType.END // END才会对应成取消状态 } - val isDiff = findDiffValue( - buildId = buildId, - taskId = taskId, - userId = userId, - newElement = element, - oldTask = taskRecord - ) - - if (isDiff) { - pipelineTaskPauseService.savePauseValue(PipelinePauseValue( + if (element != null) { + findAndSaveDiff( + element = element, buildId = buildId, taskId = taskId, - newValue = newElementStr!!, - defaultValue = objectMapper.writeValueAsString(taskRecord.taskParams) - )) + userId = userId, + taskRecord = taskRecord + ) } pipelineEventDispatcher.dispatch( @@ -161,14 +143,17 @@ class PipelinePauseBuildFacadeService( return true } - fun findDiffValue( - newElement: Element, + private fun findDiffValue( + newElement: Element?, buildId: String, taskId: String, userId: String, oldTask: PipelineBuildTask ): Boolean { var isDiff = false + if (newElement == null) { + return isDiff + } val newInputData = ParameterUtils.getElementInput(newElement) val oldInputData = ParameterUtils.getParamInputs(oldTask.taskParams) ?: return isDiff @@ -203,4 +188,39 @@ class PipelinePauseBuildFacadeService( } return isDiff } + + private fun findAndSaveDiff( + element: Element, + buildId: String, + taskId: String, + userId: String, + taskRecord: PipelineBuildTask + ) { + val newElementStr = ParameterUtils.element2Str(element, objectMapper) + if (newElementStr.isNullOrEmpty()) { + logger.warn("executePauseAtom element is too long") + throw ErrorCodeException( + statusCode = Response.Status.INTERNAL_SERVER_ERROR.statusCode, + errorCode = ProcessMessageCode.ERROR_ELEMENT_TOO_LONG, + defaultMessage = "${buildId}element大小越界", + params = arrayOf(buildId) + ) + } + val isDiff = findDiffValue( + buildId = buildId, + taskId = taskId, + userId = userId, + newElement = element, + oldTask = taskRecord + ) + + if (isDiff) { + pipelineTaskPauseService.savePauseValue(PipelinePauseValue( + buildId = buildId, + taskId = taskId, + newValue = newElementStr!!, + defaultValue = objectMapper.writeValueAsString(taskRecord.taskParams) + )) + } + } } diff --git a/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt b/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt index dcf8cdb2dee..2dc8c14ef38 100644 --- a/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt +++ b/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt @@ -568,7 +568,7 @@ class CredentialServiceImpl @Autowired constructor( companion object { private val logger = LoggerFactory.getLogger(CredentialServiceImpl::class.java) private const val CREDENTIAL_ID_MAX_SIZE = 40 - private const val CREDENTIAL_NAME_MAX_SIZE = 30 + private const val CREDENTIAL_NAME_MAX_SIZE = 64 private val CREDENTIAL_ID_REGEX = Regex("^[0-9a-zA-Z_]+$") private val CREDENTIAL_NAME_REGEX = Regex("^[a-zA-Z0-9_\u4e00-\u9fa5-.]+$") } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/EngineBuildSDKApi.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/EngineBuildSDKApi.kt index 7e8b7e25733..c8f39d8c376 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/EngineBuildSDKApi.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/EngineBuildSDKApi.kt @@ -50,4 +50,6 @@ interface EngineBuildSDKApi : WorkerRestApiSDK { fun timeout(): Result fun getCiToken(): String + + fun getCiUrl(): String } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/impl/EngineBuildResourceApi.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/impl/EngineBuildResourceApi.kt index e83b2f23646..8e5a570901c 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/impl/EngineBuildResourceApi.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/api/engine/impl/EngineBuildResourceApi.kt @@ -35,6 +35,7 @@ import com.tencent.devops.process.pojo.BuildVariables import com.tencent.devops.worker.common.api.AbstractBuildResourceApi import com.tencent.devops.worker.common.api.ApiPriority import com.tencent.devops.worker.common.api.engine.EngineBuildSDKApi +import com.tencent.devops.worker.common.env.AgentEnv import okhttp3.MediaType import okhttp3.RequestBody @@ -136,4 +137,8 @@ open class EngineBuildResourceApi : AbstractBuildResourceApi(), EngineBuildSDKAp override fun getCiToken(): String { return "" } + + override fun getCiUrl(): String { + return AgentEnv.getGateway() + } } diff --git a/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/index.vue b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/index.vue index 8f7e8c92301..303ed172c73 100644 --- a/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/index.vue +++ b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/index.vue @@ -69,45 +69,8 @@ :is-error="errors.has(`param-${param.key}.defaultValue`)" :error-msg="errors.first(`param-${param.key}.defaultValue`)" :desc="$t(`editPage.${getParamsDefaultValueLabelTips(param.valueType)}`)"> - - - - - - + + { return CHECK_PARAM_LIST.map(item => { @@ -204,9 +153,9 @@ draggable, Accordion, VuexInput, - EnumInput, VuexTextarea, - AtomCheckbox + AtomCheckbox, + DefineParamShow }, mixins: [atomFieldMixin], props: { @@ -244,10 +193,6 @@ animation: 200, disabled: this.disabled } - }, - - boolList () { - return BOOLEAN } }, watch: { @@ -262,9 +207,6 @@ this.globalParams = this.value }, methods: { - isTextareaParam, - isStringParam, - isBooleanParam, isMultipleParam, getParamsDefaultValueLabel, getParamsDefaultValueLabelTips, @@ -330,30 +272,9 @@ this.$emit('input', value) }, - editParamShow (paramIndex) { - let isShow = false - const param = this.value[paramIndex] - if (param) { - isShow = param.required - } - this.handleParamChange('required', !isShow, paramIndex) - }, - isSelectorParam (type) { return isMultipleParam(type) || isEnumParam(type) }, - - transformOpt (opts) { - const uniqueMap = {} - opts = opts.filter(opt => opt.key.length) - return Array.isArray(opts) ? opts.filter(opt => { - if (!uniqueMap[opt.key]) { - uniqueMap[opt.key] = 1 - return true - } - return false - }).map(opt => ({ id: opt.key, name: opt.value })) : [] - }, getOptions (param) { try { diff --git a/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/show.vue b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/show.vue new file mode 100644 index 00000000000..1efd2e630f9 --- /dev/null +++ b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DefineParam/show.vue @@ -0,0 +1,138 @@ + + + diff --git a/src/frontend/devops-pipeline/src/components/AtomPropertyPanel/AtomOption.vue b/src/frontend/devops-pipeline/src/components/AtomPropertyPanel/AtomOption.vue index 15988ac7c42..2da30837a04 100755 --- a/src/frontend/devops-pipeline/src/components/AtomPropertyPanel/AtomOption.vue +++ b/src/frontend/devops-pipeline/src/components/AtomPropertyPanel/AtomOption.vue @@ -88,7 +88,8 @@ Vue.set(this.element.additionalOptions, name, value) } - const currentfailControl = name === 'failControl' ? value : this.atomOption['failControl'] + const currentfailControl = [...new Set(name === 'failControl' ? value : this.atomOption['failControl'])] // 去重 + const includeManualRetry = currentfailControl.includes('MANUAL_RETRY') const continueable = currentfailControl.includes('continueWhenFailed') const isAutoSkip = continueable && (this.atomOption['manualSkip'] === false || (name === 'manualSkip' && value === false)) @@ -98,7 +99,6 @@ console.log(currentfailControl, isAutoSkip, this.atomOption['failControl'], value) const failControl = isAutoSkip ? currentfailControl.filter(item => item !== 'MANUAL_RETRY') : [...currentfailControl] - console.log(failControl) this.setPipelineEditing(true) this.handleUpdateElement('additionalOptions', { diff --git a/src/frontend/devops-pipeline/src/components/Stages/AtomList.vue b/src/frontend/devops-pipeline/src/components/Stages/AtomList.vue index 0125a325da2..320e132148f 100755 --- a/src/frontend/devops-pipeline/src/components/Stages/AtomList.vue +++ b/src/frontend/devops-pipeline/src/components/Stages/AtomList.vue @@ -2,7 +2,7 @@
  • -
    - - - - - - + +
    @@ -102,33 +64,18 @@ isStringParam, isBooleanParam, isEnumParam, - isMultipleParam, - CHECK_PARAM_LIST } from '@/store/modules/atom/paramsConfig' + isMultipleParam + } from '@/store/modules/atom/paramsConfig' import FormField from '@/components/AtomPropertyPanel/FormField' - import EnumInput from '@/components/atomFormField/EnumInput' import VuexInput from '@/components/atomFormField/VuexInput' - import VuexTextarea from '@/components/atomFormField/VuexTextarea' - import Selector from '@/components/atomFormField/Selector' - - const BOOLEAN = [ - { - value: true, - label: true - }, - { - value: false, - label: false - } - ] + import DefineParamShow from '@/components/AtomFormComponent/DefineParam/show.vue' export default { name: 'check-atom-dialog', components: { - EnumInput, VuexInput, - VuexTextarea, - Selector, - FormField + FormField, + DefineParamShow }, mixins: [atomMixin], props: { @@ -151,14 +98,8 @@ isMetadataVar: { type: Boolean, default: false - }, - // 只允许修改值,不允许增减项和修改key - editValueOnly: { - type: Boolean, - default: false } }, - data () { return { isLoading: true, @@ -187,17 +128,6 @@ }, snonVarRule () { return !this.isSupportVar ? 'nonVarRule' : '' - }, - paramsList () { - return CHECK_PARAM_LIST.map(item => { - return { - id: item.id, - name: this.$t(`storeMap.${item.name}`) - } - }) - }, - boolList () { - return BOOLEAN } }, watch: { @@ -280,24 +210,6 @@ this.$bkMessage({ message: err.content, theme: 'error' }) }) }, - getSelectorDefaultVal ({ valueType, value = '' }) { - if (isMultipleParam(valueType)) { - return value && typeof value === 'string' ? value.split(',') : [] - } - - return value - }, - transformOpt (opts) { - const uniqueMap = {} - opts = opts.filter(opt => opt.key.length) - return Array.isArray(opts) ? opts.filter(opt => { - if (!uniqueMap[opt.key]) { - uniqueMap[opt.key] = 1 - return true - } - return false - }).map(opt => ({ id: opt.key, name: opt.value })) : [] - }, handleParamChange (key, value, paramIndex) { const param = this.data.params if (isMultipleParam(param[paramIndex].valueType) && key === 'value') { @@ -310,9 +222,6 @@ }) } this.handleUpdateElement('params', param) - }, - isSelectorParam (type) { - return isMultipleParam(type) || isEnumParam(type) } } } diff --git a/src/frontend/devops-pipeline/src/components/Stages/Stage.scss b/src/frontend/devops-pipeline/src/components/Stages/Stage.scss index 6aafd120322..f27eb9b4d2f 100755 --- a/src/frontend/devops-pipeline/src/components/Stages/Stage.scss +++ b/src/frontend/devops-pipeline/src/components/Stages/Stage.scss @@ -225,14 +225,18 @@ span.skip-name { .container-title { cursor: pointer; background-color: $fontWeightColor; - - &.QUEUE { - color: $loadingColor; + + &.UNEXEC, + &.DISABLED { + background-color: $fontWeightColor; } + + &.QUEUE, &.RUNNING, &.REVIEWING, &.PREPARE_ENV, &.LOOP_WAITING, + &.DEPENDENT_WAITING, &.CALL_WAITING { background-color: $loadingColor; } @@ -240,9 +244,11 @@ span.skip-name { &.REVIEW_ABORT, &.TRY_FINALLY, &.QUEUE_CACHE, - &.UNEXEC, &.SKIP { background-color: $cancelColor; + .container-name span.skip-name { + color: white; + } } &.FAILED, &.TERMINATE, @@ -264,7 +270,7 @@ span.skip-name { &:after { border-top-color: $lineColor; } - > .container-name span:hover { + > .container-name span { color: white; } } diff --git a/src/frontend/devops-pipeline/src/components/Stages/StageContainer.vue b/src/frontend/devops-pipeline/src/components/Stages/StageContainer.vue index da236b6c390..780663320be 100755 --- a/src/frontend/devops-pipeline/src/components/Stages/StageContainer.vue +++ b/src/frontend/devops-pipeline/src/components/Stages/StageContainer.vue @@ -12,7 +12,7 @@ -

    +

    {{ containerSerialNum }} @@ -98,6 +98,13 @@ 'getAllContainers' ]), + containerCls () { + if (this.container.jobControlOption && this.container.jobControlOption.enable === false) { + return 'DISABLED' + } + + return this.container && this.container.status ? this.container.status : '' + }, showCheckedToatal () { const { isTriggerContainer, container, $route } = this return $route.path.indexOf('preview') > 0 && !isTriggerContainer(container) diff --git a/src/frontend/devops-pipeline/src/components/atomFormField/AtomCheckboxList/index.vue b/src/frontend/devops-pipeline/src/components/atomFormField/AtomCheckboxList/index.vue index 79e139e8f5e..880ea12c1b1 100755 --- a/src/frontend/devops-pipeline/src/components/atomFormField/AtomCheckboxList/index.vue +++ b/src/frontend/devops-pipeline/src/components/atomFormField/AtomCheckboxList/index.vue @@ -1,5 +1,5 @@