feat: add release engineering pipeline

This commit is contained in:
2026-08-30 09:56:40 +08:00
parent 5135455084
commit b94e46d966
20 changed files with 526 additions and 81 deletions

View File

@@ -28,21 +28,23 @@ jobs:
TEST_DATABASE_URL: postgres://configcenter:configcenter@127.0.0.1:5432/configcenter_test?sslmode=disable
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
@@ -57,6 +59,14 @@ jobs:
- name: Verify generated protobuf code
run: make proto-check
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
- name: Go vulnerability check
run: go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...
- name: Go tests
run: GOCACHE=/tmp/configcenter-go-cache go test ./...
@@ -68,3 +78,18 @@ jobs:
- name: Web build
run: npm --prefix web ci && npm --prefix web run build
- name: Validate YAML, Compose and migrations
env:
MIGRATION_BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before }}
run: make validate-manifests
- name: Dependency vulnerability scan
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: fs
scan-ref: .
scanners: vuln
severity: HIGH,CRITICAL
ignore-unfixed: false
exit-code: 1

178
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,178 @@
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: configcenter_test
POSTGRES_USER: configcenter
POSTGRES_PASSWORD: configcenter
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U configcenter -d configcenter_test"
--health-interval 5s
--health-timeout 3s
--health-retries 20
env:
TEST_DATABASE_URL: postgres://configcenter:configcenter@127.0.0.1:5432/configcenter_test?sslmode=disable
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
cache-dependency-path: web/package-lock.json
- name: Set up protoc
uses: arduino/setup-protoc@v3
with:
version: "25.1"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify release tag and changelog
run: sh scripts/check-release.sh "${GITHUB_REF_NAME}"
- name: Verify repository
run: make verify
images:
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
actions: read
strategy:
fail-fast: false
matrix:
include:
- component: server
dockerfile: Dockerfile
- component: web
dockerfile: web/Dockerfile
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Prepare image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}-${{ matrix.component }}" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Capture build timestamp
id: buildmeta
shell: bash
run: echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT"
- name: Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ steps.image.outputs.name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
- name: Build and push multi-architecture image
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
provenance: mode=max
sbom: true
build-args: |
VERSION=${{ github.ref_name }}
VCS_REF=${{ github.sha }}
BUILD_DATE=${{ steps.buildmeta.outputs.date }}
cache-from: type=gha,scope=${{ matrix.component }}
cache-to: type=gha,mode=max,scope=${{ matrix.component }}
- name: Scan image for HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: image
image-ref: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
scanners: vuln
severity: HIGH,CRITICAL
ignore-unfixed: false
exit-code: 1
- name: Generate SPDX SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
format: spdx-json
artifact-name: configcenter-${{ matrix.component }}-${{ github.ref_name }}.spdx.json
output-file: configcenter-${{ matrix.component }}-${{ github.ref_name }}.spdx.json
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
upload-release-assets: false
- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0
- name: Sign image digest with GitHub OIDC
env:
IMAGE: ${{ steps.image.outputs.name }}
DIGEST: ${{ steps.build.outputs.digest }}
run: cosign sign --yes "${IMAGE}@${DIGEST}"
- name: Verify keyless signature
env:
IMAGE: ${{ steps.image.outputs.name }}
DIGEST: ${{ steps.build.outputs.digest }}
REPOSITORY: ${{ github.repository }}
run: >-
cosign verify
--certificate-identity-regexp="https://github.com/${REPOSITORY}/.github/workflows/release.yml@refs/tags/v.*"
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
"${IMAGE}@${DIGEST}"

18
.golangci.yml Normal file
View File

@@ -0,0 +1,18 @@
version: "2"
run:
timeout: 5m
linters:
default: standard
enable:
- bodyclose
- errorlint
- gosec
- misspell
- nilerr
- noctx
issues:
max-issues-per-linter: 0
max-same-issues: 0

8
.yamllint.yml Normal file
View File

@@ -0,0 +1,8 @@
extends: default
rules:
braces: disable
document-start: disable
line-length: disable
truthy:
allowed-values: ["true", "false", "on"]

