Claude Code Plugins

Community-maintained marketplace

Feedback

24-pipeline-variable-lifecycle

@TencentBlueKing/bk-ci
2.5k
0

流水线变量生命周期管理指南,涵盖变量的创建、初始化、动态更新、存储、传递和查询的完整生命周期。当用户开发变量功能、处理变量传递、调试变量问题或理解变量作用域时使用。

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name 24-pipeline-variable-lifecycle
description 流水线变量生命周期管理指南,涵盖变量的创建、初始化、动态更新、存储、传递和查询的完整生命周期。当用户开发变量功能、处理变量传递、调试变量问题或理解变量作用域时使用。
author <NAME>

24-pipeline-variable-lifecycle

Skill 说明

流水线变量生命周期管理指南 - 涵盖变量的创建、初始化、动态更新、存储、传递和查询的完整生命周期。


1. 变量生命周期概览

┌─────────────────────────────────────────────────────────────────────────────┐
│                        流水线变量生命周期全景图                               │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────┐
                              │   用户触发构建   │
                              └────────┬────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段一:变量创建与初始化                                                     │
│                                                                              │
│  ┌─────────────────┐   ┌─────────────────┐   ┌─────────────────┐           │
│  │ 系统预置变量    │   │ 用户启动参数    │   │ 流水线定义变量  │           │
│  │ BK_CI_BUILD_ID  │ + │ 手动输入参数    │ + │ params.xxx      │           │
│  │ BK_CI_PIPELINE  │   │ API 传入参数    │   │ 默认值          │           │
│  └────────┬────────┘   └────────┬────────┘   └────────┬────────┘           │
│           │                     │                     │                     │
│           └─────────────────────┼─────────────────────┘                     │
│                                 ▼                                           │
│                    ┌─────────────────────────┐                              │
│                    │ StartBuildContext.init() │                             │
│                    │ 合并所有变量来源         │                             │
│                    └────────────┬────────────┘                              │
│                                 │                                           │
│                                 ▼                                           │
│                    ┌─────────────────────────┐                              │
│                    │ T_PIPELINE_BUILD_VAR    │                              │
│                    │ 批量写入数据库           │                             │
│                    └─────────────────────────┘                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段二:变量动态更新(执行过程中)                                           │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Worker 端 (构建机)                                                   │   │
│  │                                                                      │   │
│  │  Task 执行 ──▶ 插件输出变量 ──▶ output.json ──▶ completeTask API    │   │
│  │                  outputs:                                            │   │
│  │                    key1: value1                                      │   │
│  │                    key2: value2                                      │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                       │                                     │
│                                       ▼                                     │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ Engine 端 (服务端)                                                    │   │
│  │                                                                       │   │
│  │  EngineVMBuildService.buildCompleteTask()                            │   │
│  │      │                                                                │   │
│  │      ├──▶ 获取 Redis 分布式锁 (PipelineBuildVarLock)                 │   │
│  │      ├──▶ BuildVariableService.batchUpdateVariable()                 │   │
│  │      └──▶ PipelineBuildVarDao.batchUpdate()                          │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段三:变量传递                                                             │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Job 间传递                                                           │   │
│  │                                                                      │   │
│  │  Job-A 输出: steps.<stepId>.outputs.<key>                           │   │
│  │      │                                                               │   │
│  │      ▼                                                               │   │
│  │  Job-B 读取: ${{ jobs.<jobId>.steps.<stepId>.outputs.<key> }}       │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 父子流水线传递                                                       │   │
│  │                                                                      │   │
│  │  父流水线: SubPipelineStartUpService.callPipelineStartup()          │   │
│  │      │                                                               │   │
│  │      ├──▶ PIPELINE_START_PARENT_PROJECT_ID                          │   │
│  │      ├──▶ PIPELINE_START_PARENT_PIPELINE_ID                         │   │
│  │      ├──▶ PIPELINE_START_PARENT_BUILD_ID                            │   │
│  │      └──▶ 用户指定的传递参数                                        │   │
│  │      │                                                               │   │
│  │      ▼                                                               │   │
│  │  子流水线: 通过 getAllVariable() 读取                                │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段四:变量读取与表达式解析                                                 │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 表达式语法                                                           │   │
│  │                                                                      │   │
│  │  ${{ variables.xxx }}      → 流水线变量                              │   │
│  │  ${{ ci.xxx }}             → CI 预置变量                             │   │
│  │  ${{ settings.xxx }}       → 流水线设置                              │   │
│  │  ${{ jobs.xxx.outputs }}   → Job 输出                                │   │
│  │  ${{ steps.xxx.outputs }}  → Step 输出                               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                       │                                     │
│                                       ▼                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 解析流程                                                             │   │
│  │                                                                      │   │
│  │  EnvReplacementParser.parse()                                        │   │
│  │      │                                                               │   │
│  │      ├──▶ 检测 ${{ }} 表达式                                        │   │
│  │      ├──▶ ExpressionParser.evaluateByMap() 计算表达式               │   │
│  │      └──▶ 返回替换后的值                                            │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘

2. 变量存储机制

2.1 数据库表结构

-- 流水线构建变量表
CREATE TABLE IF NOT EXISTS `T_PIPELINE_BUILD_VAR` (
  `BUILD_ID` varchar(34) NOT NULL COMMENT '构建ID',
  `KEY` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '变量名',
  `VALUE` varchar(4000) DEFAULT NULL COMMENT '变量值(限制4000字符)',
  `PROJECT_ID` varchar(64) DEFAULT NULL COMMENT '项目ID',
  `PIPELINE_ID` varchar(64) DEFAULT NULL COMMENT '流水线ID',
  `VAR_TYPE` VARCHAR(64) COMMENT '变量类型',
  `READ_ONLY` bit(1) DEFAULT NULL COMMENT '是否只读',
  PRIMARY KEY (`BUILD_ID`,`KEY`),
  KEY `IDX_SEARCH_BUILD_ID` (`PROJECT_ID`,`PIPELINE_ID`, `KEY`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流水线变量表';

字段说明

字段 类型 说明
BUILD_ID varchar(34) 构建 ID,主键之一
KEY varchar(255) 变量名,区分大小写
VALUE varchar(4000) 变量值,最大 4000 字符
PROJECT_ID varchar(64) 项目 ID,用于索引优化
PIPELINE_ID varchar(64) 流水线 ID
VAR_TYPE varchar(64) 变量类型(见下文枚举)
READ_ONLY bit(1) 是否只读,只读变量不可被覆盖

2.2 核心类

类名 文件路径 职责
PipelineBuildVarDao biz-base/.../engine/dao/PipelineBuildVarDao.kt DAO 层,直接操作数据库
BuildVariableService biz-base/.../service/BuildVariableService.kt Service 层,变量业务逻辑
PipelineBuildVarLock biz-base/.../control/lock/PipelineBuildVarLock.kt Redis 分布式锁

2.3 DAO 层核心方法

// 文件: biz-base/.../engine/dao/PipelineBuildVarDao.kt

class PipelineBuildVarDao {
    
    // 批量保存变量(构建启动时使用)
    fun batchSave(
        dslContext: DSLContext,
        projectId: String,
        pipelineId: String,
        buildId: String,
        variables: List<BuildParameters>
    )
    
    // 批量更新变量(Task 完成时使用)
    fun batchUpdate(
        dslContext: DSLContext,
        projectId: String,
        pipelineId: String,
        buildId: String,
        variables: List<BuildParameters>
    )
    
    // 获取所有变量(返回 Map<String, String>)
    fun getVars(
        dslContext: DSLContext,
        projectId: String,
        buildId: String,
        keys: Set<String>? = null
    ): Map<String, String>
    
    // 获取带类型的变量
    fun getVarsWithType(
        dslContext: DSLContext,
        projectId: String,
        buildId: String,
        key: String? = null
    ): List<BuildParameters>
    
    // 模糊查询变量(用于 BK_REPO_GIT_WEBHOOK_PUSH_COMMIT_MSG_* 等前缀变量)
    fun fetchVarByLikeKey(
        dslContext: DSLContext,
        projectId: String,
        buildId: String,
        likeKey: String
    ): MutableMap<String, String>
    
    // 删除变量
    fun deleteBuildVar(
        dslContext: DSLContext,
        projectId: String,
        buildId: String,
        varName: String? = null
    ): Int
}

2.4 Service 层核心方法

// 文件: biz-base/.../service/BuildVariableService.kt

@Service
class BuildVariableService {
    
    // 构建启动时批量保存(无锁,因为此时不存在并发)
    fun startBuildBatchSaveWithoutThreadSafety(
        dslContext: DSLContext,
        projectId: String,
        pipelineId: String,
        buildId: String,
        variables: List<BuildParameters>
    )
    
    // 带锁保存变量(执行过程中使用)
    fun saveVariable(
        dslContext: DSLContext,
        projectId: String,
        pipelineId: String,
        buildId: String,
        name: String,
        value: Any
    )
    
    // 批量更新变量(Task 完成时使用)
    fun batchUpdateVariable(
        projectId: String,
        pipelineId: String,
        buildId: String,
        variables: List<BuildParameters>
    )
    
    // 获取所有变量
    fun getAllVariable(
        projectId: String,
        pipelineId: String,
        buildId: String,
        keys: Set<String>? = null
    ): Map<String, String>
    
    // 获取单个变量
    fun getVariable(
        projectId: String,
        pipelineId: String,
        buildId: String,
        varName: String
    ): String?
    
    // 模板变量替换
    fun replaceTemplate(
        projectId: String,
        buildId: String,
        template: String
    ): String
}

2.5 并发控制:分布式锁

// 文件: biz-base/.../control/lock/PipelineBuildVarLock.kt

class PipelineBuildVarLock(
    redisOperation: RedisOperation, 
    buildId: String, 
    name: String? = null
) : RedisLock(
    redisOperation = redisOperation,
    lockKey = "pipelineBuildVar:$buildId" + (name?.let { ":$it" } ?: ""),
    expiredTimeInSeconds = 10L  // 锁过期时间 10 秒
)

使用场景

  • 多个 Task 并发完成时,需要更新同一个构建的变量
  • 防止变量覆盖冲突

3. 变量创建与初始化

3.1 变量来源

┌─────────────────────────────────────────────────────────────────────────────┐
│                           变量来源优先级(从低到高)                          │
└─────────────────────────────────────────────────────────────────────────────┘

优先级 1 (最低): 流水线定义的默认值
    │
    ▼
优先级 2: 系统预置变量 (BK_CI_*)
    │
    ▼
优先级 3: 用户启动参数 (手动输入/API 传入)
    │
    ▼
优先级 4 (最高): 执行过程中插件输出的变量

3.2 系统预置变量

系统预置变量定义在 VariableType 枚举和 Constants 常量类中。

分类 变量示例 说明
基础构建 BK_CI_BUILD_ID, BK_CI_BUILD_NUM, BK_CI_BUILD_URL 构建标识和链接
流水线 BK_CI_PIPELINE_ID, BK_CI_PIPELINE_NAME, BK_CI_PIPELINE_VERSION 流水线信息
项目 BK_CI_PROJECT_NAME, BK_CI_PROJECT_NAME_CN 项目标识
触发信息 BK_CI_START_TYPE, BK_CI_START_USER_NAME, BK_CI_START_CHANNEL 触发来源
父子流水线 BK_CI_PARENT_PROJECT_ID, BK_CI_PARENT_PIPELINE_ID, BK_CI_PARENT_BUILD_ID 父流水线信息
执行上下文 BK_CI_BUILD_JOB_ID, BK_CI_BUILD_TASK_ID, BK_CI_ATOM_CODE 当前执行位置
Git/Webhook BK_CI_GIT_REPO_URL, BK_REPO_GIT_WEBHOOK_BRANCH, BK_CI_GIT_MR_TITLE 代码仓库信息
带前缀变量 BK_REPO_GIT_WEBHOOK_PUSH_COMMIT_MSG_1, BK_REPO_GIT_WEBHOOK_PUSH_ADD_FILE_1 支持通配的变量

完整变量列表请参考:api-process/.../enums/VariableType.kt
表达式语法映射(ci.xxx)请参考 6.2 节

3.3 触发参数解析(parseTriggerParam)

在构建启动时,用户通过前端手动输入或 API 传入的参数需要与流水线定义的参数进行合并和校验。这个过程由 BuildParametersCompatibilityTransformer.parseTriggerParam() 方法完成。

// 文件: biz-base/.../engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt

class V2BuildParametersCompatibilityTransformer : BuildParametersCompatibilityTransformer {

    /**
     * 解析前端手工启动传入的参数并与 TriggerContainer 的 BuildFormProperty 合并
     * 
     * @param userId 操作用户 ID
     * @param projectId 项目 ID
     * @param pipelineId 流水线 ID
     * @param paramProperties 流水线定义的参数列表(来自 TriggerContainer.params)
     * @param paramValues 用户传入的参数值(前端/API 传入)
     * @return 合并后的参数映射 Map<String, BuildParameters>
     */
    override fun parseTriggerParam(
        userId: String,
        projectId: String,
        pipelineId: String,
        paramProperties: List<BuildFormProperty>,
        paramValues: Map<String, String>
    ): MutableMap<String, BuildParameters> {
        
        val paramsMap = HashMap<String, BuildParameters>()
        
        paramProperties.forEach { param ->
            // 1. 变量名兼容转换:旧变量名 → 新变量名
            val key = PipelineVarUtil.oldVarToNewVar(param.id) ?: param.id
            
            // 2. 确定最终值
            val value = when {
                // 2.1 常量保护:constant=true 时强制使用默认值
                param.constant == true -> {
                    param.readOnly = true  // 强制设为只读
                    param.defaultValue
                }
                
                // 2.2 自定义文件版本控制处理
                param.type == BuildFormPropertyType.CUSTOM_FILE 
                    && param.enableVersionControl == true -> {
                    // 解析版本控制信息 JSON
                    val versionControlInfo = paramValues[key]?.let { str ->
                        JsonUtil.to(str, CustomFileVersionControlInfo::class.java)
                    }
                    versionControlInfo?.directory ?: param.defaultValue
                }
                
                // 2.3 普通参数:用户传入值覆盖默认值
                else -> {
                    paramValues[key] ?: paramValues[param.id] ?: param.defaultValue
                }
            }
            
            // 3. 空值校验(valueNotEmpty=true 时)
            if (param.valueNotEmpty == true && value.toString().isEmpty()) {
                // 检查是否因条件不满足而隐藏
                val isHidden = param.displayCondition?.any { (condKey, condValue) ->
                    paramValues[condKey] != condValue
                } ?: false
                
                // 参数未被隐藏时,抛出异常
                if (!isHidden) {
                    throw ErrorCodeException(
                        errorCode = ProcessMessageCode.ERROR_PIPELINE_BUILD_START_PARAM_NO_EMPTY,
                        params = arrayOf(param.id)
                    )
                }
            }
            
            // 4. 构建参数对象
            paramsMap[key] = BuildParameters(
                key = key,
                value = value,
                valueType = param.type,
                readOnly = param.readOnly,
                desc = param.desc,
                defaultValue = param.defaultValue
            )
        }
        
        return paramsMap
    }
}

核心处理逻辑

处理步骤 说明
变量名兼容 通过 PipelineVarUtil.oldVarToNewVar() 将旧变量名(如 pipeline.id)转换为新变量名(如 BK_CI_PIPELINE_ID
常量保护 如果参数定义为 constant=true,忽略用户传入值,强制使用默认值并设为只读
自定义文件 对于 CUSTOM_FILE 类型且启用版本控制的参数,解析 JSON 格式的版本控制信息
值覆盖 用户传入值优先级高于默认值(常量除外)
空值校验 如果 valueNotEmpty=true 且值为空,抛出异常(隐藏的参数跳过校验)
条件显示 根据 displayCondition 判断参数是否被隐藏,隐藏的参数跳过空值校验

调用场景

┌─────────────────────────────────────────────────────────────────────────────┐
│                        parseTriggerParam 调用场景                            │
└─────────────────────────────────────────────────────────────────────────────┘

1. 手动触发构建
   PipelineBuildFacadeService.buildManualStartup()
       └── buildParamCompatibilityTransformer.parseTriggerParam(...)

2. API 触发构建
   PipelineBuildFacadeService.startPipeline()
       └── buildParamCompatibilityTransformer.parseTriggerParam(...)

3. Webhook 触发构建
   PipelineBuildWebhookService.webhookTrigger()
       └── buildParamCompatibilityTransformer.parseTriggerParam(...)

4. 子流水线调用
   SubPipelineStartUpService.callPipelineStartup()
       └── buildParamCompatibilityTransformer.parseTriggerParam(...)

源码位置

类名 文件路径
BuildParametersCompatibilityTransformer biz-base/.../engine/compatibility/BuildParametersCompatibilityTransformer.kt
V2BuildParametersCompatibilityTransformer biz-base/.../engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt
CompatibilityConfig biz-base/.../engine/compatibility/CompatibilityConfig.kt

3.4 变量初始化核心方法

变量初始化的核心逻辑在 PipelineBuildService.initPipelineParamMap() 方法中,该方法负责填充系统预置变量到 pipelineParamMap

// 文件: biz-base/.../service/pipeline/PipelineBuildService.kt

class PipelineBuildService {
    
    /**
     * 初始化流水线参数映射
     * 填充系统预置变量到 pipelineParamMap
     */
    private fun initPipelineParamMap(
        buildId: String,
        startType: StartType,
        pipelineParamMap: MutableMap<String, BuildParameters>,
        userId: String,
        startValues: Map<String, String>?,
        pipeline: PipelineInfo,
        projectVO: ProjectVO?,
        channelCode: ChannelCode,
        isMobile: Boolean,
        debug: Boolean? = false,
        pipelineAuthorizer: String? = null,
        pipelineDialectType: String,
        failIfVariableInvalid: Boolean? = false
    ) {
        // 1. 触发用户信息
        val userName = when (startType) {
            StartType.PIPELINE -> pipelineParamMap[PIPELINE_START_PIPELINE_USER_ID]?.value
            StartType.WEB_HOOK -> pipelineParamMap[PIPELINE_START_WEBHOOK_USER_ID]?.value
            StartType.SERVICE -> pipelineParamMap[PIPELINE_START_SERVICE_USER_ID]?.value
            StartType.MANUAL -> pipelineParamMap[PIPELINE_START_MANUAL_USER_ID]?.value
            StartType.TIME_TRIGGER -> pipelineParamMap[PIPELINE_START_TIME_TRIGGER_USER_ID]?.value
            StartType.REMOTE -> startValues?.get(PIPELINE_START_REMOTE_USER_ID)
        } ?: userId
        
        pipelineParamMap[PIPELINE_START_USER_ID] = BuildParameters(
            key = PIPELINE_START_USER_ID, value = userId
        )
        pipelineParamMap[PIPELINE_START_USER_NAME] = BuildParameters(
            key = PIPELINE_START_USER_NAME, value = userName
        )
        
        // 2. 流水线基础信息
        pipelineParamMap[PIPELINE_NAME] = BuildParameters(
            key = PIPELINE_NAME,
            value = startValues?.get(PIPELINE_NAME) ?: pipeline.pipelineName
        )
        pipelineParamMap[PROJECT_NAME_CHINESE] = BuildParameters(
            key = PROJECT_NAME_CHINESE,
            value = projectVO?.projectName ?: "",
            valueType = BuildFormPropertyType.STRING
        )
        pipelineParamMap[PIPELINE_ID] = BuildParameters(
            PIPELINE_ID, pipeline.pipelineId, readOnly = true
        )
        pipelineParamMap[PROJECT_NAME] = BuildParameters(
            PROJECT_NAME, pipeline.projectId, readOnly = true
        )
        pipelineParamMap[PIPELINE_VERSION] = BuildParameters(
            PIPELINE_VERSION, pipeline.version, readOnly = true
        )
        
        // 3. 构建信息
        pipelineParamMap[PIPELINE_BUILD_ID] = BuildParameters(
            PIPELINE_BUILD_ID, buildId, readOnly = true
        )
        pipelineParamMap[PIPELINE_BUILD_URL] = BuildParameters(
            key = PIPELINE_BUILD_URL,
            value = pipelineUrlBean.genBuildDetailUrl(
                projectCode = pipeline.projectId,
                pipelineId = pipeline.pipelineId,
                buildId = buildId,
                position = null,
                stageId = null,
                needShortUrl = false
            ),
            readOnly = true
        )
        pipelineParamMap[PIPELINE_BUILD_MSG] = BuildParameters(
            key = PIPELINE_BUILD_MSG,
            value = BuildMsgUtils.getBuildMsg(
                buildMsg = startValues?.get(PIPELINE_BUILD_MSG)
                    ?: pipelineParamMap[PIPELINE_BUILD_MSG]?.value?.toString(),
                startType = startType,
                channelCode = channelCode
            ),
            readOnly = true
        )
        
        // 4. 触发信息
        pipelineParamMap[PIPELINE_START_TYPE] = BuildParameters(
            key = PIPELINE_START_TYPE, value = startType.name, readOnly = true
        )
        pipelineParamMap[PIPELINE_START_CHANNEL] = BuildParameters(
            key = PIPELINE_START_CHANNEL, value = channelCode.name, readOnly = true
        )
        pipelineParamMap[PIPELINE_START_MOBILE] = BuildParameters(
            key = PIPELINE_START_MOBILE, value = isMobile, readOnly = true
        )
        
        // 5. 流水线创建/更新用户
        pipelineParamMap[PIPELINE_CREATE_USER] = BuildParameters(
            key = PIPELINE_CREATE_USER, value = pipeline.creator, readOnly = true
        )
        pipelineParamMap[PIPELINE_UPDATE_USER] = BuildParameters(
            key = PIPELINE_UPDATE_USER, value = pipeline.lastModifyUser, readOnly = true
        )
        
        // 6. 调试模式标识
        pipelineParamMap[PIPELINE_BUILD_DEBUG] = BuildParameters(
            PIPELINE_BUILD_DEBUG, debug ?: false, readOnly = true
        )
        
        // 7. 流水线语法类型
        pipelineParamMap[PIPELINE_DIALECT] = BuildParameters(
            PIPELINE_DIALECT, pipelineDialectType, readOnly = true
        )
        
        // 8. 自定义触发材料(可选)
        startValues?.get(BK_CI_MATERIAL_ID)?.let {
            pipelineParamMap[BK_CI_MATERIAL_ID] = BuildParameters(
                key = BK_CI_MATERIAL_ID, value = it, readOnly = true
            )
        }
        
        // 9. 流水线权限代持人(可选)
        pipelineAuthorizer?.let {
            pipelineParamMap[BK_CI_AUTHORIZER] = BuildParameters(
                key = BK_CI_AUTHORIZER, value = it, readOnly = true
            )
        }
        
        // 10. 链路追踪信息
        val bizId = MDC.get(TraceTag.BIZID)
        if (!bizId.isNullOrBlank()) {
            pipelineParamMap[TraceTag.TRACE_HEADER_DEVOPS_BIZID] = BuildParameters(
                key = TraceTag.TRACE_HEADER_DEVOPS_BIZID, value = bizId
            )
        }
    }
}

3.5 构建启动上下文

StartBuildContext 是一个数据类,封装了构建启动所需的所有上下文信息。它的 init 方法接收已经初始化好的 pipelineParamMap,并生成最终的构建参数列表。

// 文件: api-process/.../pojo/app/StartBuildContext.kt

data class StartBuildContext(
    val projectId: String,
    val pipelineId: String,
    val buildId: String,
    val resourceVersion: Int,
    val actionType: ActionType,           // START 或 RETRY
    val executeCount: Int,                 // 执行次数
    val userId: String,                    // 操作用户
    val triggerUser: String,               // 触发用户
    val startType: StartType,              // 触发类型
    val parentBuildId: String?,            // 父流水线构建 ID
    val parentTaskId: String?,             // 父流水线任务 ID
    val channelCode: ChannelCode,          // 渠道
    val variables: Map<String, String>,    // 变量映射(最终结果)
    val pipelineParamMap: MutableMap<String, BuildParameters>,  // 参数映射
    val buildParameters: MutableList<BuildParameters>,          // 构建参数列表
    // ...
) {
    companion object {
        /**
         * 初始化构建上下文
         * 注意: pipelineParamMap 在调用前已经被 initPipelineParamMap() 填充
         */
        fun init(
            projectId: String,
            pipelineId: String,
            buildId: String,
            resourceVersion: Int,
            modelStr: String,
            pipelineSetting: PipelineSetting?,
            currentBuildNo: Int?,
            triggerReviewers: List<String>?,
            pipelineParamMap: MutableMap<String, BuildParameters>,  // 已初始化
            webHookStartParam: MutableMap<String, BuildParameters>,
            realStartParamKeys: List<String>,  // 用户定义的参数 Key 列表
            debug: Boolean,
            versionName: String?,
            yamlVersion: String?
        ): StartBuildContext {
            // 1. 生成构建参数列表(用于写入 T_PIPELINE_BUILD_VAR)
            val buildParam = genOriginStartParamsList(realStartParamKeys, pipelineParamMap)
            
            // 2. 将 pipelineParamMap 转换为 Map<String, String>
            val params: Map<String, String> = pipelineParamMap.values
                .associate { it.key to it.value.toString() }
            
            // 3. 解析重试相关参数
            val retryStartTaskId = params[PIPELINE_RETRY_START_TASK_ID]
            val (actionType, executeCount, isStageRetry) = if (params[PIPELINE_RETRY_COUNT] != null) {
                Triple(ActionType.RETRY, retryCount, isStageRetry)
            } else {
                Triple(ActionType.START, 1, false)
            }
            
            // 4. 构建上下文对象
            return StartBuildContext(
                projectId = projectId,
                pipelineId = pipelineId,
                buildId = buildId,
                variables = params,  // 最终变量映射
                buildParameters = buildParam,  // 构建参数列表
                pipelineParamMap = pipelineParamMap,
                actionType = actionType,
                executeCount = executeCount,
                userId = params[PIPELINE_START_USER_ID]!!,
                triggerUser = params[PIPELINE_START_USER_NAME]!!,
                startType = StartType.valueOf(params[PIPELINE_START_TYPE]!!),
                parentBuildId = params[PIPELINE_START_PARENT_BUILD_ID],
                parentTaskId = params[PIPELINE_START_PARENT_BUILD_TASK_ID],
                channelCode = ChannelCode.valueOf(params[PIPELINE_START_CHANNEL] ?: "BS"),
                // ...
            )
        }
        
        /**
         * 生成构建参数列表
         * 根据用户定义的参数 Key 从 pipelineParamMap 中提取参数
         * 并为每个参数添加 variables. 前缀版本
         */
        private fun genOriginStartParamsList(
            realStartParamKeys: List<String>,
            pipelineParamMap: MutableMap<String, BuildParameters>
        ): ArrayList<BuildParameters> {
            val originStartParams = ArrayList<BuildParameters>()
            val originStartContexts = HashMap<String, BuildParameters>()
            
            realStartParamKeys.forEach { key ->
                pipelineParamMap[key]?.let { param ->
                    originStartParams.add(param)
                    // 添加 variables.xxx 前缀版本
                    fillContextPrefix(param, originStartContexts)
                }
            }
            
            // 将 variables.xxx 版本也加入 pipelineParamMap
            pipelineParamMap.putAll(originStartContexts)
            
            // 添加特殊参数
            pipelineParamMap[BUILD_NO]?.let { originStartParams.add(it) }
            pipelineParamMap[PIPELINE_BUILD_MSG]?.let { originStartParams.add(it) }
            pipelineParamMap[PIPELINE_RETRY_COUNT]?.let { originStartParams.add(it) }
            
            return originStartParams
        }
        
        /**
         * 为参数添加 variables. 前缀
         * MY_VAR → variables.MY_VAR
         */
        private fun fillContextPrefix(
            param: BuildParameters,
            originStartContexts: HashMap<String, BuildParameters>
        ) {
            val key = param.key
            if (key.startsWith("variables.")) {
                originStartContexts[key] = param
            } else {
                val ctxKey = "variables.$key"
                originStartContexts[ctxKey] = param.copy(key = ctxKey)
            }
        }
    }
}

3.6 变量初始化流程

┌─────────────────────────────────────────────────────────────────────────────┐
│ PipelineBuildFacadeService.buildManualStartup() / startPipeline()            │
│ 源码: biz-process/.../service/builds/PipelineBuildFacadeService.kt          │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 0. 解析触发参数
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ buildParamCompatibilityTransformer.parseTriggerParam()                       │
│ 源码: biz-base/.../engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt │
│                                                                              │
│ 处理用户传入参数:                                                            │
│ ├── 变量名兼容转换(旧变量名 → 新变量名)                                   │
│ ├── 常量保护(constant=true 时强制使用默认值)                              │
│ ├── 自定义文件版本控制处理                                                  │
│ ├── 用户传入值覆盖默认值                                                    │
│ └── 空值校验(valueNotEmpty=true)                                          │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 1. pipelineParamMap 已包含用户启动参数和流水线定义参数
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PipelineBuildService.startPipeline()                                         │
│ 源码: biz-base/.../service/pipeline/PipelineBuildService.kt:127             │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 2. 填充系统预置变量
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ initPipelineParamMap()                                                       │
│ 源码: biz-base/.../service/pipeline/PipelineBuildService.kt:291             │
│                                                                              │
│ 填充系统预置变量到 pipelineParamMap:                                         │
│ ├── PIPELINE_START_USER_ID, PIPELINE_START_USER_NAME (触发用户)             │
│ ├── PIPELINE_NAME, PROJECT_NAME, PROJECT_NAME_CHINESE (项目/流水线信息)     │
│ ├── PIPELINE_BUILD_ID, PIPELINE_BUILD_URL (构建信息)                        │
│ ├── PIPELINE_START_TYPE, PIPELINE_START_CHANNEL (触发信息)                  │
│ ├── PIPELINE_VERSION, PIPELINE_CREATE_USER, PIPELINE_UPDATE_USER            │
│ └── 其他系统变量...                                                          │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 3. 所有变量已填充到 pipelineParamMap
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ StartBuildContext.init()                                                     │
│ 源码: api-process/.../pojo/app/StartBuildContext.kt:276                     │
│                                                                              │
│ 生成构建上下文:                                                              │
│ ├── genOriginStartParamsList() → 生成 buildParameters 列表                  │
│ ├── 为用户变量添加 variables.xxx 前缀版本                                   │
│ └── 解析重试相关参数 (actionType, executeCount)                             │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 4. 执行拦截器链检查
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ pipelineInterceptorChain.filter()                                            │
│                                                                              │
│ 拦截器检查:                                                                  │
│ ├── 排队检查                                                                 │
│ ├── 并发组检查                                                               │
│ ├── 配额检查                                                                 │
│ └── 其他前置检查...                                                          │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 5. 检查通过,开始创建构建
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PipelineRuntimeService.startBuild()                                          │
│ 源码: biz-base/.../engine/service/PipelineRuntimeService.kt                 │
│                                                                              │
│ 创建构建记录:                                                                │
│ ├── T_PIPELINE_BUILD_HISTORY (构建历史)                                      │
│ ├── T_PIPELINE_BUILD_STAGE (Stage 状态)                                      │
│ ├── T_PIPELINE_BUILD_CONTAINER (Job 状态)                                    │
│ └── T_PIPELINE_BUILD_TASK (Task 状态)                                        │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 6. 批量保存变量
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ buildVariableService.startBuildBatchSaveWithoutThreadSafety()                │
│ 源码: biz-base/.../service/BuildVariableService.kt                          │
│                                                                              │
│ 参数: context.buildParameters (List<BuildParameters>)                        │
│ 说明: 构建启动时无并发,不需要加锁                                           │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 7. 写入数据库
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PipelineBuildVarDao.batchSave()                                              │
│ 源码: biz-base/.../engine/dao/PipelineBuildVarDao.kt                        │
│                                                                              │
│ INSERT INTO T_PIPELINE_BUILD_VAR                                             │
│ (BUILD_ID, KEY, VALUE, PROJECT_ID, PIPELINE_ID, VAR_TYPE, READ_ONLY)        │
│ VALUES (?, ?, ?, ?, ?, ?, ?)                                                 │
└─────────────────────────────────────────────────────────────────────────────┘

4. 变量动态更新

4.1 插件输出变量机制

插件通过 output.json 文件输出变量,Worker 读取后上报到引擎。

┌─────────────────────────────────────────────────────────────────────────────┐
│ Worker 端 - 插件执行                                                         │
│ 源码: worker-common/.../task/market/MarketAtomTask.kt                       │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 1. 执行插件
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 插件进程                                                                     │
│                                                                              │
│ 插件通过 SDK 输出变量:                                                       │
│ ├── Python: self.set_output("key", "value")                                 │
│ ├── Java: atomContext.setOutput("key", "value")                             │
│ └── NodeJS: atomContext.setOutput("key", "value")                           │
│                                                                              │
│ 最终写入: {workspace}/.atomOutput/output.json                                │
│ {                                                                            │
│     "status": "success",                                                     │
│     "data": {                                                                │
│         "outputs": {                                                         │
│             "key1": "value1",                                                │
│             "key2": "value2"                                                 │
│         }                                                                    │
│     }                                                                        │
│ }                                                                            │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 2. 读取输出
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ MarketAtomTask.output()                                                      │
│                                                                              │
│ fun output(atomResult: AtomResult): Map<String, String> {                    │
│     val outputs = mutableMapOf<String, String>()                             │
│     atomResult.data?.forEach { (key, value) ->                               │
│         if (key == "outputs") {                                              │
│             (value as? Map<*, *>)?.forEach { (k, v) ->                       │
│                 outputs[k.toString()] = v.toString()                         │
│             }                                                                │
│         }                                                                    │
│     }                                                                        │
│     return outputs                                                           │
│ }                                                                            │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 3. 上报结果
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EngineService.completeTask(buildResult)                                      │
│                                                                              │
│ buildResult 包含:                                                            │
│ ├── status: 任务状态                                                         │
│ ├── message: 错误信息                                                        │
│ └── buildResult: Map<String, String> 输出变量                                │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 引擎端变量更新

// 文件: biz-base/.../engine/service/vmbuild/EngineVMBuildService.kt

class EngineVMBuildService {
    
    fun buildCompleteTask(
        projectId: String,
        pipelineId: String,
        buildId: String,
        vmSeqId: String,
        result: BuildTaskResult
    ) {
        // ...
        
        // 更新变量
        executeCompleteTaskBus(
            projectId = projectId,
            pipelineId = pipelineId,
            buildId = buildId,
            result = result,
            buildInfo = buildInfo
        )
    }
    
    private fun executeCompleteTaskBus(
        projectId: String,
        pipelineId: String,
        buildId: String,
        result: BuildTaskResult,
        buildInfo: BuildInfo
    ) {
        // 将插件输出变量写入数据库
        if (result.buildResult.isNotEmpty()) {
            val variables = result.buildResult.map { (key, value) ->
                BuildParameters(
                    key = key,
                    value = value,
                    valueType = BuildFormPropertyType.STRING,
                    readOnly = false
                )
            }
            
            // 批量更新变量
            buildVariableService.batchUpdateVariable(
                projectId = projectId,
                pipelineId = pipelineId,
                buildId = buildId,
                variables = variables
            )
        }
        
        // 处理特殊变量: BK_CI_BUILD_REMARK
        writeRemark(projectId, pipelineId, buildId, result)
    }
}

4.3 变量更新流程

┌─────────────────────────────────────────────────────────────────────────────┐
│ BuildVariableService.batchUpdateVariable()                                   │
│ 源码: biz-base/.../service/BuildVariableService.kt                          │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 1. 获取分布式锁
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PipelineBuildVarLock(redisOperation, buildId).use { lock ->                  │
│     lock.lock()  // 获取锁,防止并发冲突                                     │
│     // ...                                                                   │
│ }                                                                            │
│                                                                              │
│ 锁 Key: pipelineBuildVar:{buildId}                                           │
│ 过期时间: 10 秒                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 2. 查询现有变量
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ val existingVars = pipelineBuildVarDao.getVarsWithType(                      │
│     dslContext, projectId, buildId                                           │
│ )                                                                            │
│                                                                              │
│ 构建 existingMap: Map<String, BuildParameters>                               │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 3. 区分新增和更新
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ val toInsert = mutableListOf<BuildParameters>()                              │
│ val toUpdate = mutableListOf<BuildParameters>()                              │
│                                                                              │
│ variables.forEach { variable ->                                              │
│     val existing = existingMap[variable.key]                                 │
│     if (existing == null) {                                                  │
│         toInsert.add(variable)  // 新变量                                    │
│     } else if (!existing.readOnly) {                                         │
│         toUpdate.add(variable)  // 已存在且非只读,更新                      │
│     }                                                                        │
│     // 只读变量跳过                                                          │
│ }                                                                            │
└─────────────────────────────────────────────────────────────────────────────┘
      │
      │ 4. 执行数据库操作
      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ // 批量插入新变量                                                            │
│ if (toInsert.isNotEmpty()) {                                                 │
│     pipelineBuildVarDao.batchSave(dslContext, projectId, pipelineId,         │
│         buildId, toInsert)                                                   │
│ }                                                                            │
│                                                                              │
│ // 批量更新已有变量                                                          │
│ if (toUpdate.isNotEmpty()) {                                                 │
│     pipelineBuildVarDao.batchUpdate(dslContext, projectId, pipelineId,       │
│         buildId, toUpdate)                                                   │
│ }                                                                            │
└─────────────────────────────────────────────────────────────────────────────┘

5. 变量传递策略

5.1 Job 间变量传递

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Job 间变量传递机制                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Stage-1
├── Job-A (jobId: job_a)
│   ├── Step-1 (stepId: step_1)
│   │   └── 输出: key1 = "value1"
│   └── Step-2 (stepId: step_2)
│       └── 输出: key2 = "value2"
│
└── Job-B (jobId: job_b)
    └── Step-3
        └── 读取: ${{ jobs.job_a.steps.step_1.outputs.key1 }}
                  ${{ jobs.job_a.steps.step_2.outputs.key2 }}

Stage-2
└── Job-C
    └── Step-4
        └── 读取: ${{ jobs.job_a.steps.step_1.outputs.key1 }}
                  (可以跨 Stage 读取)

5.2 上下文服务

// 文件: biz-base/.../service/PipelineContextService.kt

@Service
class PipelineContextService {
    
    /**
     * 构建完整的执行上下文
     * 包含所有 Job 和 Step 的输出变量
     */
    fun getAllBuildContext(
        projectId: String,
        pipelineId: String,
        buildId: String,
        containerId: String? = null,
        taskId: String? = null
    ): Map<String, String> {
        val context = mutableMapOf<String, String>()
        
        // 1. 获取所有变量
        val allVars = buildVariableService.getAllVariable(projectId, pipelineId, buildId)
        context.putAll(allVars)
        
        // 2. 构建 Job 上下文
        buildJobContext(projectId, pipelineId, buildId, context)
        
        // 3. 构建 Step 上下文(当前 Job 内)
        if (containerId != null) {
            buildStepContext(projectId, pipelineId, buildId, containerId, context)
        }
        
        // 4. 填充 CI 预置变量
        PipelineVarUtil.fillContextVarMap(context)
        
        return context
    }
    
    /**
     * 构建 Job 上下文
     * 格式: jobs.<jobId>.steps.<stepId>.outputs.<key>
     */
    private fun buildJobContext(
        projectId: String,
        pipelineId: String,
        buildId: String,
        context: MutableMap<String, String>
    ) {
        // 查询所有已完成的 Task
        val tasks = pipelineBuildTaskDao.listByBuildId(dslContext, projectId, buildId)
        
        tasks.forEach { task ->
            val jobId = task.containerId
            val stepId = task.taskId
            
            // 获取 Task 输出变量
            val outputs = getTaskOutputs(projectId, buildId, stepId)
            
            outputs.forEach { (key, value) ->
                // 格式: jobs.job_a.steps.step_1.outputs.key1
                context["jobs.$jobId.steps.$stepId.outputs.$key"] = value
            }
        }
    }
    
    /**
     * 构建 Step 上下文(当前 Job 内)
     * 格式: steps.<stepId>.outputs.<key>
     */
    private fun buildStepContext(
        projectId: String,
        pipelineId: String,
        buildId: String,
        containerId: String,
        context: MutableMap<String, String>
    ) {
        // 查询当前 Job 的所有 Task
        val tasks = pipelineBuildTaskDao.listByContainerId(
            dslContext, projectId, buildId, containerId
        )
        
        tasks.forEach { task ->
            val stepId = task.taskId
            val outputs = getTaskOutputs(projectId, buildId, stepId)
            
            outputs.forEach { (key, value) ->
                // 格式: steps.step_1.outputs.key1
                context["steps.$stepId.outputs.$key"] = value
            }
        }
    }
}

5.3 父子流水线变量传递

// 文件: biz-process/.../service/SubPipelineStartUpService.kt

@Service
class SubPipelineStartUpService {
    
    /**
     * 调用子流水线
     */
    fun callPipelineStartup(
        projectId: String,
        parentPipelineId: String,
        parentBuildId: String,
        parentTaskId: String,
        subPipelineId: String,
        params: Map<String, String>,
        channelCode: ChannelCode
    ): BuildId {
        // 1. 构建传递给子流水线的参数
        val subParams = mutableMapOf<String, String>()
        
        // 2. 添加父流水线信息(系统变量)
        subParams[PIPELINE_START_PARENT_PROJECT_ID] = projectId
        subParams[PIPELINE_START_PARENT_PIPELINE_ID] = parentPipelineId
        subParams[PIPELINE_START_PARENT_BUILD_ID] = parentBuildId
        subParams[PIPELINE_START_PARENT_BUILD_NUM] = getParentBuildNum(parentBuildId)
        subParams[PIPELINE_START_PARENT_BUILD_TASK_ID] = parentTaskId
        
        // 3. 添加用户指定的传递参数
        subParams.putAll(params)
        
        // 4. 启动子流水线
        return pipelineBuildService.startPipeline(
            projectId = projectId,
            pipelineId = subPipelineId,
            startParams = subParams,
            startType = StartType.PIPELINE,
            triggerUser = getTriggerUser(parentBuildId),
            channelCode = channelCode
        )
    }
}

父子流水线传递的系统变量

变量名 说明
BK_CI_PARENT_PROJECT_ID 父流水线项目 ID
BK_CI_PARENT_PIPELINE_ID 父流水线 ID
BK_CI_PARENT_BUILD_ID 父流水线构建 ID
PIPELINE_START_PARENT_BUILD_NUM 父流水线构建号
PIPELINE_START_PARENT_BUILD_TASK_ID 父流水线调用任务 ID

6. 变量表达式解析

6.1 表达式语法

┌─────────────────────────────────────────────────────────────────────────────┐
│                           表达式语法规范                                      │
└─────────────────────────────────────────────────────────────────────────────┘

基本语法: ${{ <expression> }}

命名空间:
├── variables.xxx      → 流水线变量
├── ci.xxx             → CI 预置变量
├── settings.xxx       → 流水线设置
├── jobs.xxx           → Job 输出
├── steps.xxx          → Step 输出
└── matrix.xxx         → 矩阵变量

示例:
├── ${{ variables.MY_VAR }}
├── ${{ ci.build_id }}
├── ${{ jobs.job_a.steps.step_1.outputs.result }}
├── ${{ steps.step_1.outputs.result }}
└── ${{ matrix.os }}

运算符:
├── 比较: ==, !=, <, >, <=, >=
├── 逻辑: &&, ||, !
├── 字符串: contains(), startsWith(), endsWith()
└── 条件: if-else

条件表达式示例:
├── ${{ variables.ENV == 'prod' }}
├── ${{ ci.build_num > 100 }}
├── ${{ contains(variables.BRANCH, 'release') }}
└── ${{ startsWith(ci.branch, 'feature/') }}

6.2 CI 预置变量映射(ci.xxx ↔ 数据库变量)

数据库 T_PIPELINE_BUILD_VAR 中存储的是 BK_CI_* 格式的变量名,而表达式中使用的是 ci.xxx 命名空间。两者之间通过 PipelineVarUtil.contextVarMappingBuildVar 进行映射转换。

常用映射表

分类 表达式 ci.xxx 数据库变量 BK_CI_*
构建信息 ci.build_id BK_CI_BUILD_ID
ci.build_num BK_CI_BUILD_NUM
ci.build_url BK_CI_BUILD_URL
流水线 ci.pipeline_id BK_CI_PIPELINE_ID
ci.pipeline_name BK_CI_PIPELINE_NAME
项目 ci.project_id BK_CI_PROJECT_NAME
ci.project_name BK_CI_PROJECT_NAME_CN
触发信息 ci.actor BK_CI_START_USER_NAME
ci.build_start_type BK_CI_START_TYPE
Git ci.branch BK_REPO_GIT_WEBHOOK_BRANCH
ci.sha BK_CI_GIT_SHA
ci.ref BK_CI_GIT_REF
ci.repo_url BK_CI_GIT_REPO_URL
MR/PR ci.mr_id BK_CI_GIT_MR_ID
ci.mr_title BK_CI_GIT_MR_TITLE
ci.head_ref BK_CI_GIT_HEAD_REF (源分支)
ci.base_ref BK_CI_GIT_BASE_REF (目标分支)

完整映射请参考源码:api-process/.../utils/PipelineVarUtil.kt 中的 contextVarMappingBuildVar

核心方法

// 文件: api-process/.../utils/PipelineVarUtil.kt

object PipelineVarUtil {
    
    /** 填充 CI 预置变量到上下文,将 BK_CI_* 转换为 ci.xxx */
    fun fillContextVarMap(varMap: MutableMap<String, String>, buildVar: Map<String, String>)
    
    /** 根据 ci.xxx 获取对应的数据库变量名 */
    fun fetchVarName(contextKey: String): String?
    
    /** 根据数据库变量名获取对应的 ci.xxx */
    fun fetchReverseVarName(varKey: String): String?
}

转换流程

数据库: T_PIPELINE_BUILD_VAR
┌──────────────────────────────────┐
│ KEY = "BK_CI_BUILD_ID"           │
│ VALUE = "b-xxx-xxx"              │
└──────────────────────────────────┘
       │
       │ BuildVariableService.getAllVariable()
       ▼
buildVar: { "BK_CI_BUILD_ID" → "b-xxx-xxx" }
       │
       │ PipelineVarUtil.fillContextVarMap()
       ▼
contextMap: {
    "BK_CI_BUILD_ID" → "b-xxx-xxx",  // 原始变量保留
    "ci.build_id" → "b-xxx-xxx"       // 新增 ci.xxx 映射
}
       │
       ▼
表达式解析器可以同时识别两种写法:
├── ${{ ci.build_id }}        ✅
└── ${{ BK_CI_BUILD_ID }}     ✅

使用两种写法的原因

原因 说明
历史兼容 保留 BK_CI_* 确保老流水线不受影响
场景适配 Shell 用环境变量 $BK_CI_BUILD_ID,YAML 用表达式 ${{ ci.build_id }}
业界对齐 ci.xxx 风格与 GitHub Actions 的 github.xxx 一致
表达式支持 ${{ }} 语法支持条件判断如 ${{ ci.build_num > 100 }}

6.3 表达式解析器

表达式解析涉及三个核心类,它们协同工作完成变量替换:

类名 文件路径 职责
ExpressionParser common-expression/.../ExpressionParser.kt 表达式语法解析和计算
EnvReplacementParser common-pipeline/.../EnvReplacementParser.kt 字符串中的变量替换
ExprReplacementUtil common-pipeline/.../utils/ExprReplacementUtil.kt 表达式替换工具方法

解析流程

输入: "Build ${{ ci.build_num }} on ${{ ci.branch }}"
       │
       ▼
EnvReplacementParser.parse()
       │
       ├── 正则匹配 ${{ ... }}
       │
       ▼
ExpressionParser.evaluateByMap()
       │
       ├── 构建表达式上下文(按命名空间分类)
       ├── 解析表达式树
       └── 计算并返回结果
       │
       ▼
输出: "Build 123 on master"

核心方法

// ExpressionParser - 表达式计算
fun evaluateByMap(expression: String, variables: Map<String, String>): ExpressionResult

// EnvReplacementParser - 变量替换
fun parse(value: String, contextMap: Map<String, String>): String

// ExprReplacementUtil - 工具方法
fun parseExpression(value: String, contextMap: Map<String, String>): String

支持的语法

语法 示例 优先级
${{ }} 表达式 ${{ ci.build_num > 100 }} 最高
${} 变量引用 ${BK_CI_BUILD_ID}
$xxx 简单变量 $BK_CI_BUILD_ID 最低

7. 变量工具类

7.1 新旧变量名映射

BK-CI 经历了三代变量命名演进,需要兼容处理:

代次 格式 示例
第一代 点分隔 pipeline.build.id
第二代 环境变量风格 BK_CI_BUILD_ID
第三代 表达式命名空间 ci.build_id
// 文件: api-process/.../utils/PipelineVarUtil.kt

object PipelineVarUtil {
    
    // 旧变量名 → 新变量名映射
    private val oldVarMappingNewVar = mapOf(
        "pipeline.id" to PIPELINE_ID,                    // BK_CI_PIPELINE_ID
        "pipeline.name" to PIPELINE_NAME,                // BK_CI_PIPELINE_NAME
        "pipeline.build.id" to PIPELINE_BUILD_ID,        // BK_CI_BUILD_ID
        "pipeline.build.num" to PIPELINE_BUILD_NUM,      // BK_CI_BUILD_NUM
        "pipeline.start.user.id" to PIPELINE_START_USER_ID,  // BK_CI_START_USER_ID
        "project.name" to PROJECT_NAME,                  // BK_CI_PROJECT_NAME
        "project.name.chinese" to PROJECT_NAME_CHINESE,  // BK_CI_PROJECT_NAME_CN
        // ...
    )
    
    /** 旧变量名转新变量名 */
    fun oldVarToNewVar(oldVarName: String): String?
    
    /** 新变量名转旧变量名 */
    fun newVarToOldVar(newVarName: String): String?
    
    /** 混合新旧变量(兼容处理),同时设置新旧两个变量名 */
    fun mixOldVarAndNewVar(vars: MutableMap<String, String>, varName: String, value: String)
    
    /** 填充 CI 预置变量到上下文(详见 6.2 节) */
    fun fillContextVarMap(varMap: MutableMap<String, String>, buildVar: Map<String, String>)
}

8. 最佳实践

8.1 变量命名规范

推荐命名规范:

1. 用户自定义变量
   ├── 使用大写字母和下划线: MY_VARIABLE
   ├── 添加业务前缀: DEPLOY_ENV, BUILD_TYPE
   └── 避免与系统变量冲突: 不要使用 BK_CI_ 前缀

2. 插件输出变量
   ├── 使用小写字母: result, version, artifact_path
   └── 添加插件前缀: git_commit_id, docker_image_tag

3. 敏感变量
   ├── 使用凭证管理: 不要直接存储密码
   └── 标记为只读: readOnly = true

8.2 性能优化建议

// 1. 批量操作优先
// ❌ 不推荐: 循环单个保存
variables.forEach { variable ->
    buildVariableService.setVariable(projectId, pipelineId, buildId, variable.key, variable.value)
}

// ✅ 推荐: 批量保存
buildVariableService.batchUpdateVariable(projectId, pipelineId, buildId, variables)

// 2. 按需查询
// ❌ 不推荐: 获取所有变量
val allVars = buildVariableService.getAllVariable(projectId, pipelineId, buildId)

// ✅ 推荐: 指定需要的变量
val vars = buildVariableService.getAllVariable(
    projectId, pipelineId, buildId,
    keys = setOf("MY_VAR1", "MY_VAR2")
)

// 3. 避免频繁锁竞争
// 插件输出变量尽量一次性输出,减少 completeTask 调用次数

8.3 调试技巧

# 在流水线中打印所有变量
- script: |
    echo "=== 系统变量 ==="
    echo "BUILD_ID: ${{ ci.build_id }}"
    echo "PIPELINE_ID: ${{ ci.pipeline_id }}"
    echo "BUILD_NUM: ${{ ci.build_num }}"
    
    echo "=== 用户变量 ==="
    echo "MY_VAR: ${{ variables.MY_VAR }}"
    
    echo "=== Job 输出 ==="
    echo "Result: ${{ jobs.job_a.steps.step_1.outputs.result }}"

9. 源码位置索引

功能 类名 文件路径
存储层
变量 DAO PipelineBuildVarDao biz-base/.../engine/dao/PipelineBuildVarDao.kt
变量服务 BuildVariableService biz-base/.../service/BuildVariableService.kt
分布式锁 PipelineBuildVarLock biz-base/.../control/lock/PipelineBuildVarLock.kt
初始化
触发参数解析 V2BuildParametersCompatibilityTransformer.parseTriggerParam() biz-base/.../engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt
参数转换器接口 BuildParametersCompatibilityTransformer biz-base/.../engine/compatibility/BuildParametersCompatibilityTransformer.kt
变量初始化 PipelineBuildService.initPipelineParamMap() biz-base/.../service/pipeline/PipelineBuildService.kt:291
构建上下文 StartBuildContext api-process/.../pojo/app/StartBuildContext.kt
运行时服务 PipelineRuntimeService biz-base/.../engine/service/PipelineRuntimeService.kt
动态更新
引擎服务 EngineVMBuildService biz-base/.../engine/service/vmbuild/EngineVMBuildService.kt
插件任务 MarketAtomTask worker-common/.../task/market/MarketAtomTask.kt
传递
上下文服务 PipelineContextService biz-base/.../service/PipelineContextService.kt
子流水线 SubPipelineStartUpService biz-process/.../service/SubPipelineStartUpService.kt
解析
表达式解析 ExpressionParser common-expression/.../ExpressionParser.kt
环境替换 EnvReplacementParser common-pipeline/.../EnvReplacementParser.kt
替换工具 ExprReplacementUtil common-pipeline/.../utils/ExprReplacementUtil.kt
工具
变量工具 PipelineVarUtil api-process/.../utils/PipelineVarUtil.kt
变量类型 VariableType api-process/.../enums/VariableType.kt
常量定义 Constants api-process/.../utils/Constants.kt

10. 相关 Skill

  • 00-bkci-global-architecture - 全局架构指南
  • 26-pipeline-variable-extension - 流水线变量字段扩展指南
  • 29-process-module-architecture - Process 模块架构总览
  • 42-worker-module-architecture - Worker 执行器模块架构