Unverified Commit 1141ebcd authored by Lemon-miaow's avatar Lemon-miaow
Browse files

ci: 加 -race、staticcheck、govulncheck、shellcheck 与真 PostgreSQL 集成测试门禁,release 复用 ci 门禁

parent 82545548
Loading
Loading
Loading
Loading
+69 −1
Changes for .github/workflows/ci.yml: 69 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -14,10 +14,14 @@
# PR is what asks for the answer.
name: ci

#
# release.yml calls this workflow (workflow_call) before it builds anything, so a tag passes
# exactly these gates and there is one list of them.
on:
  push:
    branches: [main]
  pull_request:
  workflow_call:

permissions:
  contents: read
@@ -43,7 +47,61 @@ jobs:
            echo "gofmt needed on:"; echo "$unformatted"; exit 1
          fi
      - run: go vet ./...
      - run: go test ./...
      # -race: felis-api and the operator are mostly goroutines (watchers, the
      # registry pruner, the backup scheduler, the rate limiters).
      - run: go test -race ./...
      # The version is pinned here and bumped by hand; Dependabot does not read `go run`.
      - name: staticcheck
        run: go run honnef.co/go/tools/cmd/[email protected] ./...

  # Separate from the go job so a newly published advisory reads as what it is. govulncheck
  # exits non-zero only for vulnerable code this module can actually reach, standard
  # library included: setup-go installs the newest patch of go.mod's Go line, so a finding
  # there means the Dockerfile's golang digest (which ships the release) needs a bump too.
  vuln:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
        with:
          go-version-file: go.mod

      - run: go run golang.org/x/vuln/cmd/[email protected] ./...

  # The business stores' SQL against a real PostgreSQL (internal/pgint): the unit suites run
  # on fakes, and PGRepo drifted from them three times while those stayed green. 13 is the
  # oldest server a supported distribution installs (EL9), 18 the newest (Arch).
  pgint:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        postgres: ['13', '18']
    services:
      postgres:
        image: postgres:${{ matrix.postgres }}
        env:
          POSTGRES_USER: felis
          POSTGRES_PASSWORD: pgint
          POSTGRES_DB: felis_pgint
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U felis -d felis_pgint"
          --health-interval 2s
          --health-timeout 5s
          --health-retries 30
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
        with:
          go-version-file: go.mod

      - run: go test -race -tags pgint -count=1 ./internal/pgint/
        env:
          FELIS_TEST_PG_URL: postgres://felis:pgint@localhost:5432/felis_pgint?sslmode=disable

  shell:
    runs-on: ubuntu-latest
@@ -66,6 +124,16 @@ jobs:
            esac
          done

      # A pinned release rather than the runner image's copy, so a runner update cannot
      # change what fails. Warnings and errors fail the job; style notes (info) do not.
      - name: shellcheck
        run: |
          curl -fsSL -o shellcheck.tar.xz \
            https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz
          echo "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198  shellcheck.tar.xz" | sha256sum -c
          tar -xJf shellcheck.tar.xz
          ./shellcheck-v0.11.0/shellcheck -S warning $(git ls-files '*.sh')

      - run: sh deploy/bootstrap_test.sh

  panel:
+12 −36
Changes for .github/workflows/release.yml: 12 added lines, 36 removed lines.
Original line number Diff line number Diff line
@@ -22,9 +22,9 @@
# hash is not listed there, BEFORE it runs it. A release without the file installs by source
# build instead.
#
# Two jobs, so the write token never meets the test suite: `build` runs the tests, Gradle and
# the Docker build (each of which executes third-party code) with a read-only token and hands
# the binaries over as a workflow artifact; `publish` holds contents:write and runs only
# The write token never meets the test suite: `gates` (ci.yml) and `build` run the tests,
# Gradle and the Docker build (each of which executes third-party code) with a read-only
# token, and `build` hands the binaries over as a workflow artifact; `publish` holds contents:write and runs only
# pinned actions and gh. Every action is pinned to a commit SHA (the tag in the trailing
# comment is for humans); .github/dependabot.yml proposes the bumps.
name: release
@@ -37,44 +37,20 @@ permissions:
  contents: read

jobs:
  # A tag that ships red is worse than a tag that fails to ship. These are ci.yml's gates,
  # called rather than copied: Go (race, vet, staticcheck), govulncheck, the PostgreSQL
  # contract suite, shellcheck and the bootstrap tests, the panel, and the Java layer the
  # binary EMBEDS (bootstrap_asset.go ships the plugin sources, so a tag whose plugins do
  # not compile turns every install of that release into a failed bootstrap).
  gates:
    uses: ./.github/workflows/ci.yml

  build:
    needs: gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
        with:
          go-version-file: go.mod

      # A tag that ships red is worse than a tag that fails to ship.
      - run: go vet ./...
      - run: go test ./...

      # The same reason, for the Java layer the binary EMBEDS: the release asset is
      # the tree's plugin sources (bootstrap_asset.go), and a tag whose plugins don't
      # compile turns every install of that release into a failed bootstrap. JDK 21
      # gates the install-time plugins + codec/invite tests; JDK 17 gates the loader
      # mods (their vendored wrappers fetch their own Gradle).
      - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
        with:
          distribution: temurin
          java-version: '21'

      - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
        with:
          gradle-version: '8.14'

      - run: bash plugins/test.sh

      - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
        with:
          distribution: temurin
          java-version: '17'

      - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3

      - run: bash plugins/test-mods.sh

      # Both architectures, because bootstrap's default release channel DOWNLOADS these
      # rather than compiling on the target host — an arm64 host with no asset silently
      # falls back to a slow source build. Neither stage is emulated: the Dockerfile pins
