Skip to content

ci: run the Go and Docker heavy jobs on ubuntu-24.04-arm - #1405

Merged
IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ci/arm64-runners
Oct 25, 2026
Merged

IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ci/arm64-runners

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The longest CI jobs are compile-bound: Security Scanning 329s, Unit Tests 324s, E2E 237s, Lint 186s, and the integration shards up to 151s (main run 40186406525). The arm64 runner compiles Go noticeably faster. On main's last three runs:

Step ubuntu-latest (amd64) ubuntu-24.04-arm
Cold Dockerfile build, same Dockerfile, no layer cache 201-207s 124-142s
go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 12-16s under 3s

ubuntu-24.04-arm is free for public repos, has the same 4 vCPUs as ubuntu-latest, and is already used here: the arm64 docker-build leg, and the AWS deploys, which run arm64 on Graviton.

What changed

.github/workflows/ci.yml: runs-on: ubuntu-24.04-arm for:

  • Lint Code
  • Unit Tests
  • Integration Tests (all six shards)
  • Integration test selection self-test
  • Security Scanning
  • E2E Tests

There's also a header comment explaining the split. No steps change.

Left on ubuntu-latest, on purpose:

  • Build Docker Image (amd64): it builds natively for GCP and Azure.
  • Snyk: a Docker container action whose image I haven't checked for arm64. It's continue-on-error and off the critical path.
  • Validate Terraform: it runs init -lockfile=readonly. A lock file missing the arm64 hash could fail there, and the job takes under a minute.
  • The jobs that finish in seconds: parity checks, workflow lint, coverage merge, CI Success. There's nothing to gain.

Compatibility checked

  • Images: every image the moved jobs pull has a linux/arm64 variant at its pinned digest (checked against the Docker Hub registry index). That covers the postgres:16-alpine service, golang:1.26.10-alpine3.24, node:24-alpine, alpine:3.24.1, testcontainers' postgres:16-alpine and testcontainers/ryuk:0.13.0.
  • Tools: the arm runner image ships Docker, Compose v2, Buildx and jq. golangci-lint-action, setup-go, trivy-action (via setup-trivy) and codecov-action (its script detects aarch64) all pick arm64 binaries.
  • Cache keys: every key in this file includes RUNNER_ARCH: setup-go's, the integration GOCACHE/GOMODCACHE keys and the gosec keys. So the first arm run compiles cold, and nothing is restored across architectures.
  • Go code: there are no arch-specific Go files or build constraints in the repo.

Verification

Local, on an amd64 host:

  • GOARCH=arm64 go vet ./..., GOARCH=arm64 go vet -tags integration ./... and (cd tests/e2e && GOARCH=arm64 go vet -tags e2e ./...): all clean.
  • CGO_ENABLED=0 GOARCH=arm64 go test -exec /bin/true ./..., untagged and with -tags integration: every test binary builds (36 and 38 packages).
  • actionlint 1.7.12 (the CI image): clean.
  • pre-commit run --files .github/workflows/ci.yml: all hooks pass, zizmor included. Trivy config, run through aquasec/trivy:0.69.3 because trivy isn't installed locally: rc 0.
  • git-secrets: the scan in an isolated temp repo was clean, with the planted key flagged. scripts/test-git-secrets-allowlist.sh: 137 pass, 0 fail.

Not verified locally: running the tests on arm64, including -race. That needs an arm64 machine or a cross C toolchain, and I have neither. This PR's own CI run is the real test. It will also be a cold-cache run on arm, so the per-job times to compare are its second run, or main's after merge.

Expected effect

Compile-heavy steps should be roughly 30-40% faster, going by the Docker build numbers above. The amd64 Docker leg (262s with its layer cache export) may become the longest job.

Note for cristim

Unit and integration tests now run on arm64 only. That matches AWS (Graviton), while the amd64 binaries for GCP and Azure are still built and smoke-run by the amd64 Docker leg. Go behaves the same on both for this code. One known difference is FMA fusion of float expressions on arm64, which could shift the last bit of a float result. If an exact-float test fails here, that's a real arm64 production difference rather than CI noise. If you'd rather keep amd64 test coverage too, Unit Tests could become a two-arch matrix at no wall-time cost.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Updated the lint, test, security-scan, and end-to-end workflow jobs to run on ARM-based runners. Other runner assignments remain unchanged.