16
CHANGELOG.md Normal file
View File

@@ -0,0 +1,16 @@
# Changelog
All notable changes to Config Center are recorded in this file. Releases follow Semantic Versioning.
## [Unreleased]
### Added
- Formal gRPC read/watch and administrative publish/rollback services with Go and Python SDK transports.
- Audit console, user lifecycle management, RBAC enforcement, and immediate JWT invalidation through `token_version`.
- Release engineering checks for linting, vulnerability scanning, manifest validation, SBOM generation, and keyless image signing.
### Changed
- Runtime watch cursors now use etcd MVCC revisions with compaction-aware full synchronization.
- Redis was removed from the deployment architecture because it had no active runtime responsibility.

View File

@@ -1,13 +1,25 @@
FROM golang:1.24-alpine AS build
FROM golang:1.25-alpine AS build
WORKDIR /src
ARG VERSION=dev
ARG VCS_REF=unknown
ARG BUILD_DATE=unknown
COPY go.mod go.sum ./
RUN go mod download
COPY cmd ./cmd
COPY internal ./internal
COPY pkg ./pkg
RUN CGO_ENABLED=0 go build -buildvcs=false -trimpath -ldflags="-s -w" -o /out/configcenter ./cmd/server
RUN CGO_ENABLED=0 go build -buildvcs=false -trimpath \
-ldflags="-s -w -X main.version=${VERSION} -X main.commit=${VCS_REF} -X main.buildDate=${BUILD_DATE}" \
-o /out/configcenter ./cmd/server
FROM alpine:3.21
ARG VERSION=dev
ARG VCS_REF=unknown
ARG BUILD_DATE=unknown
LABEL org.opencontainers.image.title="Config Center Server" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${VCS_REF}" \
org.opencontainers.image.created="${BUILD_DATE}"
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 configcenter
USER configcenter
COPY --from=build /out/configcenter /usr/local/bin/configcenter

View File

@@ -1,12 +1,17 @@
.PHONY: dev test build proto proto-go proto-python proto-tools proto-check web-install web-build compose-up compose-down integration-smoke loadtest
.PHONY: dev test race build lint lint-tools vulncheck proto proto-go proto-python proto-tools proto-check yaml-tools validate-yaml validate-manifests python-check web-install web-build verify compose-up compose-down integration-smoke loadtest
PROTOC_GEN_GO_VERSION := v1.36.5
PROTOC_GEN_GO_GRPC_VERSION := v1.5.1
GRPCIO_TOOLS_VERSION := 1.71.0
GOLANGCI_LINT_VERSION := v2.12.2
GOVULNCHECK_VERSION := v1.7.0
YAMLLINT_VERSION := 1.37.1
PROTO_FILE := api/proto/configcenter/v1/config.proto
PROTO_GO_FILES := pkg/proto/v1/config.pb.go pkg/proto/v1/config_grpc.pb.go
PROTO_PY_FILES := sdk/python/configcenter/v1/config_pb2.py sdk/python/configcenter/v1/config_pb2_grpc.py
PROTO_TOOLS_STAMP := .tools/.proto-tools-$(PROTOC_GEN_GO_VERSION)-$(PROTOC_GEN_GO_GRPC_VERSION)-$(GRPCIO_TOOLS_VERSION)
LINT_TOOLS_STAMP := .tools/.lint-tools-$(GOLANGCI_LINT_VERSION)-$(GOVULNCHECK_VERSION)
YAML_TOOLS_STAMP := .tools/.yaml-tools-$(YAMLLINT_VERSION)
dev:
go run ./cmd/server
@@ -14,10 +19,27 @@ dev:
test:
GOCACHE=/tmp/configcenter-go-cache go test ./...
race:
GOCACHE=/tmp/configcenter-go-cache go test -race ./...
build:
mkdir -p bin
GOCACHE=/tmp/configcenter-go-cache go build -buildvcs=false -trimpath -o bin/configcenter ./cmd/server
lint-tools: $(LINT_TOOLS_STAMP)
$(LINT_TOOLS_STAMP):
mkdir -p .tools/bin
GOBIN=$(CURDIR)/.tools/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
GOBIN=$(CURDIR)/.tools/bin go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION)
touch $(LINT_TOOLS_STAMP)
lint: lint-tools
$(CURDIR)/.tools/bin/golangci-lint run ./...
vulncheck: lint-tools
$(CURDIR)/.tools/bin/govulncheck ./...
proto-tools: $(PROTO_TOOLS_STAMP)
$(PROTO_TOOLS_STAMP):
@@ -44,12 +66,32 @@ proto: proto-go proto-python
proto-check: proto
git diff --exit-code -- $(PROTO_FILE) $(PROTO_GO_FILES) $(PROTO_PY_FILES) sdk/python/configcenter/v1/__init__.py
web-install:
npm --prefix web install
yaml-tools: $(YAML_TOOLS_STAMP)
web-build:
$(YAML_TOOLS_STAMP):
rm -rf .tools/yamllint
mkdir -p .tools/yamllint
python3 -m pip install --disable-pip-version-check --target .tools/yamllint yamllint==$(YAMLLINT_VERSION)
touch $(YAML_TOOLS_STAMP)
validate-yaml: yaml-tools
PYTHONPATH="$(CURDIR)/.tools/yamllint" python3 -m yamllint -c .yamllint.yml compose.yaml deploy .github/workflows
validate-manifests: validate-yaml
docker compose config --quiet
sh scripts/check-migrations.sh
python-check: proto-tools
PYTHONPATH="$(CURDIR)/.tools/python:$(CURDIR)/sdk/python" python3 -m compileall -q sdk/python/configcenter
web-install:
npm --prefix web ci
web-build: web-install
npm --prefix web run build
verify: proto-check test race lint vulncheck python-check web-build validate-manifests
compose-up:
docker compose up --build