+1 −1
Changes for cmd/felis/fetchcontext.go: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -227,7 +227,7 @@ func extractTarGz(r io.Reader, root string) error {
			if err := os.MkdirAll(target, 0o755); err != nil {
				return fmt.Errorf("create %q: %w", name, err)
			}
		case tar.TypeReg, tar.TypeRegA:
		case tar.TypeReg:
			if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
				return fmt.Errorf("create parent of %q: %w", name, err)
			}
+8 −7
Changes for deploy/bootstrap.sh: 8 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@
#                     digests are pinned; a rerun moves an installer-managed JRE to the
#                     pinned build). Another feature version is checked against the
#                     digest Adoptium's API publishes for it.
#   FELIS_GO_VERSION  Go toolchain used to build the nano binary (default: 1.26.4)
#   FELIS_GO_VERSION  Go toolchain used to build the nano binary (default: 1.26.8)
#   FELIS_GO_SHA256   sha256 of that version's linux tarball for this host's architecture.
#                     REQUIRED for a non-default FELIS_GO_VERSION; the default's is pinned.
#   FELIS_K3S_VERSION k3s release a fresh install gets (default: v1.36.4+k3s1). An
@@ -214,9 +214,9 @@ FELIS_LEGACY_FORWARDING_SERVERS="${FELIS_LEGACY_FORWARDING_SERVERS:-legacy18}"
# The Go tarball is unpacked and run as root, so the default version is pinned by the sha256
# go.dev/dl publishes for each architecture install_go_toolchain handles. Move all three
# together; any other FELIS_GO_VERSION has to bring its own FELIS_GO_SHA256.
GO_PINNED_VERSION="1.26.4"
GO_PINNED_SHA256_AMD64="1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f"
GO_PINNED_SHA256_ARM64="ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768"
GO_PINNED_VERSION="1.26.8"
GO_PINNED_SHA256_AMD64="d0f743b33e8d8945e6b1f432edd15785c70507121d6e2a723b21285eddf8b57b"
GO_PINNED_SHA256_ARM64="211ffced9dcb9633a55eac6364816ec0ddd951389a740e88fa8b3337971bdda0"
FELIS_GO_VERSION="${FELIS_GO_VERSION:-$GO_PINNED_VERSION}"
FELIS_GO_SHA256="${FELIS_GO_SHA256:-}"
# cloudflared runs as root on the edge, so it gets the same treatment: a pinned release and
@@ -1053,8 +1053,8 @@ install_k3s() {
# registry-mirror restart below: both restart the agent, and a bootstrap that
# proceeds early fails later with a misleading "not found"/timeout instead.
wait_for_node_ready() {
  local i
  for i in $(seq 1 60); do
  local _
  for _ in $(seq 1 60); do
    if kube get nodes 2>/dev/null | grep -q ' Ready '; then
      ok "k3s node Ready"
      return 0
@@ -3731,7 +3731,8 @@ write_nano_config() {
  # own 0700: that directory holds secrets, and install_nano_service reports the lockout
  # rather than this widening it.
  if [ ! -d "$STATE_DIR" ]; then
    mkdir -p -m 0755 "$STATE_DIR"
    mkdir -p "$STATE_DIR"
    chmod 0755 "$STATE_DIR"
  elif [ ! -e "$SECRETS_ENV" ] && [ ! -e "$BOOTSTRAP_DONE" ]; then
    chmod 0755 "$STATE_DIR"
  fi
+28 −6
Changes for deploy/bootstrap_test.sh: 28 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -164,7 +164,7 @@ ivblock="$(awk '/^install_velocity\(\) \{/,/^}/' "$BS")"
run_velocity_choice() { # FELIS_GAME_STACK FELIS_VELOCITY_VERSION lock-version
  FELIS_GAME_STACK="$1" FELIS_VELOCITY_VERSION="$2" VELOCITY_VERSION="$3" VELOCITY_LATEST_MINOR=3.5.1 \
  VELOCITY_JAR_URL=https://fill-data.papermc.io/v1/objects/aaa/velocity-3.5.1-615.jar VELOCITY_JAR_SHA256=aaa \
  FELIS_VELOCITY_FORK_JAR= bash -c '
  FELIS_VELOCITY_FORK_JAR='' bash -c '
    set -Eeuo pipefail
    log() { :; }
    die() { printf "DIE: %s\n" "$*"; exit 1; }
@@ -993,23 +993,30 @@ rm -f "$mfile"
pblock="$(awk '/^push_image_to_registry\(\) \{/,/^}/' "$BS")"
[ -n "$pblock" ] || { echo "FAIL: no push_image_to_registry found in $BS"; exit 1; }

run_push() { # ref [docker-push-exit]
  REF="$1" PUSH_EXIT="${2:-0}" \
pushdir="$(mktemp -d)"
run_push() { # ref [failed-pushes-before-success] [registry-read-only]
  REF="$1" PUSH_FAILS="${2:-0}" READONLY="${3:-0}" COUNT="$pushdir/count" \
  REGISTRY_URL=registry.felis.svc:5000 REGISTRY_PUSH_HOST=127.0.0.1:5000 REGISTRY_DOCKER_CONFIG=/cfg \
  bash -c '
    log() { printf "LOG: %s\n" "$*"; }
    warn() { printf "WARN: %s\n" "$*"; }
    die() { printf "DIE: %s\n" "$*"; exit 1; }
    ok() { :; }
    sleep() { :; }
    systemctl() { :; }
    registry_read_only() { [ "$READONLY" = 1 ]; }
    docker() {
      printf "DOCKER %s\n" "$*"
      case " $* " in
        *" push "*) return "$PUSH_EXIT" ;;
        *" push "*)
          n="$(cat "$COUNT" 2>/dev/null || echo 0)"
          echo $((n + 1)) > "$COUNT"
          [ "$n" -ge "$PUSH_FAILS" ] ;;
      esac
    }
    '"$pblock"'
    push_image_to_registry "$REF"'
  rm -f "$pushdir/count"
}

out="$(run_push registry.felis.svc:5000/felis/felis:demo)"
@@ -1025,6 +1032,16 @@ esac

out="$(run_push registry.felis.svc:5000/felis/felis:demo 1)"
expect "a failed push fails the install loudly" "DIE: could not mirror" "$out"
out="$(run_push registry.felis.svc:5000/felis/felis:demo 2 1)"
expect "a push refused during a GC window is retried" \
  "WARN: the registry is read-only for garbage collection; retrying the push of 127.0.0.1:5000/felis/felis:demo in 30s (2/40)" "$out"
case "$out" in
  *DIE:*) echo "FAIL a push that succeeds after the GC window must not fail the install"; fails=$((fails + 1)) ;;
  *) echo "PASS a push that succeeds after the GC window completes" ;;
esac
expect "a GC window that never ends still fails the install" "DIE: could not mirror" \
  "$(run_push registry.felis.svc:5000/felis/felis:demo 99 1)"
rm -rf "$pushdir"

# docker must be started ONCE for the whole batch: a start/stop pair per image trips
# systemd's start rate limit ("start-limit-hit" — observed live; the 4th image was never
@@ -1130,8 +1147,11 @@ expect "the running felis image is pinned" "CTR ctr images label registry.felis.
expect "the registry image is pinned by digest" "CTR ctr images label docker.io/library/registry@${regdigest} io.cri-containerd.pinned=pinned" "$out"
expect "a previous felis tag is unpinned" "CTR ctr images label registry.felis.svc:5000/felis/felis:v1 io.cri-containerd.pinned=" "$out"
expect "the old registry:2 tag is unpinned" "CTR ctr images label docker.io/library/registry:2 io.cri-containerd.pinned=" "$out"
# $'\n' is bash; this file runs under dash in CI.
nl='
'
case "$out" in
  *"registry@${regdigest} io.cri-containerd.pinned="$'\n'*) echo "FAIL: the current registry image must not be unpinned"; fails=$((fails + 1)) ;;
  *"registry@${regdigest} io.cri-containerd.pinned=${nl}"*) echo "FAIL: the current registry image must not be unpinned"; fails=$((fails + 1)) ;;
