ci: validate openapi contracts with redocly
This commit is contained in:
parent
a92dcab333
commit
5e9e8c0d49
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -8,6 +8,16 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
openapi:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- shell: pwsh
|
||||
run: ./scripts/verify-openapi.ps1
|
||||
|
||||
proto:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@ -10,10 +10,15 @@ servers:
|
||||
- url: http://127.0.0.1:8082
|
||||
tags:
|
||||
- name: Status
|
||||
description: 不含代理明细与凭据的控制面聚合状态。
|
||||
- name: Audit
|
||||
description: 权威管理状态变更的游标审计查询。
|
||||
- name: Upstreams
|
||||
description: 上游的启停与受控状态变更。
|
||||
- name: Routing
|
||||
description: Routing 当前上游的受控切换。
|
||||
- name: Configuration
|
||||
description: 经校验后原子发布的配置重载。
|
||||
security:
|
||||
- AdminApiKey: []
|
||||
- BasicAuth: []
|
||||
|
||||
@ -16,7 +16,9 @@ servers:
|
||||
description: Distribution API
|
||||
tags:
|
||||
- name: Distribution
|
||||
description: 一次性独占提取上游代理的受保护接口。
|
||||
- name: Health
|
||||
description: Distribution 监听器的存活与就绪探针。
|
||||
paths:
|
||||
/api/v1/proxies/extract:
|
||||
post:
|
||||
|
||||
5
api/openapi/redocly.yaml
Normal file
5
api/openapi/redocly.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
extends:
|
||||
- minimal
|
||||
|
||||
rules:
|
||||
tag-description: error
|
||||
@ -26,6 +26,31 @@ func TestOpenAPIDocumentsHaveClosedContracts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAPITagsHaveDescriptions(t *testing.T) {
|
||||
for _, name := range []string{"proxy-pool.yaml", "admin.yaml"} {
|
||||
name := name
|
||||
t.Run(name, func(t *testing.T) {
|
||||
root := readOpenAPIRoot(t, name)
|
||||
tags, ok := root["tags"].([]any)
|
||||
if !ok || len(tags) == 0 {
|
||||
t.Fatal("tags must be a non-empty array")
|
||||
}
|
||||
for _, rawTag := range tags {
|
||||
tag, ok := rawTag.(map[string]any)
|
||||
if !ok {
|
||||
t.Errorf("tag has type %T, want object", rawTag)
|
||||
continue
|
||||
}
|
||||
name, _ := tag["name"].(string)
|
||||
description, _ := tag["description"].(string)
|
||||
if strings.TrimSpace(description) == "" {
|
||||
t.Errorf("tag %q has no description", name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validateLocalReferences(t *testing.T, root map[string]any) {
|
||||
t.Helper()
|
||||
walkOpenAPI(root, func(path string, value any) {
|
||||
|
||||
@ -22,12 +22,13 @@ func TestCIValidatesProtocolAndDeploymentAssets(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("ci workflow has no jobs")
|
||||
}
|
||||
for _, name := range []string{"proto", "deployment", "test", "race", "integration"} {
|
||||
for _, name := range []string{"openapi", "proto", "deployment", "test", "race", "integration"} {
|
||||
if _, exists := jobs[name]; !exists {
|
||||
t.Errorf("ci workflow has no %s job", name)
|
||||
}
|
||||
}
|
||||
for _, required := range []string{
|
||||
"./scripts/verify-openapi.ps1",
|
||||
"./scripts/verify-proto.ps1",
|
||||
"docker compose -f deploy/docker-compose.yml config --quiet",
|
||||
"kubectl kustomize deploy/kubernetes/base",
|
||||
|
||||
@ -344,7 +344,8 @@ Supervisor 也改为同时服从静态配置与管理态,消除两条启停消
|
||||
校验本地 `$ref` 闭合、operationId 唯一、响应存在及 security scheme 引用;
|
||||
`scripts/verify-proto.ps1` 已可复现编译包含 imports/source info 的 descriptor,
|
||||
并使用 SHA-256 固定的 `protoc` 35.0 安装器在 CI 完整验证 descriptor 与生成代码漂移;
|
||||
OpenAPI 结构契约由 Go 测试在双平台 CI 执行。
|
||||
OpenAPI 结构契约由 Go 测试在双平台 CI 执行;`scripts/verify-openapi.ps1` 固定
|
||||
`@redocly/cli@2.25.4`,按 OpenAPI 3.1 最小规则集验证两份文档并将 Tag 描述作为错误。
|
||||
|
||||
## Task 13: Deployment and Observability
|
||||
|
||||
|
||||
@ -21,7 +21,8 @@
|
||||
- Admin OpenAPI:状态、按 ID 游标分页的权威审计查询、Upstream 启停、Routing
|
||||
切换和配置重载。
|
||||
- 两份 OpenAPI 已进入 Go/CI 结构门禁,覆盖本地引用闭合、operationId、响应和
|
||||
security scheme;完整标准工具验证仍待补齐。
|
||||
security scheme;固定版本的 Redocly CLI 还会按 OpenAPI 3.1 最小规则集验证两份文档,
|
||||
并把 Tag 描述提升为错误。
|
||||
- Protobuf:Worker 注册、全量/增量 Snapshot、`usable_until`、ACK、运行态/
|
||||
结果上报、Checker 任务与 Observation。
|
||||
|
||||
|
||||
@ -261,3 +261,10 @@ Routing 自上而下匹配,首条命中停止;支持 Gateway 与 Extract 两
|
||||
- Windows 本地 Go 运行环境为 `CGO_ENABLED=0` 且没有 C 编译器;Docker Engine 可用,但
|
||||
`golang:1.26-bookworm` 与 `debian:bookworm-slim` 未缓存,Docker Desktop HTTPS 代理也不可用。
|
||||
因此已完成 Go 全量、Compose 静态和 Kustomize 静态验证,容器端到端验证仍待具备镜像网络的环境。
|
||||
|
||||
## OpenAPI 契约审计(2026-08-07)
|
||||
|
||||
- Go 结构契约继续锁定本地引用、operationId、响应与认证引用;额外以固定
|
||||
`@redocly/cli@2.25.4` 对两份 OpenAPI 3.1 文档执行标准验证,避免只依赖自定义遍历器。
|
||||
- 标准最小规则集中的 Tag 描述已提升为错误;Distribution、Health、Status、Audit、Upstreams、
|
||||
Routing 和 Configuration 标签均有面向 API 使用者的稳定说明。
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
## 2026-08-07
|
||||
|
||||
- OpenAPI 标准验证已固定为 `@redocly/cli@2.25.4`:`verify-openapi.ps1` 对两份 OpenAPI
|
||||
3.1 文档运行最小规则集,并将 Tag 描述提升为错误;本地统一校验与 GitHub Actions 均执行该门禁。
|
||||
- 运行验证环境探测:Windows Go 为 `CGO_ENABLED=0`,系统没有可用 C 编译器;WSL 仅安装
|
||||
`docker-desktop` 发行版。Docker Engine 可用,但 Dockerfile 所需的
|
||||
`golang:1.26-bookworm`、`debian:bookworm-slim` 未缓存,且 Docker Desktop HTTPS 代理不可用;
|
||||
|
||||
21
scripts/verify-openapi.ps1
Normal file
21
scripts/verify-openapi.ps1
Normal file
@ -0,0 +1,21 @@
|
||||
param(
|
||||
[string]$Npx = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repositoryRoot = Split-Path -Parent $PSScriptRoot
|
||||
if ([string]::IsNullOrWhiteSpace($Npx)) {
|
||||
$Npx = (Get-Command npx -ErrorAction Stop).Source
|
||||
}
|
||||
$npxCommand = Get-Command $Npx -ErrorAction Stop
|
||||
$configuration = Join-Path $repositoryRoot "api/openapi/redocly.yaml"
|
||||
$documents = @(
|
||||
(Join-Path $repositoryRoot "api/openapi/proxy-pool.yaml"),
|
||||
(Join-Path $repositoryRoot "api/openapi/admin.yaml")
|
||||
)
|
||||
|
||||
& $npxCommand.Source --yes "@redocly/cli@2.25.4" lint --config $configuration @documents
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "OpenAPI validation failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
@ -23,6 +23,7 @@ Invoke-Step "go vet" { go vet ./... }
|
||||
Invoke-Step "unit tests" { go test -timeout 60s ./... }
|
||||
|
||||
Invoke-Step "protobuf contracts" { & (Join-Path $PSScriptRoot "verify-proto.ps1") }
|
||||
Invoke-Step "OpenAPI contracts" { & (Join-Path $PSScriptRoot "verify-openapi.ps1") }
|
||||
|
||||
if ((go env CGO_ENABLED) -eq "1") {
|
||||
Invoke-Step "race tests" { go test -race -timeout 60s ./internal/... }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user