26-pipeline-variable-extension by TencentBlueKing
流水线变量字段扩展指南,涵盖变量字段定义、类型扩展、变量作用域、变量继承、自定义变量处理。当用户扩展流水线变量、添加新变量字段、处理变量作用域或实现变量继承时使用。
DevOps
2.5K Stars
514 Forks
Updated Jan 9, 2026, 09:52 AM
Why Use This
This skill provides specialized capabilities for TencentBlueKing's codebase.
Use Cases
- Developing new features in the TencentBlueKing repository
- Refactoring existing code to follow TencentBlueKing standards
- Understanding and working with TencentBlueKing's codebase structure
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 541 Lines
Total Files 1
Total Size 0 B
License NOASSERTION
---
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`
- 职责:实现 `BuildFormProperty` ↔ `Variable` 的双向转换
- 核心方法:
- `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`
**操作**:在 `Variable` 或 `VariableProps` data class 中添加字段
**规范**:
```kotlin
@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)**:
```kotlin
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 中添加对应字段
**规范**:
```kotlin
@Schema(title = "构建模型-表单元素属性")
data class BuildFormProperty(
// ... 现有字段
@get:Schema(
title = "字段中文描述,说明用途和作用范围",
required = false
)
var newFieldName: Boolean? = null
)
```
**注意事项**:
- 字段名与 Variable 中保持一致(camelCase)
- 使用 Swagger v3 注解(`@Schema`)
- 参数顺序:建议放在 data class 末尾,避免影响现有构造函数调用
**实际案例(#12471)**:
```kotlin
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` 对象时,添加字段映射逻辑
**规范**:
```kotlin
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)**:
```kotlin
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` 时,添加字段赋值
**规范**:
```kotlin
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)**:
```kotlin
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"` 或相关对象
**规范**:
```json
{
"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)**:
```json
{
"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`
**操作**:在构建参数方法中传递字段
**规范**:
```kotlin
private fun copyFormProperty(
property: BuildFormProperty,
// ... 其他参数
): BuildFormProperty {
return BuildFormProperty(
id = property.id,
required = property.required,
// ... 其他字段
newFieldName = property.newFieldName // 传递新字段
)
}
```
**实际案例(#12471)**:
```kotlin
return BuildFormProperty(
id = property.id,
// ...
displayCondition = property.displayCondition,
asInstanceInput = property.asInstanceInput // 新增传递
)
```
---
## 国际化(如需要)
### 7. 添加多语言支持
**文件**:`support-files/i18n/project/message_*.properties`
**操作**:如果字段涉及用户可见文案,需添加翻译
**示例**:
```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`
**测试用例类型**:
```kotlin
@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. **可空类型优先**
```kotlin
var newField: Boolean? = null // ✅ 推荐
var newField: Boolean = false // ❌ 避免(破坏兼容性)
```
3. **省略默认值**
```kotlin
// 使用 nullIfDefault 减少 YAML 冗余
newField = variable.newField.nullIfDefault(true)
```
4. **条件序列化**
```kotlin
// 仅在特定条件下才序列化字段
asInstanceInput = if (const != true && required) {
property.asInstanceInput.nullIfDefault(true)
} else null
```
5. **注释清晰**
```kotlin
// 仅在非常量且必填时生效
@get:Schema(title = "默认为实例入参,只有模版才有值,流水线没有值", required = false)
```
### DON'T ❌
1. **直接修改字段顺序**
- 避免修改 data class 已有字段的顺序
- 新字段追加到末尾
2. **忽略向后兼容**
```kotlin
// ❌ 错误:强制要求非空
var newField: Boolean
// ✅ 正确:可空类型
var newField: Boolean? = null
```
3. **缺少 Schema 注解**
```kotlin
// ❌ 缺少文档
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`:因为它是模板实例化的核心逻辑
- `label`、`description` 在 `VariableProps`:UI 展示属性
### Q2: 如何处理字段间的依赖关系?
**场景**:`asInstanceInput` 依赖 `required` 字段
**解决方案**:
```kotlin
// 先计算依赖字段
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` - 传递字段
**核心逻辑**:
```kotlin
// 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`
Name Size