esac
case "$out" in
  *"limbo:demo io.cri"*) echo "FAIL: only the registry pod's images may be pinned or unpinned"; fails=$((fails + 1)) ;;
@@ -1698,7 +1718,7 @@ printf 'JAVA_VERSION="25"\n' > "$vdir/jre/release"
run_velocity_service() { # is-active(0|1)
  ACTIVE="$1" VELOCITY_SERVICE="$vdir/unit" VELOCITY_DIR="$vdir/v" JRE_DIR="$vdir/jre" \
  VELOCITY_FINGERPRINT="$vdir/fp" VELOCITY_USER=felis-velocity FELIS_GAME_PORT=25565 \
  FELIS_LEGACY_FORWARDING_SERVERS= bash -c '
  FELIS_LEGACY_FORWARDING_SERVERS='' bash -c '
    set -Eeuo pipefail
    ok() { printf "OK: %s\n" "$*"; }
    felis_internal_ip() { printf "10.43.0.9"; }
@@ -1882,6 +1902,8 @@ expect "a v6 node address gets a v6 rule" "tcp dport 5432 ip6 saddr 2001:db8::7
rm -rf "$fwdir"




# ---------------------------------------------------------------------------------------
if [ "$fails" -eq 0 ]; then
  echo "ALL PASS"
Loading