View File

@@ -201,16 +201,14 @@ client.start_background_watch("application")
## 开发与验证
```bash
make test
make proto-check
go test -race ./...
make verify
make build
make web-build
docker compose config
make integration-smoke
make loadtest
```
`make verify` 会执行 protobuf 生成代码漂移检查、Go 单元/集成测试与 race、`golangci-lint``govulncheck`、Python SDK 编译检查、Web Console 生产构建、YAML/Compose 校验和数据库 migration 规则检查。CI 另外使用 Trivy 对仓库依赖进行 HIGH/CRITICAL 漏洞阻断。
核心端到端测试覆盖CRUD → 发布事务 → outbox worker → 运行时读取 → 灰度覆盖与指标;单元/HTTP 集成测试还覆盖 JWT 过期与篡改、应用级 RBAC、多版本回滚和 SDK 鉴权/灰度参数。
PostgreSQL 用户生命周期与审计集成测试在设置 `TEST_DATABASE_URL` 时执行CI 会启动 PostgreSQL 16 service 自动运行该测试,覆盖 migration、用户禁用/密码重置/角色变化的 token version 轮换、最后管理员保护和审计游标分页。
@@ -223,6 +221,20 @@ go run ./cmd/loadtest \
-token="$CONFIGCENTER_TOKEN" -concurrency=64 -duration=60s -max-p95=200ms
```
## 发布
正式版本使用 `vMAJOR.MINOR.PATCH` Git tag。创建 tag 前必须把 `CHANGELOG.md` 中对应内容整理到 `## [MAJOR.MINOR.PATCH] - YYYY-MM-DD`,否则 Release workflow 会拒绝发布。完整规则见 `docs/release-policy.md`
tag 发布流程会构建并推送 server/web 的 `linux/amd64``linux/arm64` 镜像到 GHCR对最终镜像执行 HIGH/CRITICAL 漏洞扫描,生成 SPDX JSON SBOM并通过 GitHub OIDC 使用 Cosign 对不可变 digest 做 keyless 签名和即时验签。生产部署应引用已经过预生产验证的镜像 digest不使用可变 tag。
服务二进制支持输出构建追溯信息:
```bash
configcenter -version
```
版本号、Git commit 和构建时间同时写入 server/web OCI image labels。
## 生产部署
`deploy/kubernetes/configcenter.yaml` 提供三副本无状态 Config Server、三节点 etcd、跨节点调度、PDB、HPA、探针、资源限制和 etcd mTLS。`operations.yaml` 额外提供 etcd 访问网络策略、每 6 小时快照、每周 defrag 和每日 PostgreSQL 备份。部署前替换镜像与 Origin并创建密钥