On main's last three runs the same cold Dockerfile build took 124-142s on
the arm64 runner against 201-207s on ubuntu-latest, and `go install
govulncheck` took under 3s against 12-16s. Lint, Unit Tests, Integration
Tests, Security Scanning and E2E are compile-bound and make up the
critical path, so they move to ubuntu-24.04-arm, as does the selection
self-test (go list). arm64 is also what the AWS deploys run.

The amd64 image leg stays native on ubuntu-latest for GCP and Azure.
Snyk (a Docker action), Terraform (provider lock files) and the
seconds-long jobs stay there too. Every cache key already includes the
runner architecture, so nothing restores across architectures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Walkthrough

Six CI jobs now run on ubuntu-24.04-arm instead of ubuntu-latest. The workflow adds comments about runner architecture assignments, timing comparisons, the amd64 image-build exception, and architecture-specific cache keys.

Changes: CI runner updates

Layer / File(s) Summary
ARM runner assignments
.github/workflows/ci.yml
Comments describe runner assignments and architecture-specific details. The lint, unit-test, integration-test, integration-selection self-test, security-scan, and E2E jobs now use ubuntu-24.04-arm.

Priority: Not assessed

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other

Suggested reviewers: cristim

Merge Risk: Minimal · up to f66b3

No actionable merge-blocking risk remains; the ARM runner migration is ready to merge.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD
IAmJSD marked this pull request as ready for review October 25, 2026 11:08
@IAmJSD
IAmJSD merged commit efc1373 into LeanerCloud:main Oct 25, 2026
33 of 34 checks passed
@IAmJSD

IAmJSD commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

CI on f66b316 is all green on arm64 (run 40187507363), including -race unit tests, all six integration shards (testcontainers and the Postgres service) and E2E. The pre-commit run is green too.

This was the first arm run, so every arm cache was empty. For comparison, main's run 40186406525 (x64) had warm caches.

Job / step main, x64 this PR, arm64 (cold caches)
Unit Tests, test step 271s 170s
Unit Tests, job 324s 209s
E2E Tests 237s 175s
govulncheck step 25s 14s
gosec step (full scan on both runs) 239s 242s
golangci-lint step 150s 147s
Integration shards 80-151s (warm) 116-235s (cold)

On integration, the x64 cold run of the same six shards in #1402 took 142-319s, so arm is faster cold-for-cold as well. Warm arm numbers will show up on the next run. The longest jobs are now Security Scanning (gosec, which is no faster on arm and is skipped on a results-cache hit) and the amd64 Docker build, which stays on x64.

@IAmJSD
IAmJSD deleted the ci/arm64-runners branch October 25, 2026 11:13
IAmJSD added a commit that referenced this pull request Oct 25, 2026
…r create/update (#1205)

The view-only/write-capable contradiction check existed only in the
frontend (validateGroupCombination, #1405), so POST /api/users and
PUT /api/users/{id} accepted a contradictory membership from any non-UI
API consumer.

Port the frontend's classification to internal/auth, reading each group's
stored permissions (never its name): view-only means at least one
permission and all actions == "view"; write-capable means any action
other than "view"; empty and missing groups are neutral. Wire it into
CreateUser (via checkCreateUserGroups) and guardGroupChange, after the
existing access guards so an escalation is still reported as a 403. It
returns a new ErrContradictoryGroups sentinel, mapped to 400 by
mapAuthError like ErrNoGroups.

An unchanged membership (groups omitted, or echoed back in any order) is
not re-checked, so users who already hold a contradictory combination can
still be renamed or deactivated; any real change is checked against the
full resulting set, as the frontend does.

Existing tests whose "ordinary group" fixture was view-only next to an
admin group now use a write-capable group, since that combination is
exactly what this change refuses.

Closes #78

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant