Claude Code Plugins

Community-maintained marketplace

Feedback

26-pipeline-variable-extension

@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 26-pipeline-variable-extension
description 流水线变量字段扩展指南,涵盖变量字段定义、类型扩展、变量作用域、变量继承、自定义变量处理。当用户扩展流水线变量、添加新变量字段、处理变量作用域或实现变量继承时使用。

Skill 26: 流水线变量字段扩展

适用场景

当需要为流水线变量(Pipeline Variable)新增字段时,本指南提供完整的改动路径和规范。

核心概念

两种变量模型

BK-CI 中流水线变量存在两种数据模型的双向转换:

  1. BuildFormProperty(后端内部模型)

    • 位置:common-pipeline/src/main/kotlin/.../BuildFormProperty.kt
    • 用途:微服务内部使用,数据库存储格式
    • 特点:完整的字段定义,包含 Swagger 注解
  2. Variable(YAML 模型)

    • 位置:common-pipeline-yaml/src/main/kotlin/.../yaml/v3/models/Variable.kt
    • 用途:YAML 流水线定义,对外 API 交互
    • 特点:Jackson 注解,支持 JSON 序列化

转换器(VariableTransfer)

  • 位置:common-pipeline-yaml/src/main/kotlin/.../yaml/transfer/VariableTransfer.kt
  • 职责:实现 BuildFormPropertyVariable 的双向转换
  • 核心方法:
    • makeVariableFromModel() - Model → YAML
    • makeVariableFromYaml() - YAML → Model

改动清单(按顺序执行)

1. 定义 YAML 模型字段

文件src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt

操作:在 VariableVariableProps data class 中添加字段

规范

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
data class Variable(
    // ... 现有字段
    
    @get:JsonProperty("new-field-name")  // YAML 中的字段名(kebab-case)
    @get:Schema(title = "字段中文描述", required = false)
    var newFieldName: Boolean? = null,  // Kotlin 属性名(camelCase)
    
    // 注意事项:
    // 1. 使用可空类型(Type?)避免破坏已有数据
    // 2. @JsonProperty 注解指定 YAML 序列化名称
    // 3. @Schema 提供 API 文档描述
)

实际案例(#12471)

data class Variable(
    // ... 其他字段
    @get:JsonProperty("as-instance-input")
    @get:Schema(title = "默认为实例入参,只有模版才有值,流水线没有值", required = false)
    var asInstanceInput: Boolean? = null,
)

2. 定义内部模型字段

文件src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt

操作:在 BuildFormProperty data class 中添加对应字段

规范

@Schema(title = "构建模型-表单元素属性")
data class BuildFormProperty(
    // ... 现有字段
    
    @get:Schema(
        title = "字段中文描述,说明用途和作用范围",
        required = false
    )
    var newFieldName: Boolean? = null
)

注意事项

  • 字段名与 Variable 中保持一致(camelCase)
  • 使用 Swagger v3 注解(@Schema
  • 参数顺序:建议放在 data class 末尾,避免影响现有构造函数调用

实际案例(#12471)

data class BuildFormProperty(
    // ... 其他字段
    @get:Schema(
        title = "在新增实例、以及新增变量时作用,控制实例化页面「实例入参」按钮, 当required:true时,值才生效",
        required = false
    )
    var asInstanceInput: Boolean? = null
)

3. 实现 Model → YAML 转换逻辑

文件src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt

方法makeVariableFromModel(triggerContainer: TriggerContainer?): Map<String, Variable>?

操作:在构建 Variable 对象时,添加字段映射逻辑

规范

fun makeVariableFromModel(triggerContainer: TriggerContainer?): Map<String, Variable>? {
    // ... 现有代码
    
    triggerContainer?.params?.forEach {
        // ... 处理 props
        
        result[it.id] = Variable(
            value = ...,
            readonly = ...,
            // 新增字段转换逻辑(根据业务规则)
            newFieldName = if (/* 条件判断 */) {
                it.newFieldName.nullIfDefault(defaultValue)
            } else null,
            // ... 其他字段
        )
    }
}

实际案例(#12471)

result[it.id] = Variable(
    value = ...,
    readonly = ...,
    allowModifyAtStartup = if (const != true) it.required.nullIfDefault(true) else null,
    // 新增 asInstanceInput 转换:仅在非常量且 required=true 时转换
    asInstanceInput = if (const != true && it.required) {
        it.asInstanceInput.nullIfDefault(true)
    } else null,
    const = const,
    // ...
)

关键技巧

  • 使用 .nullIfDefault(默认值) 扩展方法省略默认值(减少 YAML 冗余)
  • 根据业务逻辑判断是否需要序列化该字段
  • 常见条件:if (const != true)(非常量时才处理)

4. 实现 YAML → Model 转换逻辑

文件:同上 VariableTransfer.kt

方法makeVariableFromYaml(variables: Map<String, Variable>?): List<BuildFormProperty>

操作:在构建 BuildFormProperty 时,添加字段赋值

规范

fun makeVariableFromYaml(variables: Map<String, Variable>?): List<BuildFormProperty> {
    // ... 现有代码
    
    variables.forEach { (key, variable) ->
        // 计算派生字段(如有必要)
        val allowModifyAtStartup = variable.allowModifyAtStartup ?: true
        
        buildFormProperties.add(
            BuildFormProperty(
                id = key,
                // ... 其他字段
                
                // 新增字段赋值(可能需要计算逻辑)
                newFieldName = if (/* 条件 */) {
                    variable.newFieldName ?: defaultValue
                } else null
            )
        )
    }
}

实际案例(#12471)

val allowModifyAtStartup = variable.allowModifyAtStartup ?: true
buildFormProperties.add(
    BuildFormProperty(
        id = key,
        required = allowModifyAtStartup,
        // ...
        // 仅在 allowModifyAtStartup=true 时赋值,否则为 null
        asInstanceInput = if (allowModifyAtStartup) {
            variable.asInstanceInput ?: true
        } else null
    )
)

注意事项

  • 考虑字段间的依赖关系(如 asInstanceInput 依赖 required
  • 提供合理的默认值(?: defaultValue
  • 保持与前端约定的语义一致

5. 更新 YAML JSON Schema

文件src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json

操作:在 Variable 的 JSON Schema 定义中添加字段

路径定位:搜索 "definitions""Variable" 或相关对象

规范

{
  "definitions": {
    "Variable": {
      "type": "object",
      "properties": {
        "value": { "type": ["string", "number", "boolean", "object"] },
        "readonly": { "type": "boolean" },
        "allow-modify-at-startup": { "type": "boolean" },
        
        "new-field-name": {
          "type": "boolean",
          "description": "字段描述"
        }
      }
    }
  }
}

实际案例(#12471)

{
  "allow-modify-at-startup" : {
    "type" : "boolean"
  },
  "as-instance-input" : {
    "type" : "boolean"
  },
  "value-not-empty" : {
    "type" : "boolean"
  }
}

作用

  • 为 IDE 提供 YAML 自动补全
  • 验证 YAML 文件格式正确性
  • 生成 API 文档

6. 传递字段到服务层(可选)

场景:如果需要在业务服务中使用新字段

文件src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/ParamFacadeService.kt

操作:在构建参数方法中传递字段

规范

private fun copyFormProperty(
    property: BuildFormProperty,
    // ... 其他参数
): BuildFormProperty {
    return BuildFormProperty(
        id = property.id,
        required = property.required,
        // ... 其他字段
        newFieldName = property.newFieldName  // 传递新字段
    )
}

实际案例(#12471)

return BuildFormProperty(
    id = property.id,
    // ... 
    displayCondition = property.displayCondition,
    asInstanceInput = property.asInstanceInput  // 新增传递
)

国际化(如需要)

7. 添加多语言支持

文件support-files/i18n/project/message_*.properties

操作:如果字段涉及用户可见文案,需添加翻译

示例

# message_zh_CN.properties
variable.asInstanceInput=默认为实例入参

# message_en_US.properties
variable.asInstanceInput=Default as Instance Input

# message_ja_JP.properties
variable.asInstanceInput=デフォルトでインスタンス入力

测试建议

单元测试覆盖

测试文件位置src/backend/ci/core/common/common-pipeline-yaml/src/test/kotlin/.../VariableTransferTest.kt

测试用例类型

@Test
fun `test makeVariableFromModel with new field`() {
    // 测试 Model → YAML 转换
    val property = BuildFormProperty(
        id = "testVar",
        newFieldName = true,
        // ...
    )
    val result = variableTransfer.makeVariableFromModel(...)
    assertEquals(true, result["testVar"]?.newFieldName)
}

@Test
fun `test makeVariableFromYaml with new field`() {
    // 测试 YAML → Model 转换
    val variable = Variable(
        value = "test",
        newFieldName = false
    )
    val result = variableTransfer.makeVariableFromYaml(mapOf("testVar" to variable))
    assertEquals(false, result[0].newFieldName)
}

@Test
fun `test new field default value`() {
    // 测试默认值处理
    val variable = Variable(value = "test", newFieldName = null)
    val result = variableTransfer.makeVariableFromYaml(mapOf("testVar" to variable))
    assertEquals(expectedDefault, result[0].newFieldName)
}

最佳实践

DO ✅

  1. 字段命名一致性

    • YAML:kebab-case(如 as-instance-input
    • Kotlin:camelCase(如 asInstanceInput
    • 使用 @JsonProperty 映射
  2. 可空类型优先

    var newField: Boolean? = null  // ✅ 推荐
    var newField: Boolean = false  // ❌ 避免(破坏兼容性)
    
  3. 省略默认值

    // 使用 nullIfDefault 减少 YAML 冗余
    newField = variable.newField.nullIfDefault(true)
    
  4. 条件序列化

    // 仅在特定条件下才序列化字段
    asInstanceInput = if (const != true && required) {
        property.asInstanceInput.nullIfDefault(true)
    } else null
    
  5. 注释清晰

    // 仅在非常量且必填时生效
    @get:Schema(title = "默认为实例入参,只有模版才有值,流水线没有值", required = false)
    

DON'T ❌

  1. 直接修改字段顺序

    • 避免修改 data class 已有字段的顺序
    • 新字段追加到末尾
  2. 忽略向后兼容

    // ❌ 错误:强制要求非空
    var newField: Boolean
    
    // ✅ 正确:可空类型
    var newField: Boolean? = null
    
  3. 缺少 Schema 注解

    // ❌ 缺少文档
    var newField: Boolean? = null
    
    // ✅ 完整注解
    @get:Schema(title = "字段说明", required = false)
    var newField: Boolean? = null
    
  4. 忘记更新 JSON Schema

    • 会导致 IDE 自动补全失效
    • YAML 验证可能不准确

常见问题

Q1: 字段应该放在 Variable 还是 VariableProps?

判断标准

  • Variable:核心字段(值、只读、常量等)
  • VariableProps:UI 相关、类型特定属性(标签、选项、描述等)

案例(#12471)

  • asInstanceInput 放在 Variable:因为它是模板实例化的核心逻辑
  • labeldescriptionVariableProps:UI 展示属性

Q2: 如何处理字段间的依赖关系?

场景asInstanceInput 依赖 required 字段

解决方案

// 先计算依赖字段
val allowModifyAtStartup = variable.allowModifyAtStartup ?: true

// 再使用条件赋值
asInstanceInput = if (allowModifyAtStartup) {
    variable.asInstanceInput ?: true
} else null

Q3: 什么时候需要更新 ParamFacadeService?

场景判断

  • ✅ 需要:字段在业务逻辑中使用(如实例化、执行时参数校验)
  • ❌ 不需要:纯粹的存储字段(如 UI 配置)

检查清单

变更完成后,逐项确认:

  • Variable.kt 新增字段(含 @JsonProperty、@Schema)
  • BuildFormProperty.kt 新增字段(含 @Schema)
  • VariableTransfer.makeVariableFromModel() 添加转换逻辑
  • VariableTransfer.makeVariableFromYaml() 添加转换逻辑
  • ci.json JSON Schema 更新
  • ParamFacadeService.kt 字段传递(如需要)
  • 国际化文件添加翻译(如需要)
  • 单元测试覆盖双向转换和默认值场景
  • 代码通过 Detekt 静态检查
  • 提交信息符合规范(feat: 变量支持xxx字段 #issue

参考案例

完整实现:asInstanceInput 字段(#12471)

需求:模板实例化时控制变量是否作为实例入参

改动文件

  1. Variable.kt - 添加 asInstanceInput 字段
  2. BuildFormProperty.kt - 添加 asInstanceInput 字段
  3. VariableTransfer.kt - 实现双向转换(共 2 处)
  4. ci.json - 添加 Schema 定义
  5. ParamFacadeService.kt - 传递字段

核心逻辑

// Model → YAML: 仅在非常量且必填时转换
asInstanceInput = if (const != true && it.required) {
    it.asInstanceInput.nullIfDefault(true)
} else null

// YAML → Model: 依赖 allowModifyAtStartup
asInstanceInput = if (allowModifyAtStartup) {
    variable.asInstanceInput ?: true
} else null

总结

新增流水线变量字段的标准流程:

数据模型定义(2个文件)
    ↓
转换逻辑实现(1个文件,2个方法)
    ↓
Schema 更新(1个文件)
    ↓
服务层传递(按需)
    ↓
国际化支持(按需)
    ↓
测试验证

遵循本指南可确保字段扩展的完整性、一致性和向后兼容性。

相关文件

  • Variable.kt: src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt
  • BuildFormProperty.kt: src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt
  • VariableTransfer.kt: src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt
  • ci.json: src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json