ci: add buf lint, breaking and drift checks
- commit
- 620ae64c77d1ebef37a401afc750c253981653af
- author
- brice · · 2026-10-10 06:19 UTC
- parent
- b7fa16d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +57 -3
M .forgejo/workflows/ci.yml +28 -0
| @@ -41,3 +41,31 @@ jobs: | |||
| 41 | 41 | - run: go test -race ./... | |
| 42 | 42 | - name: Architecture | |
| 43 | 43 | run: go tool hexcheck -fail-on warn ./... | |
| 44 | + | ||
| 45 | + | proto: | |
| 46 | + | name: Protobuf and generated code | |
| 47 | + | runs-on: ubuntu-latest | |
| 48 | + | steps: | |
| 49 | + | - uses: actions/checkout@v6 | |
| 50 | + | with: | |
| 51 | + | # buf breaking compares with the base branch. | |
| 52 | + | fetch-depth: 0 | |
| 53 | + | - uses: actions/setup-go@v5 | |
| 54 | + | with: | |
| 55 | + | go-version-file: go.mod | |
| 56 | + | cache: true | |
| 57 | + | - run: go tool buf lint | |
| 58 | + | - name: Breaking changes | |
| 59 | + | if: github.event_name == 'pull_request' | |
| 60 | + | # Skipped by the script while the base branch has no proto/ directory. | |
| 61 | + | run: scripts/buf-breaking.sh "origin/${{ github.base_ref }}" | |
| 62 | + | - name: Generated protobuf code is up to date | |
| 63 | + | run: | | |
| 64 | + | go tool buf generate | |
| 65 | + | git diff --exit-code -- gen/ | |
| 66 | + | test -z "$(git status --porcelain -- gen/)" | |
| 67 | + | - name: Generated mocks are up to date | |
| 68 | + | run: | | |
| 69 | + | go generate ./internal/ports | |
| 70 | + | git diff --exit-code -- internal/ports/mocks/ | |
| 71 | + | test -z "$(git status --porcelain -- internal/ports/mocks/)" | |
| @@ -41,3 +41,31 @@ jobs: | |||||
| 41 | - run: go test -race ./... | 41 | - run: go test -race ./... | ||
| 42 | - name: Architecture | 42 | - name: Architecture | ||
| 43 | run: go tool hexcheck -fail-on warn ./... | 43 | run: go tool hexcheck -fail-on warn ./... | ||
| 44 | + | ||||
| 45 | + | proto: | |||
| 46 | + | name: Protobuf and generated code | |||
| 47 | + | runs-on: ubuntu-latest | |||
| 48 | + | steps: | |||
| 49 | + | - uses: actions/checkout@v6 | |||
| 50 | + | with: | |||
| 51 | + | # buf breaking compares with the base branch. | |||
| 52 | + | fetch-depth: 0 | |||
| 53 | + | - uses: actions/setup-go@v5 | |||
| 54 | + | with: | |||
| 55 | + | go-version-file: go.mod | |||
| 56 | + | cache: true | |||
| 57 | + | - run: go tool buf lint | |||
| 58 | + | - name: Breaking changes | |||
| 59 | + | if: github.event_name == 'pull_request' | |||
| 60 | + | # Skipped by the script while the base branch has no proto/ directory. | |||
| 61 | + | run: scripts/buf-breaking.sh "origin/${{ github.base_ref }}" | |||
| 62 | + | - name: Generated protobuf code is up to date | |||
| 63 | + | run: | | |||
| 64 | + | go tool buf generate | |||
| 65 | + | git diff --exit-code -- gen/ | |||
| 66 | + | test -z "$(git status --porcelain -- gen/)" | |||
| 67 | + | - name: Generated mocks are up to date | |||
| 68 | + | run: | | |||
| 69 | + | go generate ./internal/ports | |||
| 70 | + | git diff --exit-code -- internal/ports/mocks/ | |||
| 71 | + | test -z "$(git status --porcelain -- internal/ports/mocks/)" | |||
M cmd/ermite/main_test.go +9 -1
| @@ -4,6 +4,7 @@ package main | |||
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | 6 | "bytes" | |
| 7 | + | "context" | |
| 7 | 8 | "errors" | |
| 8 | 9 | "io" | |
| 9 | 10 | "net" | |
| @@ -20,6 +21,8 @@ import ( | |||
| 20 | 21 | ||
| 21 | 22 | "github.com/stretchr/testify/assert" | |
| 22 | 23 | "github.com/stretchr/testify/require" | |
| 24 | + | ||
| 25 | + | "bnema.dev/ermite/internal/adapters/gitcli" | |
| 23 | 26 | ) | |
| 24 | 27 | ||
| 25 | 28 | const adminToken = "0123456789abcdef0123456789abcdef-secret" | |
| @@ -178,9 +181,14 @@ func TestServeMissingGitNamesVariable(t *testing.T) { | |||
| 178 | 181 | } | |
| 179 | 182 | ||
| 180 | 183 | func TestServeHealthAndSigterm(t *testing.T) { | |
| 181 | - | if _, err := exec.LookPath("git"); err != nil { | |
| 184 | + | gitBin, err := exec.LookPath("git") | |
| 185 | + | if err != nil { | |
| 182 | 186 | t.Skip("git not found") | |
| 183 | 187 | } | |
| 188 | + | probe := gitcli.New(gitcli.Config{Bin: gitBin, DataDir: t.TempDir(), Path: os.Getenv("PATH")}) | |
| 189 | + | if _, err := probe.CheckVersion(context.Background()); err != nil { | |
| 190 | + | t.Skipf("git is not usable by ermite: %v", err) | |
| 191 | + | } | |
| 184 | 192 | data := t.TempDir() | |
| 185 | 193 | // State from a previous run: run/ is recreated, an orphan repository is | |
| 186 | 194 | // registered, a staging leftover is removed. | |
| @@ -4,6 +4,7 @@ package main | |||||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | "bytes" | 6 | "bytes" | ||
| 7 | + | "context" | |||
| 7 | "errors" | 8 | "errors" | ||
| 8 | "io" | 9 | "io" | ||
| 9 | "net" | 10 | "net" | ||
| @@ -20,6 +21,8 @@ import ( | |||||
| 20 | 21 | ||||
| 21 | "github.com/stretchr/testify/assert" | 22 | "github.com/stretchr/testify/assert" | ||
| 22 | "github.com/stretchr/testify/require" | 23 | "github.com/stretchr/testify/require" | ||
| 24 | + | ||||
| 25 | + | "bnema.dev/ermite/internal/adapters/gitcli" | |||
| 23 | ) | 26 | ) | ||
| 24 | 27 | ||||
| 25 | const adminToken = "0123456789abcdef0123456789abcdef-secret" | 28 | const adminToken = "0123456789abcdef0123456789abcdef-secret" | ||
| @@ -178,9 +181,14 @@ func TestServeMissingGitNamesVariable(t *testing.T) { | |||||
| 178 | } | 181 | } | ||
| 179 | 182 | ||||
| 180 | func TestServeHealthAndSigterm(t *testing.T) { | 183 | func TestServeHealthAndSigterm(t *testing.T) { | ||
| 181 | - | if _, err := exec.LookPath("git"); err != nil { | 184 | + | gitBin, err := exec.LookPath("git") |
| 185 | + | if err != nil { | |||
| 182 | t.Skip("git not found") | 186 | t.Skip("git not found") | ||
| 183 | } | 187 | } | ||
| 188 | + | probe := gitcli.New(gitcli.Config{Bin: gitBin, DataDir: t.TempDir(), Path: os.Getenv("PATH")}) | |||
| 189 | + | if _, err := probe.CheckVersion(context.Background()); err != nil { | |||
| 190 | + | t.Skipf("git is not usable by ermite: %v", err) | |||
| 191 | + | } | |||
| 184 | data := t.TempDir() | 192 | data := t.TempDir() | ||
| 185 | // State from a previous run: run/ is recreated, an orphan repository is | 193 | // State from a previous run: run/ is recreated, an orphan repository is | ||
| 186 | // registered, a staging leftover is removed. | 194 | // registered, a staging leftover is removed. | ||
M go.mod +1 -1
| @@ -12,6 +12,7 @@ tool ( | |||
| 12 | 12 | require ( | |
| 13 | 13 | bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c3 | |
| 14 | 14 | github.com/bnema/zerowrap v1.4.1 | |
| 15 | + | github.com/spf13/cobra v1.10.2 | |
| 15 | 16 | github.com/stretchr/testify v1.12.1 | |
| 16 | 17 | golang.org/x/sys v0.49.0 | |
| 17 | 18 | google.golang.org/protobuf v1.36.12 | |
| @@ -96,7 +97,6 @@ require ( | |||
| 96 | 97 | github.com/segmentio/asm v1.2.1 // indirect | |
| 97 | 98 | github.com/segmentio/encoding v0.5.4 // indirect | |
| 98 | 99 | github.com/sirupsen/logrus v1.10.2 // indirect | |
| 99 | - | github.com/spf13/cobra v1.10.2 // indirect | |
| 100 | 100 | github.com/spf13/pflag v1.0.10 // indirect | |
| 101 | 101 | github.com/stretchr/objx v0.5.3 // indirect | |
| 102 | 102 | github.com/tetratelabs/wazero v1.12.0 // indirect | |
| @@ -12,6 +12,7 @@ tool ( | |||||
| 12 | require ( | 12 | require ( | ||
| 13 | bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c3 | 13 | bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c3 | ||
| 14 | github.com/bnema/zerowrap v1.4.1 | 14 | github.com/bnema/zerowrap v1.4.1 | ||
| 15 | + | github.com/spf13/cobra v1.10.2 | |||
| 15 | github.com/stretchr/testify v1.12.1 | 16 | github.com/stretchr/testify v1.12.1 | ||
| 16 | golang.org/x/sys v0.49.0 | 17 | golang.org/x/sys v0.49.0 | ||
| 17 | google.golang.org/protobuf v1.36.12 | 18 | google.golang.org/protobuf v1.36.12 | ||
| @@ -96,7 +97,6 @@ require ( | |||||
| 96 | github.com/segmentio/asm v1.2.1 // indirect | 97 | github.com/segmentio/asm v1.2.1 // indirect | ||
| 97 | github.com/segmentio/encoding v0.5.4 // indirect | 98 | github.com/segmentio/encoding v0.5.4 // indirect | ||
| 98 | github.com/sirupsen/logrus v1.10.2 // indirect | 99 | github.com/sirupsen/logrus v1.10.2 // indirect | ||
| 99 | - | github.com/spf13/cobra v1.10.2 // indirect | |||
| 100 | github.com/spf13/pflag v1.0.10 // indirect | 100 | github.com/spf13/pflag v1.0.10 // indirect | ||
| 101 | github.com/stretchr/objx v0.5.3 // indirect | 101 | github.com/stretchr/objx v0.5.3 // indirect | ||
| 102 | github.com/tetratelabs/wazero v1.12.0 // indirect | 102 | github.com/tetratelabs/wazero v1.12.0 // indirect | ||
M internal/adapters/httpserver/server.go +0 -1
| @@ -118,4 +118,3 @@ func DirWritable(dir string) Check { | |||
| 118 | 118 | return nil | |
| 119 | 119 | } | |
| 120 | 120 | } | |
| 121 | - | ||
| @@ -118,4 +118,3 @@ func DirWritable(dir string) Check { | |||||
| 118 | return nil | 118 | return nil | ||
| 119 | } | 119 | } | ||
| 120 | } | 120 | } | ||
| 121 | - | ||||
A scripts/buf-breaking.sh +19 -0
| @@ -0,0 +1,19 @@ | |||
| 1 | + | #!/bin/sh | |
| 2 | + | # Check the protobuf schema against the base branch with buf breaking. | |
| 3 | + | # Usage: scripts/buf-breaking.sh [base-ref] (default: origin/main) | |
| 4 | + | # | |
| 5 | + | # The check is skipped while the base has no proto/ directory (the change that | |
| 6 | + | # introduces the schema has nothing to be compared with). | |
| 7 | + | set -eu | |
| 8 | + | ||
| 9 | + | base="${1:-origin/main}" | |
| 10 | + | ||
| 11 | + | if ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then | |
| 12 | + | echo "buf breaking: base $base not found, fetch it first" >&2 | |
| 13 | + | exit 1 | |
| 14 | + | fi | |
| 15 | + | if ! git cat-file -e "$base:proto" 2>/dev/null; then | |
| 16 | + | echo "buf breaking: $base has no proto/ directory, nothing to compare" | |
| 17 | + | exit 0 | |
| 18 | + | fi | |
| 19 | + | GOWORK=off go tool buf breaking --against ".git#ref=$base" | |
| @@ -0,0 +1,19 @@ | |||||
| 1 | + | #!/bin/sh | |||
| 2 | + | # Check the protobuf schema against the base branch with buf breaking. | |||
| 3 | + | # Usage: scripts/buf-breaking.sh [base-ref] (default: origin/main) | |||
| 4 | + | # | |||
| 5 | + | # The check is skipped while the base has no proto/ directory (the change that | |||
| 6 | + | # introduces the schema has nothing to be compared with). | |||
| 7 | + | set -eu | |||
| 8 | + | ||||
| 9 | + | base="${1:-origin/main}" | |||
| 10 | + | ||||
| 11 | + | if ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then | |||
| 12 | + | echo "buf breaking: base $base not found, fetch it first" >&2 | |||
| 13 | + | exit 1 | |||
| 14 | + | fi | |||
| 15 | + | if ! git cat-file -e "$base:proto" 2>/dev/null; then | |||
| 16 | + | echo "buf breaking: $base has no proto/ directory, nothing to compare" | |||
| 17 | + | exit 0 | |||
| 18 | + | fi | |||
| 19 | + | GOWORK=off go tool buf breaking --against ".git#ref=$base" | |||