buildkite/go-pipeline
GitHub: buildkite/go-pipeline
Buildkite 官方的 Go 库,用于在代码中解析、构建和修改 CI/CD 流水线配置,支持 YAML/JSON 序列化与字段顺序保留。
Stars: 12 | Forks: 3
# go-pipeline
[](https://buildkite.com/buildkite/go-pipeline)
[](https://pkg.go.dev/github.com/buildkite/go-pipeline)
`go-pipeline` 是一个 Go 库,用于在 golang 中构建和修改 Buildkite pipeline。[Buildkite Agent](https://github.com/buildkite/agent) 在内部使用它在上传前检查和签名 pipeline,但它对于构建生成 pipeline 的工具也非常有用。
## 安装
要安装,请运行
```
go get -u github.com/buildkite/go-pipeline
```
这会将 go-pipeline 添加到你的 go.mod 文件中,并使其在你的项目中可用。
## 用法
### 从 yaml 加载 pipeline
```
const aPipeline = `
env:
MOUNTAIN: cotopaxi
COUNTRY: ecuador
steps:
- command: echo "hello world"
- wait
- command: echo "goodbye world"
`
p, err := pipeline.Parse(strings.NewReader(aPipeline))
if err != nil {
panic(err)
}
pretty.Println(p)
// &pipeline.Pipeline{
// Env: &ordered.Map[string,string]{
// items: {
// {Key:"MOUNTAIN", Value:"cotopaxi", deleted:false},
// {Key:"COUNTRY", Value:"ecuador", deleted:false},
// },
// index: {"MOUNTAIN":0, "COUNTRY":1},
// },
// Steps: {
// &pipeline.CommandStep{
// Command: "echo \"hello world\"",
// Env: {},
// RemainingFields: {},
// },
// &pipeline.WaitStep{
// Scalar: "wait",
// Contents: {},
// },
// &pipeline.CommandStep{
// Command: "echo \"goodbye world\"",
// Env: {},
// RemainingFields: {},
// },
// },
// RemainingFields: {},
// }
```
### 序列化为 YAML 或 JSON
```
aPipeline := `...`
p, err := pipeline.Parse(strings.NewReader(aPipeline))
if err != nil {
// ...
}
//... modify the pipeline
// Marshal to YAML
b, err := yaml.Marshal(p)
if err != nil {
// ...
}
// Marshal to JSON
b, err := json.Marshal(p)
if err != nil {
// ...
}
```
## 注意事项
pipeline 对象模型(`Pipeline`、`Steps`、`Plugin` 等)有以下注意事项:
- 它是不完整的:可能存在 API 接受但未列出的字段。不要将 Pipeline、CommandStep 等视为如何编写 pipeline 的全面参考指南。
- 它会规范化:反序列化接受多种 step 形式,但重新序列化会生成更规范的输出。反序列化/序列化的往返过程可能会产生不同的输出。
- 它是非规范的:使用此对象模型不能保证 pipeline 会被 pipeline 上传 API 接受。
值得注意的是,此模块定义的大多数 struct 仅包含 agent 理解所需的 pipeline(和 step)元素,并且(在撰写本文时)并不全面。在相关的地方——即存在未包含在 struct 中的更多字段的地方——`RemainingFields` 字段用于将剩余字段捕获为 `map[string]any`。这允许在不丢失信息的情况下加载和修改 pipeline,即使 pipeline 包含 agent 尚未理解的字段。
例如,command step:
```
command: echo "hello world"
env:
FOO: bar
BAZ: qux
artifact_paths:
- "logs/**/*"
- "coverage/**/*"
parallelism: 5
```
在 go 中将表示为:
```
&pipeline.CommandStep{
Command: `echo "hello world"`,
Env: ordered.MapFromItems(
ordered.TupleSS("FOO", "bar"),
ordered.TupleSS("BAZ", "qux"),
),
RemainingFields: map[string]any{
"artifact_paths": []string{"logs/**/*", "coverage/**/*"},
"parallelism": 5,
},
}
```
这个 go struct 将被重新序列化为与原始输入等效的 YAML。
## Checkout
`checkout` 块为 pipeline 或 command step 配置 git checkout 行为。它支持 `skip`、`submodules`、`depth`、`lfs`、`ssh_secret`、`commit_verification` 以及嵌套的 `flags` 映射。`skip`、`lfs` 和 `submodules` 是 `*bool`,因此模型保留了 `true`、`false` 和缺失值之间的区别;`depth`(`*int`)和 `ssh_secret`(`*string`)在显式值和缺失值之间保持相同的区别;`flags` 包含每个阶段的 git 覆盖设置。`commit_verification` 是一个 `string`,支持 `warn` 或 `strict` 值。
最简单的情况是让 step 完全退出 checkout:
```
steps:
- command: echo "no git checkout for me"
checkout:
skip: true
```
step 级别的 `skip: false` 会显式覆盖任何原本会跳过 checkout 的 pipeline 级别或 agent 级别默认值;缺失的 `skip` 则继承适用的默认值。往返过程会保留这种区别,因此 `skip: false` 不会折叠为空映射。
`skip` 映射到 agent 上的 `BUILDKITE_SKIP_CHECKOUT`:`true` 会跳过 checkout 阶段,缺失则交由 agent 默认值处理。`submodules` 遵循相同的三态模式,并映射到 `BUILDKITE_GIT_SUBMODULES`:`true` 和 `false` 会显式设置该环境变量,缺失则交由 agent 默认值处理。
`ssh_secret` 包含 Buildkite Secret 的名称或 ID,该 Secret 包含 agent 用于 git checkout 的 SSH 私钥。agent 负责检索和验证;go-pipeline 仅解析并往返传递该值。
```
steps:
- command: make test
checkout:
ssh_secret: deploy-key
```
`commit_verification` 启用验证步骤,确保指定的 commit SHA 确实存在于指定的 branch 上。允许的值为 `warn` 或 `strict`。`warn` 会记录警告但允许 checkout 继续;`strict` 如果确定 branch 上不存在该 SHA,则会因错误而导致 checkout 失败。
```
steps:
- command: build.sh
checkout:
commit_verification: strict
```
`flags` 包含针对 `clone`、`fetch`、`checkout` 和 `clean` 的每阶段 git 调用覆盖设置:
```
steps:
- command: build.sh
checkout:
flags:
clone: "--depth 1"
fetch: "--prune"
checkout: "--force"
clean: "-fdx"
```
每个叶子节点都是 `*string`。省略某个 flag 会保留使用者应用的任何默认值;显式的空字符串(`clone: ""`)会在往返过程中保留,并指示“此阶段没有 flag”。非字符串标量(`clone: 42`、`clone: true`)在解析时会被拒绝,因为该值会作为 flag 文本传递给 git,强制类型转换会悄无声息地产生错误的调用。`flags:` 下的未知键将存入 `RemainingFields`,因此即使 pipeline 使用了此库尚未识别的 flag 名称,它仍然可以正常解析和往返。
pipeline 级别的 `checkout` 为 command step 提供默认值。继承是可选的:使用者将 pipeline 值合并到每个 step 中。合并后,step 的值在每个叶子节点上优先,而 step 未设置的任何内容都从 pipeline 继承,无论是在顶层还是在 `flags` 内部:
```
checkout:
skip: true
steps:
- command: echo "inherits skip: true from the pipeline"
- command: echo "explicit override - checkout runs"
checkout:
skip: false
```
合并后,第二个 step 的 `skip: false`(step 值优先),而第一个 step 的 `skip: true`(继承而来)。
常规顺序是先执行 `Pipeline.Interpolate`,然后在分发给 agent 之前按 step 进行合并。
`checkout:` 和 `flags:` 都必须是映射。非映射形式(标量,包括 `checkout: true` / `checkout: false` 简写,以及序列)在解析时会被拒绝。退出的写法为 `checkout: { skip: true }`。
如果在 checkout 成为已签名字段之前对 pipeline 进行了签名,那么当该 step 现在携带任何非空的 Checkout 数据(例如,一个仅设置了 `submodules` 的 step)时,验证将会失败。在升级到包含 checkout 的验证器时,请对此类 pipeline 重新签名。
`Checkout.Depth` 是一个 `*int`,其原因与 `Skip` 相同,这可以与任何显式值区分开来,因此可以在 step 级别干净地覆盖继承的 pipeline 级别 depth。后端会验证 `depth >= 1`,但此库不会。
```
checkout:
depth: 10
steps:
- command: echo "Shallow depth defaulting to 10 from build level checkout"
- command: echo "Deeper shallow at the step level"
checkout:
depth: 50
```
`lfs` 是一个 `*bool`,遵循与 `skip` 和 `submodules` 相同的三态模式:`true` 和 `false` 会显式设置行为,缺失值则交由 agent 默认值处理。除非 step 设置了自己的 `lfs`,否则它会继承 pipeline 级别的 `lfs`。
```
checkout:
lfs: true
steps:
- command: echo "inherits lfs: true from the pipeline"
- command: echo "explicit override - no LFS for this step"
checkout:
lfs: false
```
## ordered 模块是怎么回事?
在实现 pipeline 模块时,我们遇到了一个问题:在某些情况下,在 buildkite 的 pipeline.yaml 中,map 字段的顺序是有意义的。正因为如此,每当 pipeline 从 YAML 或 JSON 反序列化时,它都需要以一种保留字段顺序的方式进行存储。`ordered` 模块是有序 map 的简单实现。在大多数情况下,当 pipeline 处理用户输入的 map 时,它会在内部将它们存储为 `ordered.Map`。当 pipeline 被重新序列化为 YAML 或 JSON 时,`ordered.Map` 将以正确的顺序进行序列化。
## 贡献
我们始终欢迎贡献、错误修复、问题和 PR!有关更多详细信息,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
标签:Buildkite, EVTX分析, Go, Homebrew安装, Ruby工具, SOC Prime, 开发工具, 日志审计, 流水线