View File

@@ -48,12 +48,13 @@
## P3发布工程化
- [ ] CI 执行 Go 单元测试与 race、golangci-lint、前端构建、YAML/Compose 校验和生成代码漂移检查。
- [ ] 构建并签名多架构镜像,生成 SBOM执行依赖和镜像漏洞扫描
- [ ] 建立版本号、变更日志、数据库 migration 发布规则及回滚准入检查。
- [x] CI 执行 Go 单元测试与 race、golangci-lint、`govulncheck`前端构建、YAML/Compose 校验、migration 规则检查和生成代码漂移检查。
- [x] 增加 tag Release workflow构建并推送 server/web 的 `linux/amd64``linux/arm64` 镜像,生成 SPDX SBOM执行依赖/最终镜像 HIGH/CRITICAL 漏洞阻断,并使用 GitHub OIDC + Cosign 对镜像 digest 做 keyless 签名和验签
- [x] 建立 SemVer tag、`CHANGELOG.md`、构建版本追溯、数据库 migration append-only/expand-migrate-contract 规则及应用回滚准入检查,见 `docs/release-policy.md`
- [ ] 在首个正式 `vMAJOR.MINOR.PATCH` tag 上验证 GHCR push、SBOM artifact、Cosign OIDC 签名/验签的真实 GitHub 执行结果,并记录最终 image digest。
- [ ] 在预生产执行持续压测和故障注入,验证 p95、错误率、Outbox 积压和 Watch 重连告警。
完成标准:主分支 CI 全部通过;发布产物可追溯、可验证、可回滚;预生产持续压测故障注入达到既定 SLO;发布前无未处理的高危依赖或镜像漏洞
仓库侧发布工程已完成;本地 server/web 最终镜像 HIGH/CRITICAL 扫描均为 0。完成标准仍要求首个真实 tag 的远端制品链验证,以及预生产持续压测/故障注入达到既定 SLO。
## 推荐实施顺序
@@ -61,5 +62,5 @@
2. ~~gRPC 代码生成、服务端和 SDK transport。~~ 已完成。
3. ~~审计控制台与用户生命周期,并明确 JWT 即时失效策略。~~ 已完成。
4. ~~Redis ADR 和清理。~~ 已完成,结论为移除 Redis。
5. CI/CD、制品签名、SBOM、漏洞扫描和发布规则。
5. ~~CI/CD、制品签名、SBOM、漏洞扫描和发布规则。~~ 仓库实现完成,待首个真实 tag 验证远端制品链。
6. 预生产 mTLS、备份恢复、故障演练与生产准入验收。

View File

