feat: expose build identity for release verification
This commit is contained in:
10
Dockerfile
10
Dockerfile
@@ -7,6 +7,9 @@ FROM golang:1.27.1-bookworm@sha256:69a7b9788769bec032d238959b61854e9ae87f57be902
|
|||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG VERSION=dev
|
||||||
|
ARG REVISION=unknown
|
||||||
|
ARG BUILD_TIME=unknown
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
ENV GOWORK=off
|
ENV GOWORK=off
|
||||||
|
|
||||||
@@ -14,9 +17,10 @@ COPY go.mod go.sum ./
|
|||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags='-s -w' -o /out/orbit ./cmd/orbit \
|
RUN BUILD_FLAGS="-s -w -X git.yuntuo.hk/k2safe/orbit/internal/buildinfo.Version=${VERSION} -X git.yuntuo.hk/k2safe/orbit/internal/buildinfo.Revision=${REVISION} -X git.yuntuo.hk/k2safe/orbit/internal/buildinfo.BuiltAt=${BUILD_TIME}" \
|
||||||
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags='-s -w' -o /out/orbit-migrate ./cmd/orbit-migrate \
|
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="${BUILD_FLAGS}" -o /out/orbit ./cmd/orbit \
|
||||||
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags='-s -w' -o /out/orbit-admin ./cmd/orbit-admin
|
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="${BUILD_FLAGS}" -o /out/orbit-migrate ./cmd/orbit-migrate \
|
||||||
|
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="${BUILD_FLAGS}" -o /out/orbit-admin ./cmd/orbit-admin
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,17 @@ docker build --platform linux/amd64 -t registry.example/orbit:dev .
|
|||||||
docker image inspect registry.example/orbit:dev --format '{{.Id}}'
|
docker image inspect registry.example/orbit:dev --format '{{.Id}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
生产发布应把标签绑定到不可变提交和镜像摘要,并在目标架构重新构建或执行等价的交叉构建核验。`registry.example` 只是占位符,不是平台依赖。
|
生产发布应把标签绑定到不可变提交和镜像摘要,并在目标架构重新构建或执行等价的交叉构建核验。构建时可注入版本身份:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build --platform linux/amd64 \
|
||||||
|
--build-arg VERSION=1.0.0 \
|
||||||
|
--build-arg REVISION="$(git rev-parse HEAD)" \
|
||||||
|
--build-arg BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||||
|
-t registry.example/orbit:1.0.0 .
|
||||||
|
```
|
||||||
|
|
||||||
|
节点启动后通过 `GET /version` 核对这些值。`registry.example` 只是占位符,不是平台依赖。
|
||||||
|
|
||||||
## 2. 迁移、核验和启动
|
## 2. 迁移、核验和启动
|
||||||
|
|
||||||
@@ -53,6 +63,7 @@ docker run --rm \
|
|||||||
```text
|
```text
|
||||||
GET /health/live 进程存活
|
GET /health/live 进程存活
|
||||||
GET /health/ready MySQL、迁移账本及零外键检查
|
GET /health/ready MySQL、迁移账本及零外键检查
|
||||||
|
GET /version 版本、提交和协议身份
|
||||||
```
|
```
|
||||||
|
|
||||||
`ready` 返回 `503` 时应摘除节点并保留原幂等键;不能把数据库连接失败解释为业务失败。metrics 监听由 `ORBIT_METRICS_ADDR` 单独启用,默认关闭,不应通过公共业务入口暴露。
|
`ready` 返回 `503` 时应摘除节点并保留原幂等键;不能把数据库连接失败解释为业务失败。metrics 监听由 `ORBIT_METRICS_ADDR` 单独启用,默认关闭,不应通过公共业务入口暴露。
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `GET /health/live` | 已实现 | [健康检查](health.md) |
|
| `GET /health/live` | 已实现 | [健康检查](health.md) |
|
||||||
| `GET /health/ready` | 已实现,验证数据库、迁移记录及零外键要求 | [健康检查](health.md) |
|
| `GET /health/ready` | 已实现,验证数据库、迁移记录及零外键要求 | [健康检查](health.md) |
|
||||||
|
| `GET /version` | 已实现,返回版本、提交、构建时间及协议版本,不包含数据库、凭据或租户信息 | [健康检查](health.md) |
|
||||||
| 独立监听的 `GET /metrics` | 已实现基础运行指标,默认关闭 | [运行观测](observability.md) |
|
| 独立监听的 `GET /metrics` | 已实现基础运行指标,默认关闭 | [运行观测](observability.md) |
|
||||||
| `/v1/tasks` 提交、查询、分页与待执行取消 | 已实现,P3扩展业务键与创建时间筛选 | [任务接口](tasks.md) / [查询契约](task-queries.md) |
|
| `/v1/tasks` 提交、查询、分页与待执行取消 | 已实现,P3扩展业务键与创建时间筛选 | [任务接口](tasks.md) / [查询契约](task-queries.md) |
|
||||||
| `GET /v1/tasks/{task_id}/source` | 已实现普通、人工重试、计划固定版本及流程历史激活/轮次来源 | [来源查询](task-source.md) |
|
| `GET /v1/tasks/{task_id}/source` | 已实现普通、人工重试、计划固定版本及流程历史激活/轮次来源 | [来源查询](task-source.md) |
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ curl -i http://127.0.0.1:8080/health/ready
|
|||||||
|
|
||||||
响应不包含 DSN、数据库错误文本或凭据。检查迁移记录不能发现所有人工 DDL 漂移,运行账户权限和迁移流程仍需受控。
|
响应不包含 DSN、数据库错误文本或凭据。检查迁移记录不能发现所有人工 DDL 漂移,运行账户权限和迁移流程仍需受控。
|
||||||
|
|
||||||
|
## GET /version
|
||||||
|
|
||||||
|
返回当前服务的构建和协议身份,不访问数据库,也无需鉴权。该接口适合在滚动发布或故障接管后核对节点是否运行了预期版本。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsS http://127.0.0.1:8080/version
|
||||||
|
```
|
||||||
|
|
||||||
|
响应字段包括 `version`、`revision`、`built_at`、`go_version`、HTTP/Worker/Event 协议版本和 Weave 定义结构版本。默认本地构建使用 `dev`、`unknown`;发布镜像应通过构建参数注入不可变提交和构建时间。响应不包含主机、数据库、凭据或租户信息。
|
||||||
|
|
||||||
## SDK 与运行限制
|
## SDK 与运行限制
|
||||||
|
|
||||||
对应 `Client.Liveness(ctx)` 和 `Client.Readiness(ctx)`,见 [SDK 接入](../sdk/README.md)。HTTP 503 返回 `*orbit.HTTPError`。
|
对应 `Client.Liveness(ctx)` 和 `Client.Readiness(ctx)`,见 [SDK 接入](../sdk/README.md)。HTTP 503 返回 `*orbit.HTTPError`。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 服务端与 SDK 代码来自同一提交 | `git rev-parse HEAD`;发布时记录不可变提交 | 待发布候选冻结 |
|
| 服务端与 SDK 代码来自同一提交 | `git rev-parse HEAD`;发布时记录不可变提交 | 待发布候选冻结 |
|
||||||
| Go 工具链 | Go 1.27.1;Docker `golang:1.27.1`,真实 `go.mod`/`GOWORK=off` | 模块级 race/vet/build、MySQL 存储/进程组、24 个 SDK 示例及两个独立业务模块真实集成已通过;发布环境仍需复核 |
|
| Go 工具链 | Go 1.27.1;Docker `golang:1.27.1`,真实 `go.mod`/`GOWORK=off` | 模块级 race/vet/build、MySQL 存储/进程组、24 个 SDK 示例及两个独立业务模块真实集成已通过;发布环境仍需复核 |
|
||||||
| 容器发布物 | `make image`;[容器发布说明](21-container-deployment.md) | native linux/arm64 与显式 linux/amd64 镜像构建已通过;生产镜像标签、摘要、签名和目标编排环境仍需冻结 |
|
| 容器发布物 | `make image`;[容器发布说明](21-container-deployment.md) | native linux/arm64 与显式 linux/amd64 镜像构建已通过,支持注入版本身份并由 `/version` 核对;生产镜像标签、摘要、签名和目标编排环境仍需冻结 |
|
||||||
| 服务端单元、竞态、vet、build | `make test && make vet && make build` | Go 1.27.1 Docker 等价命令已通过;宿主机 `make` 因未安装 Go 不能作为本机证据 |
|
| 服务端单元、竞态、vet、build | `make test && make vet && make build` | Go 1.27.1 Docker 等价命令已通过;宿主机 `make` 因未安装 Go 不能作为本机证据 |
|
||||||
| 当前协议线一致 | `make protocol-check` | 已通过:HTTP `/v1`、Worker/Event v1、流程 schema 1 |
|
| 当前协议线一致 | `make protocol-check` | 已通过:HTTP `/v1`、Worker/Event v1、流程 schema 1 |
|
||||||
| SDK 独立性和发布门禁 | `make sdk-check` | Go 1.27.1 Docker 下独立 module、示例和业务模块 race/vet/build 已通过 |
|
| SDK 独立性和发布门禁 | `make sdk-check` | Go 1.27.1 Docker 下独立 module、示例和业务模块 race/vet/build 已通过 |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 服务端单元与竞态、vet、构建 | Docker `golang:1.27.1`,仓库声明版本;`GOWORK=off go test -race ./... && go vet ./... && go build ./...` | PASS |
|
| 服务端单元与竞态、vet、构建 | Docker `golang:1.27.1`,仓库声明版本;`GOWORK=off go test -race ./... && go vet ./... && go build ./...` | PASS |
|
||||||
| SDK 与独立示例竞态、vet、构建 | Docker `golang:1.27.1`;SDK 全 module、全部示例、`orbit-load`、`orbit-exec-load`、`orbit-schedule-load`、`orbit-workflow-load`,以及 result-inbox/task-outbox | PASS |
|
| SDK 与独立示例竞态、vet、构建 | Docker `golang:1.27.1`;SDK 全 module、全部示例、`orbit-load`、`orbit-exec-load`、`orbit-schedule-load`、`orbit-workflow-load`,以及 result-inbox/task-outbox | PASS |
|
||||||
| 容器发布物构建 | `make image`;宿主平台 linux/arm64;显式 `docker build --platform linux/amd64`;镜像内 `GOWORK=off` | PASS;native 与 amd64 镜像均构建成功,包含 `orbit`、`orbit-migrate`、`orbit-admin`,运行用户为非 root;无 DSN 时服务按预期拒绝启动 |
|
| 容器发布物构建 | `make image`;宿主平台 linux/arm64;显式 `docker build --platform linux/amd64`;带 `VERSION`、`REVISION`、`BUILD_TIME` 构建参数的版本化镜像;镜像内 `GOWORK=off` | PASS;native 与 amd64 镜像均构建成功,包含 `orbit`、`orbit-migrate`、`orbit-admin`,运行用户为非 root;版本化 amd64 二进制包含注入的构建身份,`GET /version` 契约单测通过;无 DSN 时服务按预期拒绝启动 |
|
||||||
| 当前协议门禁 | `make protocol-check` | PASS,当前 v1 契约同步 |
|
| 当前协议门禁 | `make protocol-check` | PASS,当前 v1 契约同步 |
|
||||||
| 历史 SDK / 服务端任务 API 双向演练 | Go 1.27.1、MySQL 8.4 一次性环境;`make mysql-compatibility`;历史提交 `a50d5fa` | PASS;旧服务端+当前 SDK、当前服务端+旧 SDK 均完成真实任务受理、幂等、查询、分页和取消;不是正式标签或全协议矩阵 |
|
| 历史 SDK / 服务端任务 API 双向演练 | Go 1.27.1、MySQL 8.4 一次性环境;`make mysql-compatibility`;历史提交 `a50d5fa` | PASS;旧服务端+当前 SDK、当前服务端+旧 SDK 均完成真实任务受理、幂等、查询、分页和取消;不是正式标签或全协议矩阵 |
|
||||||
| 外键迁移防回归 | `TestPostRemovalMigrationGuard`;服务端迁移运行时校验 | PASS;000008 以前仅保留兼容清理历史,后续迁移在执行前拒绝引用约束 DDL |
|
| 外键迁移防回归 | `TestPostRemovalMigrationGuard`;服务端迁移运行时校验 | PASS;000008 以前仅保留兼容清理历史,后续迁移在执行前拒绝引用约束 DDL |
|
||||||
|
|||||||
37
internal/buildinfo/buildinfo.go
Normal file
37
internal/buildinfo/buildinfo.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Package buildinfo 提供不包含凭据和业务数据的构建身份信息。
|
||||||
|
package buildinfo
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
// 这些变量由发布构建通过 -ldflags 注入;本地开发保持安全的默认值。
|
||||||
|
var (
|
||||||
|
Version = "dev"
|
||||||
|
Revision = "unknown"
|
||||||
|
BuiltAt = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Info 是服务版本端点返回的固定结构,不包含主机、数据库或租户信息。
|
||||||
|
type Info struct {
|
||||||
|
Version string `json:"version"` // Version 发布版本或本地开发标识。
|
||||||
|
Revision string `json:"revision"` // Revision 不可变源码提交标识。
|
||||||
|
BuiltAt string `json:"built_at"` // BuiltAt 构建时间,由发布流水线提供。
|
||||||
|
GoVersion string `json:"go_version"` // GoVersion 编译器版本。
|
||||||
|
HTTPProtocolVersion string `json:"http_protocol_version"` // HTTPProtocolVersion HTTP接口主版本。
|
||||||
|
WorkerProtocolVersion string `json:"worker_protocol_version"` // WorkerProtocolVersion Worker WebSocket子协议版本。
|
||||||
|
EventProtocolVersion string `json:"event_protocol_version"` // EventProtocolVersion Event WebSocket子协议版本。
|
||||||
|
WorkflowSchemaVersion int `json:"workflow_schema_version"` // WorkflowSchemaVersion Weave定义结构版本。
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current 返回当前进程的静态构建信息。
|
||||||
|
func Current() Info {
|
||||||
|
return Info{
|
||||||
|
Version: Version,
|
||||||
|
Revision: Revision,
|
||||||
|
BuiltAt: BuiltAt,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
HTTPProtocolVersion: "1",
|
||||||
|
WorkerProtocolVersion: "1",
|
||||||
|
EventProtocolVersion: "1",
|
||||||
|
WorkflowSchemaVersion: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.yuntuo.hk/k2safe/orbit/internal/buildinfo"
|
||||||
"git.yuntuo.hk/k2safe/orbit/internal/observability"
|
"git.yuntuo.hk/k2safe/orbit/internal/observability"
|
||||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||||
)
|
)
|
||||||
@@ -32,6 +33,13 @@ func New(addr string, ready Checker, tasks ...*service.Service) *http.Server {
|
|||||||
}
|
}
|
||||||
write(w, http.StatusOK, map[string]string{"status": "ready", "scope": "database_and_schema"})
|
write(w, http.StatusOK, map[string]string{"status": "ready", "scope": "database_and_schema"})
|
||||||
})
|
})
|
||||||
|
mux.HandleFunc("GET /version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(w).Encode(buildinfo.Current())
|
||||||
|
})
|
||||||
if len(tasks) > 0 && tasks[0] != nil {
|
if len(tasks) > 0 && tasks[0] != nil {
|
||||||
registerTasks(mux, tasks[0])
|
registerTasks(mux, tasks[0])
|
||||||
registerHistory(mux, tasks[0])
|
registerHistory(mux, tasks[0])
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ func TestHealthSeparatesLivenessAndReadiness(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVersionIsPublicAndBounded(t *testing.T) {
|
||||||
|
server := New("127.0.0.1:0", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
server.Handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/version", nil))
|
||||||
|
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), `"http_protocol_version":"1"`) || strings.Contains(rr.Body.String(), "ORBIT_MYSQL_DSN") {
|
||||||
|
t.Fatalf("unexpected version response: %d %s", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMetricsListenerIsSeparateAndOptional(t *testing.T) {
|
func TestMetricsListenerIsSeparateAndOptional(t *testing.T) {
|
||||||
metrics := observability.NewRegistry()
|
metrics := observability.NewRegistry()
|
||||||
business := New("", nil)
|
business := New("", nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user