From 505aeb8e500dbe248cc431f7bd46cfb61f548579 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Mon, 21 Sep 2026 15:48:05 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9C=A8=20Pod=20CI=20=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=20Docker=20=E5=B9=B6=E4=BF=9D=E7=95=99=20fix?= =?UTF-8?q?ture=20=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/verify.yml | 29 +++++++++- docs/database/README.md | 3 +- docs/database/development.md | 6 +- .../postgresql/fixture_integration_test.go | 58 ++++++++++++++++++- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml index fcb53c1..90d8b53 100644 --- a/.gitea/workflows/verify.yml +++ b/.gitea/workflows/verify.yml @@ -45,7 +45,10 @@ jobs: make lint-database-integration database-integration: - runs-on: [self-hosted, vm] + runs-on: [self-hosted, pod] + timeout-minutes: 30 + env: + DOCKER_HOST: unix:///var/run/docker.sock steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -58,5 +61,27 @@ jobs: go-version-file: go.mod cache: true - - name: Test Database credentials with real backends + # 复用 dynamic runner 的 pod-smoke:daemon 与数据只属于本次 Pod job。 + - name: Start job-local Docker + shell: bash + run: | + set -euo pipefail + command -v docker + command -v dockerd + findmnt /var/lib/docker + sudo -n nohup dockerd \ + --host=unix:///var/run/docker.sock \ + --storage-driver=overlay2 \ + >/tmp/ayatori-dockerd.log 2>&1 & + for _ in {1..60}; do + if docker info >/dev/null 2>&1; then + docker version + exit 0 + fi + sleep 1 + done + cat /tmp/ayatori-dockerd.log + exit 1 + + - name: Test Database integration with real backends run: make test-database-integration diff --git a/docs/database/README.md b/docs/database/README.md index f0e14aa..4d47da1 100644 --- a/docs/database/README.md +++ b/docs/database/README.md @@ -53,7 +53,8 @@ controller 接入、Secret watch、finalizer、registry 与真实权限检查仍 运行 `make test-database-integration` 验证真实 API server + 一次性 PostgreSQL;fixture 不接受外部 DSN,镜像固定摘要,使用随机本机回环端口并在退出时删除测试容器。覆盖缺失/错误凭据、RBAC、 namespace 边界、有效值轮换、metadata 无关变化、中途轮换、重建/重试、并发读取、Forget/Close -与 TLS DNS/IP SAN、错误 CA/主机名和禁止明文降级。CI 使用 VM runner 执行,普通 lint 之外还检查 +与 TLS DNS/IP SAN、错误 CA/主机名和禁止明文降级。CI 使用 Pod runner 执行,workflow 显式启动 +本 job 独占的 Docker daemon,随 Pod 销毁;不依赖 VM 或共享宿主 Docker。普通 lint 之外还检查 integration 标签代码。领域单测、真实 API 行为与真实 PostgreSQL 行为分别验收,不以本切片 替代整个 Instance controller 的集成验收。 diff --git a/docs/database/development.md b/docs/database/development.md index edb10db..b79d126 100644 --- a/docs/database/development.md +++ b/docs/database/development.md @@ -21,7 +21,11 @@ TLS 测试在临时目录生成一次性证书与私钥,不使用生产 CA。 当前覆盖固定 namespace 的 Secret 读取与 RBAC、缺失/无效凭据恢复、有效凭据变化后的重连、 无关字段更新不重连、中途轮换时丢弃观察、会话重建、并发读取、本地连接释放和 TLS 验证。 -默认 Pod runner 跑快速测试与 lint,VM runner 跑带 Docker 的 Database 集成测试。 +快速测试、lint 和 Database 集成测试均使用 Pod runner。集成 job 按 dynamic runner 的 +`pod-smoke.yml` 显式启动 Pod 内独占的 Docker daemon(overlay2),预检成功后再运行测试, +不依赖 VM、共享宿主 Docker 或生产数据库。daemon 与数据随 job Pod 销毁。 +daemon 启动失败会打印启动日志;fixture 启动失败会保留退出错误与 stderr,并遮蔽测试密码, +以区分缺少命令、daemon 不可达、权限和镜像拉取失败。 这些测试尚不包含 Instance CRD/controller、Secret watch、status/finalizer 事件链、registry、 权限探测矩阵、ESO 或 Tenant 供应。版本查询成功不意味着 Instance Ready。 diff --git a/internal/database/adapter/postgresql/fixture_integration_test.go b/internal/database/adapter/postgresql/fixture_integration_test.go index 3b18199..c791be6 100644 --- a/internal/database/adapter/postgresql/fixture_integration_test.go +++ b/internal/database/adapter/postgresql/fixture_integration_test.go @@ -20,6 +20,7 @@ package postgresql_test import ( "context" + "errors" "os/exec" "regexp" "strconv" @@ -55,7 +56,7 @@ func postgresFixture(t *testing.T, ctx context.Context) (string, int) { output, err := exec.CommandContext(ctx, "docker", "run", "--rm", "-d", "-p", "127.0.0.1::5432", "-e", "POSTGRES_PASSWORD="+fixturePassword, fixtureImage).Output() if err != nil { - t.Fatal("cannot start isolated PostgreSQL fixture") + t.Fatalf("cannot start isolated PostgreSQL fixture: %s", fixtureCommandError(err)) } id := strings.TrimSpace(string(output)) if !regexp.MustCompile(`^[a-f0-9]{64}$`).MatchString(id) { @@ -70,7 +71,7 @@ func postgresFixture(t *testing.T, ctx context.Context) (string, int) { }) output, err = exec.CommandContext(ctx, "docker", "inspect", "--format", `{{(index (index .NetworkSettings.Ports "5432/tcp") 0).HostPort}}`, id).Output() if err != nil { - t.Fatal("cannot inspect fixture port") + t.Fatalf("cannot inspect fixture port: %s", fixtureCommandError(err)) } port, err := strconv.Atoi(strings.TrimSpace(string(output))) if err != nil { @@ -87,6 +88,59 @@ func postgresFixture(t *testing.T, ctx context.Context) (string, int) { return id, port } +// Output 将 stderr 保存在 ExitError 中;保留诊断,但不打印命令参数和测试密码。 +func fixtureCommandError(err error) string { + detail := err.Error() + if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { + detail += ": " + strings.TrimSpace(string(exitErr.Stderr)) + } + redactor := strings.NewReplacer( + fixturePassword, "[REDACTED]", + rotatedPassword, "[REDACTED]", + ) + return redactor.Replace(detail) +} + +func TestFixtureCommandErrorPreservesDiagnosticsAndRedactsPasswords(t *testing.T) { + tests := []struct { + name string + err error + want string + }{ + { + name: "missing docker executable", + err: &exec.Error{Name: "docker", Err: exec.ErrNotFound}, + want: "executable file not found", + }, + { + name: "daemon failure from stderr", + err: &exec.ExitError{Stderr: []byte("Cannot connect to the Docker daemon")}, + want: "Cannot connect to the Docker daemon", + }, + { + name: "passwords in stderr", + err: &exec.ExitError{Stderr: []byte("failure: " + fixturePassword + " " + rotatedPassword)}, + want: "failure: [REDACTED] [REDACTED]", + }, + { + name: "password in error text", + err: errors.New("failure: " + fixturePassword), + want: "failure: [REDACTED]", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + detail := fixtureCommandError(tt.err) + if !strings.Contains(detail, tt.want) { + t.Fatalf("diagnostic lost expected information: %q", tt.want) + } + if strings.Contains(detail, fixturePassword) || strings.Contains(detail, rotatedPassword) { + t.Fatal("diagnostic exposed a fixture password") + } + }) + } +} + func target(t *testing.T, port int, mode instance.TLSMode) instance.ObservationTarget { t.Helper() id, err := instance.NewIdentity("fixture-uid", "fixture")