@@ -33,11 +33,23 @@ import (
"github.com/longpeng/configcenter/internal/watch"
)
var (
version = "dev"
commit = "unknown"
buildDate = "unknown"
)
func main() {
migrateOnly := flag.Bool("migrate-only", false, "apply database migrations and exit")
showVersion := flag.Bool("version", false, "print build version and exit")
flag.Parse()
if *showVersion {
fmt.Printf("configcenter %s commit=%s built=%s\n", version, commit, buildDate)
return
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
logger.Info("starting config center", "version", version, "commit", commit, "buildDate", buildDate)
cfg := config.Load()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
@@ -93,7 +105,7 @@ func main() {
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
grpcListener, err := net.Listen("tcp", cfg.GRPCAddr)
grpcListener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.GRPCAddr)
if err != nil {
logger.Error("listen grpc", "address", cfg.GRPCAddr, "error", err)
os.Exit(1)
@@ -113,7 +125,7 @@ func main() {
case <-ctx.Done():
logger.Info("shutting down config center")
case err := <-serverErrors:
if err != nil && err != http.ErrServerClosed && !errors.Is(err, grpc.ErrServerStopped) {
if err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, grpc.ErrServerStopped) {
logger.Error("server stopped", "error", err)
}
}

49
docs/release-policy.md Normal file
View File

@@ -0,0 +1,49 @@
# Release Policy
## Version source of truth
Config Center uses Semantic Versioning. A production release is created only from an annotated or lightweight Git tag named `vMAJOR.MINOR.PATCH`, for example `v0.2.0`.
Before creating the tag, move the relevant entries from `CHANGELOG.md` `Unreleased` into a dated heading in the form `## [MAJOR.MINOR.PATCH] - YYYY-MM-DD`. The release workflow rejects a tag that has no matching changelog heading.
Server images expose the release version, Git commit, and build timestamp through OCI labels and the `configcenter -version` command. Production deployments must reference immutable image digests rather than mutable tags.
## Required release gates
A release is eligible only when all repository verification succeeds:
1. protobuf generation has no drift;
2. Go unit/integration tests and race tests pass;
3. `golangci-lint` and `govulncheck` pass;
4. Python SDK compile validation and Web Console production build pass;
5. YAML, Docker Compose, and migration policy validation pass;
6. dependency and final container image scans contain no untreated HIGH or CRITICAL vulnerabilities.
The tag-triggered workflow then builds Linux `amd64` and `arm64` server/web images, pushes them to GHCR, generates SPDX JSON SBOMs, and signs each immutable digest with Cosign using GitHub OIDC. The workflow verifies the signature immediately after signing.
## Database migration rules
PostgreSQL migrations under `internal/store/postgres/migrations` are append-only and use contiguous filenames such as `004_add_feature.sql`.
- Never edit, rename, reorder, or delete a migration that may have been applied outside a developer's disposable database.
- Schema changes must be forward-compatible with the previous application release during rolling deployment.
- Destructive changes use expand/migrate/contract: add the new structure first, migrate traffic/data, and remove the old structure only in a later release.
- The application runtime verifies migration checksums and refuses to start if an already-recorded migration has changed.
## Rollback admission
Application rollback is permitted only when the database schema currently deployed remains compatible with the target application version. If a release contains a migration that breaks backward compatibility, rollback is blocked until a corrective forward migration restores compatibility.
Database rollback is not implemented by executing down migrations in production. Recovery from an erroneous destructive migration uses a tested PostgreSQL backup/restore procedure or a new forward migration, depending on incident scope.
## Release procedure
1. Ensure the main branch is green and update `CHANGELOG.md`.
2. Run `make verify` locally where practical.
3. Create and push `vMAJOR.MINOR.PATCH`.
4. Wait for the `Release` workflow to finish successfully.
5. Record the server and web image digests and deploy by digest to staging.
6. Complete staging smoke/load/fault checks and production change approval.
7. Promote the exact tested digests to production; do not rebuild between staging and production.
The remaining staging mTLS, backup/restore, sustained load, and fault-injection gates are tracked separately because they require a real multi-node environment.

22
go.mod
View File

@@ -1,14 +1,14 @@
module github.com/longpeng/configcenter
go 1.24.0
go 1.25.0
require (
github.com/jackc/pgx/v5 v5.7.2
github.com/jackc/pgx/v5 v5.10.0
go.etcd.io/etcd/api/v3 v3.6.7
go.etcd.io/etcd/client/v3 v3.6.7
golang.org/x/crypto v0.42.0
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.5
golang.org/x/crypto v0.53.0
google.golang.org/grpc v1.83.0
google.golang.org/protobuf v1.36.11
)
require (
@@ -23,10 +23,10 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
)

76
go.sum
View File

@@ -1,3 +1,5 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -5,8 +7,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -24,8 +26,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -35,8 +37,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
@@ -45,18 +47,18 @@ go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/Ah
go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -66,30 +68,30 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -98,14 +100,16 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -80,7 +80,7 @@ func TestCRUDPublishAndRuntimeRead(t *testing.T) {
streamCtx, streamCancel := context.WithCancel(ctx)
defer streamCancel()
streamRequest := httptest.NewRequest(http.MethodGet, "/v1/watch?env=PROD&app=orders&namespace=application&instance=orders-7", nil).WithContext(streamCtx)
streamRequest := httptest.NewRequestWithContext(streamCtx, http.MethodGet, "/v1/watch?env=PROD&app=orders&namespace=application&instance=orders-7", nil)
streamRecorder := newStreamResponseRecorder()
streamDone := make(chan struct{})
go func() {
@@ -147,7 +147,7 @@ func TestAuthenticationRBACAndMetrics(t *testing.T) {
collector := metrics.New()
handler := httpapi.New(repository, runtimeStore, watch.New(ctx, runtimeStore), authorizer, collector, nil, logger).Handler()
request := httptest.NewRequest(http.MethodGet, "/v1/applications", nil)
request := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/v1/applications", nil)
doStatus(t, handler, request, http.StatusUnauthorized)
var adminLogin struct {
@@ -190,9 +190,9 @@ func TestAuthenticationRBACAndMetrics(t *testing.T) {
}
requestJSON(t, handler, http.MethodPost, "/v1/namespaces", viewerLogin.Token, map[string]any{"appId": application.ID, "name": "application", "type": "properties"}, http.StatusForbidden, nil)
doStatus(t, handler, httptest.NewRequest(http.MethodGet, "/health/live", nil), http.StatusOK)
doStatus(t, handler, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/health/live", nil), http.StatusOK)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/metrics", nil))
handler.ServeHTTP(recorder, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/metrics", nil))
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `configcenter_http_requests_total{method="GET",route="GET /health/live",status="200"}`) || !strings.Contains(recorder.Body.String(), "configcenter_outbox_pending 0") {
t.Fatalf("unexpected metrics response: status=%d body=%s", recorder.Code, recorder.Body.String())
}
@@ -291,7 +291,7 @@ func post(t *testing.T, handler http.Handler, path string, input any, wantStatus
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, path, bytes.NewReader(payload))
request := httptest.NewRequestWithContext(context.Background(), http.MethodPost, path, bytes.NewReader(payload))
request.Header.Set("Content-Type", "application/json")
request.Header.Set("X-User", "tester")
do(t, handler, request, wantStatus, output)
@@ -299,7 +299,7 @@ func post(t *testing.T, handler http.Handler, path string, input any, wantStatus
func get(t *testing.T, handler http.Handler, path string, wantStatus int, output any) {
t.Helper()
request := httptest.NewRequest(http.MethodGet, path, nil)
request := httptest.NewRequestWithContext(context.Background(), http.MethodGet, path, nil)
do(t, handler, request, wantStatus, output)
}
@@ -337,7 +337,7 @@ func requestJSON(t *testing.T, handler http.Handler, method, path, token string,
}
body = bytes.NewReader(payload)
}
request := httptest.NewRequest(method, path, body)
request := httptest.NewRequestWithContext(context.Background(), method, path, body)
if input != nil {
request.Header.Set("Content-Type", "application/json")
}

View File

@@ -27,7 +27,7 @@ func TestLoginTokenExpiryAndApplicationRoles(t *testing.T) {
if err != nil || !principal.IsAdmin {
t.Fatalf("bootstrap login failed: principal=%#v err=%v", principal, err)
}
request := httptest.NewRequest("GET", "/v1/applications", nil)
request := httptest.NewRequestWithContext(ctx, "GET", "/v1/applications", nil)
request.Header.Set("Authorization", "Bearer "+token)
parsed, err := manager.AuthenticateRequest(request)
if err != nil || parsed.Username != "root" {
@@ -64,7 +64,7 @@ func TestLoginTokenExpiryAndApplicationRoles(t *testing.T) {
if _, err := manager.AuthenticateRequest(request); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("expired token must be rejected: %v", err)
}
tampered := httptest.NewRequest("GET", "/", nil)
tampered := httptest.NewRequestWithContext(ctx, "GET", "/", nil)
tampered.Header.Set("Authorization", "Bearer "+token+"x")
if _, err := manager.AuthenticateRequest(tampered); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("tampered token must be rejected: %v", err)

View File

@@ -1,6 +1,7 @@
package metrics_test
import (
"context"
"net/http"
"net/http/httptest"
"strings"
@@ -17,7 +18,7 @@ func TestHTTPDurationHistogram(t *testing.T) {
collector.ObserveHTTP(http.MethodGet, "GET /v1/config", http.StatusOK, 300*time.Millisecond)
recorder := httptest.NewRecorder()
collector.Handler(memory_store.New(false)).ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/metrics", nil))
collector.Handler(memory_store.New(false)).ServeHTTP(recorder, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/metrics", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected metrics status: %d", recorder.Code)
}

View File

@@ -225,7 +225,7 @@ func (c *Client) WatchAndSync(ctx context.Context, namespace string) error {
for {
err := c.watchOnce(ctx, namespace)
if ctx.Err() != nil {
return nil
return ctx.Err()
}
if err == nil {
backoff = time.Second

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env sh
set -eu
migration_dir="internal/store/postgres/migrations"
previous=0
for path in "$migration_dir"/*.sql; do
name="$(basename "$path")"
case "$name" in
[0-9][0-9][0-9]_[a-z0-9_]*.sql) ;;
*)
printf 'invalid migration filename: %s\n' "$name" >&2
exit 1
;;
esac
number="${name%%_*}"
number="$(printf '%s' "$number" | sed 's/^0*//')"
[ -n "$number" ] || number=0
if [ "$number" -ne $((previous + 1)) ]; then
printf 'migration sequence must be contiguous: expected %03d, found %03d (%s)\n' "$((previous + 1))" "$number" "$name" >&2
exit 1
fi
previous="$number"
done
base_ref="${MIGRATION_BASE_REF:-}"
if [ -n "$base_ref" ] && git cat-file -e "$base_ref^{commit}" 2>/dev/null; then
changed="$(git diff --name-status "$base_ref"...HEAD -- "$migration_dir" || true)"
if [ -n "$changed" ]; then
printf '%s\n' "$changed" | while IFS="$(printf '\t')" read -r status path rest; do
case "$status" in
A) ;;
*)
printf 'existing migrations are immutable; only new migration files may be added: %s %s %s\n' "$status" "$path" "$rest" >&2
exit 1
;;
esac
done
fi
fi
printf 'migration policy check passed (%d migrations)\n' "$previous"

17
scripts/check-release.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env sh
set -eu
tag="${1:-}"
if ! printf '%s\n' "$tag" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then
printf 'release tag must use vMAJOR.MINOR.PATCH, got: %s\n' "$tag" >&2
exit 1
fi
version="${tag#v}"
if ! grep -Eq "^## \\[$version\\] - [0-9]{4}-[0-9]{2}-[0-9]{2}$" CHANGELOG.md; then
printf 'CHANGELOG.md must contain a dated release heading: ## [%s] - YYYY-MM-DD\n' "$version" >&2
exit 1
fi
sh scripts/check-migrations.sh
printf 'release policy check passed for %s\n' "$tag"

View File

@@ -6,7 +6,15 @@ COPY ConfigCenter.jsx ./ConfigCenter.jsx
COPY web ./web
RUN cd web && npm run build
FROM nginx:1.27-alpine
FROM nginx:1.31.4-alpine3.24-slim
ARG VERSION=dev
ARG VCS_REF=unknown
ARG BUILD_DATE=unknown
LABEL org.opencontainers.image.title="Config Center Web" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${VCS_REF}" \
org.opencontainers.image.created="${BUILD_DATE}"
RUN apk upgrade --no-cache
COPY web/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /src/web/dist /usr/share/nginx/html
EXPOSE 80