Skip to content

chore(deps): lock file maintenance #671

chore(deps): lock file maintenance

chore(deps): lock file maintenance #671

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
site:
name: Check Site
# Every other job here is GitHub-hosted, and the self-hosted scale sets are
# not available to this repository: pinning this one to them left it queued
# forever, so the gate never ran at all. The prerender needs heap, not a
# bigger machine, and NODE_OPTIONS below gives it that.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '24'
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@ferrlabs'
- name: Install dependencies
env:
NODE_AUTH_TOKEN: ${{ secrets.FERRFLOW_PACKAGES_READ || secrets.GITHUB_TOKEN }}
run: pnpm install --frozen-lockfile
- name: Typecheck
working-directory: site
run: pnpm typecheck
- name: Build
working-directory: site
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build
rust:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: The image build sees every workspace member
run: |
set -euo pipefail
members=$(grep '^members = ' Cargo.toml | sed 's/.*= *\[//; s/\].*//' | tr -d '" ' | tr ',' ' ')
missing=0
for member in $members; do
if ! grep -q "^COPY ${member} " Dockerfile; then
echo "::error::${member} is a workspace member the Dockerfile never copies, so cargo cannot load the workspace"
missing=1
fi
done
echo "checked: ${members}"
exit "$missing"
- run: cargo fmt --check
- run: cargo clippy --all-targets -- -D warnings
- run: cargo test --locked
bucket:
# The stub the unit tests drive agrees with whatever it is sent, so it
# cannot catch what a real implementation refuses. It missed an upload with
# no Content-Length, which every push to a real bucket answered with a 500.
name: Objects in a bucket
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
# Not a service container: MinIO needs a command, and services take an
# image and an entrypoint override at best.
- name: Start MinIO
run: |
set -euo pipefail
docker run -d --name minio -p 9000:9000 -e MINIO_ROOT_USER=lfsxkey -e MINIO_ROOT_PASSWORD=lfsxsecret quay.io/minio/minio:latest server /data
for _ in $(seq 60); do
if curl -fsS http://127.0.0.1:9000/minio/health/live >/dev/null 2>&1; then
docker exec minio mc alias set local http://127.0.0.1:9000 lfsxkey lfsxsecret
docker exec minio mc mb --ignore-existing local/lfsx-test
exit 0
fi
sleep 1
done
docker logs minio
echo "::error::MinIO never came up"
exit 1
- run: cargo test --locked --test bucket
env:
LFSX_TEST_S3_ENDPOINT: http://127.0.0.1:9000
bucket-garage:
# Same suite, second dialect. The startup probes ask the implementation
# what it enforces instead of assuming, and MinIO alone cannot catch a
# probe that trusts something MinIO happens to do. Garage is what the
# homelab deployments this server courts actually run, and its answers
# to conditional writes are the ones verify_locking has to classify.
name: Objects in a Garage bucket
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
# The version is pinned on purpose: the dialect is the thing under test,
# so it must not drift underneath the suite on someone else's schedule.
- name: Start Garage
run: |
set -euo pipefail
cat > garage.toml <<EOF
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "$(openssl rand -hex 32)"
[s3_api]
s3_region = "us-east-1"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"
EOF
# Only the S3 port is mapped. The RPC port stays inside the
# container on purpose: this is a single node talking to itself, and
# every admin call below goes through docker exec.
docker run -d --name garage -p 3900:3900 -v "$PWD/garage.toml:/etc/garage.toml" dxflrs/garage:v1.1.0
for _ in $(seq 60); do
if docker exec garage /garage status >/dev/null 2>&1; then
break
fi
sleep 1
done
docker exec garage /garage status >/dev/null || { docker logs garage; echo "::error::Garage never came up"; exit 1; }
NODE=$(docker exec garage /garage node id -q | cut -d@ -f1)
docker exec garage /garage layout assign -z ci -c 1G "$NODE"
docker exec garage /garage layout apply --version 1
docker exec garage /garage key create ci-key > key.txt
docker exec garage /garage bucket create lfsx-test
docker exec garage /garage bucket allow --read --write lfsx-test --key ci-key
{
awk '/Key ID:/{print "LFSX_TEST_S3_ACCESS_KEY=" $3}' key.txt
awk '/Secret key:/{print "LFSX_TEST_S3_SECRET_KEY=" $3}' key.txt
} >> "$GITHUB_ENV"
- run: cargo test --locked --test bucket
env:
LFSX_TEST_S3_ENDPOINT: http://127.0.0.1:3900
e2e:
# The audience is a studio: artists on Windows, programmers on macOS, CI on
# Linux, and whatever git-lfs their tooling happened to bundle. One job on
# one runner proved none of that.
name: git-lfs ${{ matrix.lfs && matrix.lfs || 'bundled' }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: windows-latest
- os: macos-latest
# The floor this project claims to support. Locking settled in 3.0, and
# the clients that ship inside Git for Windows, Sourcetree or Unity are
# rarely the newest.
- os: ubuntu-latest
lfs: v3.0.2
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-e2e-${{ hashFiles('Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-e2e-
- name: Pin git-lfs ${{ matrix.lfs }}
if: matrix.lfs
run: |
set -euo pipefail
version="${{ matrix.lfs }}"
curl -fsSL "https://github.com/git-lfs/git-lfs/releases/download/${version}/git-lfs-linux-amd64-${version}.tar.gz" | tar xz -C "$RUNNER_TEMP"
# 3.0 tarballs unpack flat; later ones nest under a directory. Take
# whichever binary is there rather than guessing the shape.
binary=$(find "$RUNNER_TEMP" -name git-lfs -type f | head -n1)
chmod +x "$binary"
mkdir -p "$RUNNER_TEMP/bin"
mv "$binary" "$RUNNER_TEMP/bin/git-lfs"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- run: git lfs version
- run: bash ci/e2e.sh
helm:
name: Helm chart
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5
- run: helm lint chart --set config.publicUrl=https://lfs.example.com
- name: An unpinned public URL leaves the variable out
run: |
set -euo pipefail
helm template lfsx chart > unpinned.yaml
if grep -q 'LFSX_PUBLIC_URL' unpinned.yaml; then
echo "::error::LFSX_PUBLIC_URL was set with nothing to pin it to"
exit 1
fi
echo "no public URL pinned, the server answers on the requested host"
- name: Render the shapes people actually deploy
run: |
set -euo pipefail
helm template lfsx chart --set config.publicUrl=https://lfs.example.com >/dev/null
helm template lfsx chart --set ingress.enabled=true --set ingress.className=nginx --set ingress.host=lfs.example.com > nginx.yaml
grep -q 'proxy-body-size: "0"' nginx.yaml || { echo "::error::nginx ingress lost its body size annotation, uploads over 1MB would fail"; exit 1; }
grep -q 'https://lfs.example.com' nginx.yaml || { echo "::error::LFSX_PUBLIC_URL was not derived from the ingress host"; exit 1; }
helm template lfsx chart --set config.publicUrl=http://lfsx.internal --set auth.mode=disabled --set persistence.existingClaim=objects >/dev/null
helm template lfsx chart --set config.publicUrl=https://lfs.example.com --set auth.mode=gitea --set auth.giteaApiUrl=https://git.example.com/api/v1 > gitea.yaml
grep -q 'LFSX_GITEA_API_URL' gitea.yaml || { echo "::error::auth.mode=gitea did not reach the server"; exit 1; }
grep -q 'https://git.example.com/api/v1' gitea.yaml || { echo "::error::auth.giteaApiUrl was not rendered"; exit 1; }
if grep -q 'LFSX_GITHUB_API_URL' gitea.yaml; then
echo "::error::a gitea deployment fell into the GitHub branch"; exit 1
fi
- name: A bucket deployment carries its credentials by reference
run: |
set -euo pipefail
helm template lfsx chart --set config.publicUrl=https://lfs.example.com --set storage.type=s3 --set storage.s3.endpoint=https://s3.example.com --set storage.s3.bucket=assets --set storage.s3.existingSecret=lfsx-s3 > bucket.yaml
grep -A1 'name: LFSX_STORAGE$' bucket.yaml | grep -q 's3' || { echo "::error::storage.type=s3 did not reach the server"; exit 1; }
grep -q 'secretKeyRef' bucket.yaml || { echo "::error::the bucket keys were not taken from a Secret"; exit 1; }
grep -q 'type: RollingUpdate' bucket.yaml || { echo "::error::a bucket deployment cannot roll, so it has downtime it does not need"; exit 1; }
if grep -A1 'name: LFSX_S3_ACCESS_KEY' bucket.yaml | grep -q 'value:'; then
echo "::error::a key was rendered as a literal value"; exit 1
fi
- name: A volume deployment stays one pod, and says so rather than breaking
run: |
set -euo pipefail
helm template lfsx chart --set config.publicUrl=https://lfs.example.com > one.yaml
grep -q 'replicas: 1' one.yaml || { echo "::error::the default is no longer a single replica"; exit 1; }
grep -q 'type: Recreate' one.yaml || { echo "::error::a volume deployment must not roll: two pods would share one claim"; exit 1; }
refuses() {
if helm template lfsx chart "$@" >/dev/null 2>&1; then
echo "::error::the chart rendered a topology it cannot serve: $*"
exit 1
fi
}
refuses --set config.publicUrl=https://lfs.example.com --set replicaCount=2
refuses --set config.publicUrl=https://lfs.example.com --set storage.type=s3 --set storage.s3.endpoint=https://s3.example.com --set storage.s3.bucket=assets --set storage.s3.existingSecret=lfsx-s3 --set replicaCount=2
refuses --set config.publicUrl=https://lfs.example.com --set storage.type=s3 --set storage.s3.endpoint=https://s3.example.com --set storage.s3.bucket=assets
refuses --set config.publicUrl=https://lfs.example.com --set storage.type=nfs
refuses --set config.publicUrl=https://lfs.example.com --set auth.mode=gitea
- name: Two pods over one bucket is the shape that carries HA
run: |
set -euo pipefail
helm template lfsx chart --set config.publicUrl=https://lfs.example.com --set storage.type=s3 --set storage.s3.endpoint=https://s3.example.com --set storage.s3.bucket=assets --set storage.s3.existingSecret=lfsx-s3 --set persistence.enabled=false --set replicaCount=2 > ha.yaml
grep -q 'replicas: 2' ha.yaml || { echo "::error::the replica count did not reach the Deployment"; exit 1; }
grep -q 'emptyDir' ha.yaml || { echo "::error::each replica has to stage its own uploads"; exit 1; }
if grep -q 'kind: PersistentVolumeClaim' ha.yaml; then
echo "::error::a ReadWriteOnce claim cannot be shared by two pods"; exit 1
fi