Monorepo for Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

knotmirror: requeue resync on event mid-sync

Lewis: May this revision serve well! <lewis@tangled.org>

Lewis 8de73ad4 d41fb1ea

+810 -172
+17
.tangled/workflows/test.yml
··· 8 8 nixpkgs: 9 9 - go 10 10 - gcc 11 + - postgresql 12 + - shadow 13 + - util-linux 11 14 12 15 steps: 13 16 - name: patch static dir 14 17 command: | 15 18 mkdir -p appview/pages/static; touch appview/pages/static/x 16 19 20 + - name: start postgres 21 + command: | 22 + set -euo pipefail 23 + mkdir -p /pg/socket 24 + useradd -r -m -d /pg/home pguser 25 + chown -R pguser /pg 26 + uid=$(id -u pguser); gid=$(id -g pguser) 27 + setpriv --reuid "$uid" --regid "$gid" --init-groups -- initdb -D /pg/home/data --auth=trust --username=postgres 28 + setpriv --reuid "$uid" --regid "$gid" --init-groups -- pg_ctl -D /pg/home/data -l /pg/home/pg.log -o "-p 5432 -k /pg/socket -h 127.0.0.1" start 29 + for i in $(seq 1 20); do pg_isready -h 127.0.0.1 -p 5432 -U postgres && break; sleep 0.5; done 30 + pg_isready -h 127.0.0.1 -p 5432 -U postgres 31 + createdb -h 127.0.0.1 -p 5432 -U postgres mirror 32 + 17 33 - name: run linter 18 34 environment: 19 35 CGO_ENABLED: 1 ··· 23 39 - name: run all tests 24 40 environment: 25 41 CGO_ENABLED: 1 42 + TEST_POSTGRES_URL: postgresql://postgres@127.0.0.1:5432/mirror?sslmode=disable 26 43 command: | 27 44 go test -v ./...
+32 -17
go.mod
··· 4 4 5 5 require ( 6 6 github.com/Blank-Xu/sql-adapter v1.1.1 7 + github.com/adrg/frontmatter v0.2.0 7 8 github.com/alecthomas/assert/v2 v2.11.0 8 9 github.com/alecthomas/chroma/v2 v2.23.1 9 10 github.com/avast/retry-go/v4 v4.6.1 10 11 github.com/aws/aws-sdk-go-v2 v1.41.4 12 + github.com/aws/aws-sdk-go-v2/config v1.32.12 11 13 github.com/aws/aws-sdk-go-v2/credentials v1.19.12 12 14 github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 13 15 github.com/blevesearch/bleve/v2 v2.5.3 ··· 28 30 github.com/go-chi/chi/v5 v5.2.0 29 31 github.com/go-enry/go-enry/v2 v2.9.2 30 32 github.com/go-git/go-git/v5 v5.14.0 31 - github.com/goki/freetype v1.0.5 32 33 github.com/google/uuid v1.6.0 33 34 github.com/gorilla/feeds v1.2.0 34 35 github.com/gorilla/sessions v1.4.0 ··· 48 49 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c 49 50 github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef 50 51 github.com/stretchr/testify v1.11.1 52 + github.com/testcontainers/testcontainers-go v0.42.0 53 + github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 51 54 github.com/urfave/cli/v3 v3.6.2 52 55 github.com/whyrusleeping/cbor-gen v0.3.1 53 56 github.com/yuin/goldmark v1.7.13 ··· 58 61 golang.org/x/crypto v0.48.0 59 62 golang.org/x/image v0.31.0 60 63 golang.org/x/net v0.50.0 64 + golang.org/x/sync v0.19.0 61 65 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 62 66 gopkg.in/yaml.v3 v3.0.1 63 67 ) 64 68 65 69 require ( 66 - dario.cat/mergo v1.0.1 // indirect 70 + dario.cat/mergo v1.0.2 // indirect 71 + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 67 72 github.com/BurntSushi/toml v0.3.1 // indirect 68 73 github.com/Microsoft/go-winio v0.6.2 // indirect 69 74 github.com/ProtonMail/go-crypto v1.3.0 // indirect 70 75 github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect 71 - github.com/adrg/frontmatter v0.2.0 // indirect 72 76 github.com/alecthomas/repr v0.5.2 // indirect 73 77 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 74 78 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect 75 - github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect 76 79 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect 77 80 github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect 78 81 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect ··· 120 123 github.com/containerd/errdefs v1.0.0 // indirect 121 124 github.com/containerd/errdefs/pkg v0.3.0 // indirect 122 125 github.com/containerd/log v0.1.0 // indirect 126 + github.com/containerd/platforms v0.2.1 // indirect 127 + github.com/cpuguy83/dockercfg v0.3.2 // indirect 123 128 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 124 129 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 125 130 github.com/distribution/reference v0.6.0 // indirect 126 131 github.com/dlclark/regexp2 v1.11.5 // indirect 127 - github.com/docker/go-connections v0.5.0 // indirect 132 + github.com/docker/go-connections v0.6.0 // indirect 128 133 github.com/docker/go-units v0.5.0 // indirect 129 134 github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 135 + github.com/ebitengine/purego v0.10.0 // indirect 130 136 github.com/emirpasic/gods v1.18.1 // indirect 131 137 github.com/felixge/httpsnoop v1.0.4 // indirect 132 138 github.com/fsnotify/fsnotify v1.6.0 // indirect ··· 137 143 github.com/go-logfmt/logfmt v0.6.0 // indirect 138 144 github.com/go-logr/logr v1.4.3 // indirect 139 145 github.com/go-logr/stdr v1.2.2 // indirect 146 + github.com/go-ole/go-ole v1.2.6 // indirect 140 147 github.com/go-redis/cache/v9 v9.0.0 // indirect 141 148 github.com/go-test/deep v1.1.1 // indirect 142 149 github.com/goccy/go-json v0.10.5 // indirect ··· 176 183 github.com/jackc/puddle/v2 v2.2.2 // indirect 177 184 github.com/json-iterator/go v1.1.12 // indirect 178 185 github.com/kevinburke/ssh_config v1.2.0 // indirect 179 - github.com/klauspost/compress v1.18.0 // indirect 186 + github.com/klauspost/compress v1.18.5 // indirect 180 187 github.com/klauspost/cpuid/v2 v2.3.0 // indirect 181 188 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 189 + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 190 + github.com/magiconair/properties v1.8.10 // indirect 182 191 github.com/mattn/go-isatty v0.0.20 // indirect 183 192 github.com/mattn/go-runewidth v0.0.16 // indirect 184 193 github.com/minio/sha256-simd v1.0.1 // indirect 185 194 github.com/mitchellh/mapstructure v1.5.0 // indirect 186 195 github.com/moby/docker-image-spec v1.3.1 // indirect 196 + github.com/moby/go-archive v0.2.0 // indirect 197 + github.com/moby/moby/api v1.54.1 // indirect 198 + github.com/moby/moby/client v0.4.0 // indirect 199 + github.com/moby/patternmatcher v0.6.1 // indirect 187 200 github.com/moby/sys/atomicwriter v0.1.0 // indirect 201 + github.com/moby/sys/sequential v0.6.0 // indirect 202 + github.com/moby/sys/user v0.4.0 // indirect 203 + github.com/moby/sys/userns v0.1.0 // indirect 188 204 github.com/moby/term v0.5.2 // indirect 189 205 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 190 206 github.com/modern-go/reflect2 v1.0.2 // indirect ··· 206 222 github.com/pkg/errors v0.9.1 // indirect 207 223 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 208 224 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 225 + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 209 226 github.com/prometheus/client_model v0.6.2 // indirect 210 227 github.com/prometheus/common v0.67.5 // indirect 211 228 github.com/prometheus/procfs v0.19.2 // indirect 212 229 github.com/rivo/uniseg v0.4.7 // indirect 213 230 github.com/ryanuber/go-glob v1.0.0 // indirect 214 231 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 232 + github.com/shirou/gopsutil/v4 v4.26.3 // indirect 233 + github.com/sirupsen/logrus v1.9.4 // indirect 215 234 github.com/spaolacci/murmur3 v1.1.0 // indirect 216 235 github.com/tidwall/gjson v1.18.0 // indirect 217 236 github.com/tidwall/match v1.2.0 // indirect 218 237 github.com/tidwall/pretty v1.2.1 // indirect 219 238 github.com/tidwall/sjson v1.2.5 // indirect 239 + github.com/tklauser/go-sysconf v0.3.16 // indirect 240 + github.com/tklauser/numcpus v0.11.0 // indirect 220 241 github.com/vmihailenco/go-tinylfu v0.2.2 // indirect 221 242 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 222 243 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 223 244 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 245 + github.com/yusufpapurcu/wmi v1.2.4 // indirect 224 246 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 225 247 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 226 248 go.etcd.io/bbolt v1.4.0 // indirect 227 249 go.opentelemetry.io/auto/sdk v1.2.1 // indirect 228 250 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect 229 - go.opentelemetry.io/otel v1.40.0 // indirect 230 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect 231 - go.opentelemetry.io/otel/metric v1.40.0 // indirect 232 - go.opentelemetry.io/otel/trace v1.40.0 // indirect 233 - go.opentelemetry.io/proto/otlp v1.9.0 // indirect 251 + go.opentelemetry.io/otel v1.41.0 // indirect 252 + go.opentelemetry.io/otel/metric v1.41.0 // indirect 253 + go.opentelemetry.io/otel/trace v1.41.0 // indirect 234 254 go.uber.org/atomic v1.11.0 // indirect 235 255 go.uber.org/multierr v1.11.0 // indirect 236 256 go.uber.org/zap v1.27.1 // indirect 237 257 go.yaml.in/yaml/v2 v2.4.3 // indirect 238 258 golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect 239 - golang.org/x/sync v0.19.0 // indirect 240 - golang.org/x/sys v0.41.0 // indirect 259 + golang.org/x/sys v0.42.0 // indirect 241 260 golang.org/x/text v0.34.0 // indirect 242 261 golang.org/x/time v0.12.0 // indirect 243 - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect 244 - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect 245 - google.golang.org/grpc v1.78.0 // indirect 246 262 google.golang.org/protobuf v1.36.11 // indirect 247 263 gopkg.in/fsnotify.v1 v1.4.7 // indirect 248 264 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 249 265 gopkg.in/warnings.v0 v0.1.2 // indirect 250 266 gopkg.in/yaml.v2 v2.4.0 // indirect 251 - gotest.tools/v3 v3.5.2 // indirect 252 267 lukechampine.com/blake3 v1.4.1 // indirect 253 268 ) 254 269
+74 -48
go.sum
··· 1 - dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 2 - dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 1 + dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 + dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= 4 + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 3 5 github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 4 6 github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 7 github.com/Blank-Xu/sql-adapter v1.1.1 h1:+g7QXU9sl/qT6Po97teMpf3GjAO0X9aFaqgSePXvYko= ··· 25 27 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 26 28 github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= 27 29 github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= 28 - github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= 29 - github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= 30 30 github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= 31 31 github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= 32 - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= 33 - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= 34 32 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY= 35 33 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= 36 34 github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= 37 35 github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= 38 - github.com/aws/aws-sdk-go-v2/credentials v1.19.9 h1:sWvTKsyrMlJGEuj/WgrwilpoJ6Xa1+KhIpGdzw7mMU8= 39 - github.com/aws/aws-sdk-go-v2/credentials v1.19.9/go.mod h1:+J44MBhmfVY/lETFiKI+klz0Vym2aCmIjqgClMmW82w= 40 36 github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= 41 37 github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= 42 38 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= 43 39 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= 44 - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= 45 - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= 46 40 github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= 47 41 github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= 48 - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= 49 - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= 50 42 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= 51 43 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= 52 44 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= 53 45 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= 54 - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k= 55 - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0= 56 46 github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= 57 47 github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= 58 - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= 59 - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= 60 48 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= 61 49 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= 62 - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs= 63 - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE= 64 50 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= 65 51 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= 66 - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= 67 - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= 68 52 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= 69 53 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= 70 - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U= 71 - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g= 72 54 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= 73 55 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= 74 - github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 h1:oeu8VPlOre74lBA/PMhxa5vewaMIMmILM+RraSyB8KA= 75 - github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo= 76 56 github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7MSNWeQ6eo247kE= 77 57 github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0= 78 58 github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= ··· 83 63 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= 84 64 github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= 85 65 github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= 86 - github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= 87 - github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= 88 66 github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= 89 67 github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 90 68 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= ··· 191 169 github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 192 170 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 193 171 github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 172 + github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 173 + github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 174 + github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 175 + github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 194 176 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 195 177 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 178 + github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= 179 + github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 196 180 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 197 181 github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 198 182 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= ··· 205 189 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 206 190 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 207 191 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 208 - github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 h1:AGh+Vn9fXhf9eo8erG1CK4+LACduPo64P1OICQLDv88= 209 - github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038/go.mod h1:ddIXqTTSXWtj5kMsHAPj8SvbIx2GZdAkBFgFa6e6+CM= 210 192 github.com/did-method-plc/go-didplc v0.2.2 h1:53HFhTT8NCAeFmZ6fdIZCf3PGDvj7A3cDjzOOEqn5XM= 211 193 github.com/did-method-plc/go-didplc v0.2.2/go.mod h1:bKdJ21irnwNHgVLWWL32zUWqZueXYbJRUcxplZghByo= 212 194 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= ··· 216 198 github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 217 199 github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= 218 200 github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 219 - github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 220 - github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 201 + github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= 202 + github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= 221 203 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 222 204 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 223 205 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 224 206 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 225 207 github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 226 208 github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 209 + github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= 210 + github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 227 211 github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 228 212 github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 229 213 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= ··· 264 248 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 265 249 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 266 250 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 251 + github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 252 + github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 267 253 github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= 268 254 github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI= 269 255 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= ··· 280 266 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 281 267 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 282 268 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 283 - github.com/goki/freetype v1.0.5 h1:yi2lQeUhXnBgSMqYd0vVmPw6RnnfIeTP3N4uvaJXd7A= 284 - github.com/goki/freetype v1.0.5/go.mod h1:wKmKxddbzKmeci9K96Wknn5kjTWLyfC8tKOqAFbEX8E= 285 269 github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= 286 270 github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 287 271 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= ··· 307 291 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 308 292 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 309 293 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 294 + github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 310 295 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 311 296 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 312 297 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= ··· 333 318 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 334 319 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 335 320 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 336 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= 337 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= 321 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 h1:NpbJl/eVbvrGE0MJ6X16X9SAifesl6Fwxg/YmCvubRI= 322 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8/go.mod h1:mi7YA+gCzVem12exXy46ZespvGtX/lZmD/RLnQhVW7U= 338 323 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 339 324 github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 340 325 github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= ··· 411 396 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 412 397 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 413 398 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 414 - github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 415 - github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 399 + github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= 400 + github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= 416 401 github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 417 402 github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 418 403 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= ··· 425 410 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 426 411 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 427 412 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 413 + github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 414 + github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 428 415 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 429 416 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 417 + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 418 + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 419 + github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= 420 + github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 430 421 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 431 422 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 432 423 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= ··· 435 426 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 436 427 github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= 437 428 github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 429 + github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= 430 + github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= 438 431 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 439 432 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 440 433 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= ··· 443 436 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 444 437 github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 445 438 github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 439 + github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= 440 + github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= 441 + github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= 442 + github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= 443 + github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= 444 + github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= 445 + github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= 446 + github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 446 447 github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 447 448 github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 448 449 github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 449 450 github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 451 + github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= 452 + github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 453 + github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 454 + github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 450 455 github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 451 456 github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 452 457 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= ··· 526 531 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 527 532 github.com/posthog/posthog-go v1.5.5 h1:2o3j7IrHbTIfxRtj4MPaXKeimuTYg49onNzNBZbwksM= 528 533 github.com/posthog/posthog-go v1.5.5/go.mod h1:3RqUmSnPuwmeVj/GYrS75wNGqcAKdpODiwc83xZWgdE= 534 + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= 535 + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 529 536 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 530 537 github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 531 538 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= ··· 553 560 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 554 561 github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= 555 562 github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= 563 + github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= 564 + github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= 556 565 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 557 - github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 558 - github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 566 + github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= 567 + github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= 559 568 github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 560 569 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 561 570 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= ··· 569 578 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 570 579 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 571 580 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 581 + github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= 582 + github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= 572 583 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 573 584 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 574 585 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= ··· 579 590 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 580 591 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 581 592 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 593 + github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= 594 + github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= 595 + github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo= 596 + github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs= 582 597 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 583 598 github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 584 599 github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= ··· 590 605 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 591 606 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= 592 607 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 608 + github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= 609 + github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= 610 + github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= 611 + github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= 593 612 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 594 613 github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8= 595 614 github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= ··· 618 637 github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= 619 638 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 620 639 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 640 + github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 641 + github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 621 642 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A= 622 643 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab/go.mod h1:SPu13/NPe1kMrbGoJldQwqtpNhXsmIuHCfm/aaGjU0c= 623 644 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= ··· 632 653 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 633 654 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= 634 655 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= 635 - go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= 636 - go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= 656 + go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= 657 + go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= 637 658 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= 638 659 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= 639 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= 640 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= 641 - go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= 642 - go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= 660 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= 661 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= 662 + go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= 663 + go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= 643 664 go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= 644 665 go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= 645 666 go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= 646 667 go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= 647 - go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= 648 - go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= 668 + go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= 669 + go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= 649 670 go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= 650 671 go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= 651 672 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= ··· 722 743 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 723 744 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 724 745 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 746 + golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 725 747 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 726 748 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 727 749 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 728 750 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 729 751 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 730 752 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 753 + golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 731 754 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 732 755 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 733 756 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 734 757 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 735 758 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 759 + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 736 760 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 737 761 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 738 762 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 748 772 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 749 773 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 750 774 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 751 - golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= 752 - golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 775 + golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= 776 + golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 753 777 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 754 778 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 755 779 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= ··· 842 866 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 843 867 lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= 844 868 lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= 869 + pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= 870 + pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 845 871 tangled.sh/oppi.li/go-gitdiff v0.8.2 h1:pASJJNWaFn6EmEIUNNjHZQ3stRu6BqTO2YyjKvTcxIc= 846 872 tangled.sh/oppi.li/go-gitdiff v0.8.2/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
+11
knotmirror/db/db.go
··· 68 68 constraint hosts_pkey primary key (hostname) 69 69 ); 70 70 71 + create table if not exists resync_buffer ( 72 + did text not null, 73 + rkey text not null, 74 + event_rkey text not null, 75 + db_created_at timestamptz not null default now(), 76 + 77 + constraint resync_buffer_pkey primary key (did, rkey, event_rkey), 78 + constraint resync_buffer_repo_fkey foreign key (did, rkey) 79 + references repos (did, rkey) on delete cascade 80 + ); 81 + 71 82 create index if not exists idx_repos_aturi on repos (at_uri); 72 83 create index if not exists idx_repos_db_updated_at on repos (db_updated_at desc); 73 84 create index if not exists idx_hosts_db_updated_at on hosts (db_updated_at desc);
-13
knotmirror/db/repos.go
··· 54 54 return nil 55 55 } 56 56 57 - func UpdateRepoState(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey, state models.RepoState) error { 58 - if _, err := e.ExecContext(ctx, 59 - `update repos 60 - set state = $1 61 - where did = $2 and rkey = $3`, 62 - state, 63 - did, rkey, 64 - ); err != nil { 65 - return fmt.Errorf("updating repo: %w", err) 66 - } 67 - return nil 68 - } 69 - 70 57 func DeleteRepo(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey) error { 71 58 if _, err := e.ExecContext(ctx, 72 59 `delete from repos where did = $1 and rkey = $2`,
+217
knotmirror/db/resync_buffer.go
··· 1 + package db 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "errors" 7 + "fmt" 8 + 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 + "tangled.org/core/knotmirror/models" 11 + ) 12 + 13 + func BufferEvent(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey, eventRkey string) error { 14 + tx, err := e.BeginTx(ctx, nil) 15 + if err != nil { 16 + return fmt.Errorf("begin tx: %w", err) 17 + } 18 + defer tx.Rollback() 19 + 20 + var state models.RepoState 21 + if err := tx.QueryRowContext(ctx, 22 + `select state from repos where did = $1 and rkey = $2 for update`, 23 + did, rkey, 24 + ).Scan(&state); err != nil { 25 + if errors.Is(err, sql.ErrNoRows) { 26 + return nil 27 + } 28 + return fmt.Errorf("locking repo: %w", err) 29 + } 30 + if state != models.RepoStateResyncing { 31 + return nil 32 + } 33 + 34 + if _, err := tx.ExecContext(ctx, 35 + `insert into resync_buffer (did, rkey, event_rkey) 36 + values ($1, $2, $3) 37 + on conflict (did, rkey, event_rkey) do nothing`, 38 + did, rkey, eventRkey, 39 + ); err != nil { 40 + return fmt.Errorf("buffering event: %w", err) 41 + } 42 + 43 + if err := tx.Commit(); err != nil { 44 + return fmt.Errorf("commit: %w", err) 45 + } 46 + return nil 47 + } 48 + 49 + func MarkDesync(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey) error { 50 + tx, err := e.BeginTx(ctx, nil) 51 + if err != nil { 52 + return fmt.Errorf("begin tx: %w", err) 53 + } 54 + defer tx.Rollback() 55 + 56 + var state models.RepoState 57 + if err := tx.QueryRowContext(ctx, 58 + `select state from repos where did = $1 and rkey = $2 for update`, 59 + did, rkey, 60 + ).Scan(&state); err != nil { 61 + if errors.Is(err, sql.ErrNoRows) { 62 + return nil 63 + } 64 + return fmt.Errorf("locking repo: %w", err) 65 + } 66 + switch state { 67 + case models.RepoStateActive, models.RepoStateDesynchronized, models.RepoStateError: 68 + default: 69 + return nil 70 + } 71 + 72 + if _, err := tx.ExecContext(ctx, 73 + `update repos set state = $1 where did = $2 and rkey = $3`, 74 + models.RepoStateDesynchronized, did, rkey, 75 + ); err != nil { 76 + return fmt.Errorf("marking desync: %w", err) 77 + } 78 + 79 + if err := tx.Commit(); err != nil { 80 + return fmt.Errorf("commit: %w", err) 81 + } 82 + return nil 83 + } 84 + 85 + func CompleteResync(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey) error { 86 + tx, err := e.BeginTx(ctx, nil) 87 + if err != nil { 88 + return fmt.Errorf("begin tx: %w", err) 89 + } 90 + defer tx.Rollback() 91 + 92 + var state models.RepoState 93 + if err := tx.QueryRowContext(ctx, 94 + `select state from repos where did = $1 and rkey = $2 for update`, 95 + did, rkey, 96 + ).Scan(&state); err != nil { 97 + if errors.Is(err, sql.ErrNoRows) { 98 + return nil 99 + } 100 + return fmt.Errorf("locking repo: %w", err) 101 + } 102 + if state != models.RepoStateResyncing { 103 + return nil 104 + } 105 + 106 + res, err := tx.ExecContext(ctx, 107 + `delete from resync_buffer where did = $1 and rkey = $2`, 108 + did, rkey, 109 + ) 110 + if err != nil { 111 + return fmt.Errorf("draining buffer: %w", err) 112 + } 113 + deleted, _ := res.RowsAffected() 114 + 115 + finalState := models.RepoStateActive 116 + if deleted > 0 { 117 + finalState = models.RepoStateDesynchronized 118 + } 119 + 120 + if _, err := tx.ExecContext(ctx, 121 + `update repos 122 + set error_msg = '', 123 + retry_count = 0, 124 + retry_after = 0, 125 + state = $1 126 + where did = $2 and rkey = $3`, 127 + finalState, did, rkey, 128 + ); err != nil { 129 + return fmt.Errorf("finalising repo: %w", err) 130 + } 131 + 132 + return tx.Commit() 133 + } 134 + 135 + func CancelResync(ctx context.Context, e *sql.DB, aturi syntax.ATURI) error { 136 + tx, err := e.BeginTx(ctx, nil) 137 + if err != nil { 138 + return fmt.Errorf("begin tx: %w", err) 139 + } 140 + defer tx.Rollback() 141 + 142 + var did syntax.DID 143 + var rkey syntax.RecordKey 144 + var state models.RepoState 145 + if err := tx.QueryRowContext(ctx, 146 + `select did, rkey, state from repos where at_uri = $1 for update`, 147 + aturi, 148 + ).Scan(&did, &rkey, &state); err != nil { 149 + if errors.Is(err, sql.ErrNoRows) { 150 + return nil 151 + } 152 + return fmt.Errorf("locking repo: %w", err) 153 + } 154 + if state != models.RepoStateResyncing { 155 + return nil 156 + } 157 + 158 + if _, err := tx.ExecContext(ctx, 159 + `delete from resync_buffer where did = $1 and rkey = $2`, 160 + did, rkey, 161 + ); err != nil { 162 + return fmt.Errorf("draining buffer: %w", err) 163 + } 164 + 165 + if _, err := tx.ExecContext(ctx, 166 + `update repos set state = $1 where did = $2 and rkey = $3`, 167 + models.RepoStateSuspended, did, rkey, 168 + ); err != nil { 169 + return fmt.Errorf("suspending repo: %w", err) 170 + } 171 + 172 + return tx.Commit() 173 + } 174 + 175 + func FailResync(ctx context.Context, e *sql.DB, did syntax.DID, rkey syntax.RecordKey, state models.RepoState, errMsg string, retryCount int, retryAfter int64) error { 176 + tx, err := e.BeginTx(ctx, nil) 177 + if err != nil { 178 + return fmt.Errorf("begin tx: %w", err) 179 + } 180 + defer tx.Rollback() 181 + 182 + var current models.RepoState 183 + if err := tx.QueryRowContext(ctx, 184 + `select state from repos where did = $1 and rkey = $2 for update`, 185 + did, rkey, 186 + ).Scan(&current); err != nil { 187 + if errors.Is(err, sql.ErrNoRows) { 188 + return nil 189 + } 190 + return fmt.Errorf("locking repo: %w", err) 191 + } 192 + if current != models.RepoStateResyncing { 193 + return nil 194 + } 195 + 196 + if _, err := tx.ExecContext(ctx, 197 + `delete from resync_buffer where did = $1 and rkey = $2`, 198 + did, rkey, 199 + ); err != nil { 200 + return fmt.Errorf("draining buffer: %w", err) 201 + } 202 + 203 + if _, err := tx.ExecContext(ctx, 204 + `update repos 205 + set error_msg = $1, 206 + retry_count = $2, 207 + retry_after = $3, 208 + state = $4 209 + where did = $5 and rkey = $6`, 210 + errMsg, retryCount, retryAfter, state, 211 + did, rkey, 212 + ); err != nil { 213 + return fmt.Errorf("finalising repo: %w", err) 214 + } 215 + 216 + return tx.Commit() 217 + }
+8 -1
knotmirror/knotmirror.go
··· 49 49 } 50 50 logger.Info(fmt.Sprintf("clearing resyning states: %d records updated", rows)) 51 51 52 + if _, err := db.ExecContext(ctx, `delete from resync_buffer`); err != nil { 53 + return fmt.Errorf("clearing resync buffer: %w", err) 54 + } 55 + 52 56 knotstream := knotstream.NewKnotStream(logger, db, cfg) 53 57 crawler := NewCrawler(logger, db) 54 58 resyncer := NewResyncer(logger, db, gitm, cfg) ··· 119 123 } 120 124 121 125 logger.Info("shutting down knotmirror") 122 - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) 126 + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 35*time.Second) 123 127 defer shutdownCancel() 124 128 125 129 var errs []error 126 130 if err := knotstream.Shutdown(shutdownCtx); err != nil { 131 + errs = append(errs, err) 132 + } 133 + if err := resyncer.Shutdown(shutdownCtx); err != nil { 127 134 errs = append(errs, err) 128 135 } 129 136 if err := db.Close(); err != nil {
+14 -12
knotmirror/knotstream/slurper.go
··· 312 312 } 313 313 l = l.With("repoAt", curr.AtUri()) 314 314 315 - // TODO: should plan resync to resyncBuffer on RepoStateResyncing 316 - if curr.State != models.RepoStateActive { 317 - l.Debug("skipping non-active repo") 315 + if curr.State == models.RepoStateSuspended || curr.State == models.RepoStatePending { 316 + l.Debug("skipping repo", "state", curr.State) 318 317 knotstreamEventsSkipped.Inc() 319 318 return nil 320 319 } ··· 325 324 return nil 326 325 } 327 326 328 - // if curr.State == models.RepoStateResyncing { 329 - // firehoseEventsSkipped.Inc() 330 - // return fp.events.addToResyncBuffer(ctx, commit) 331 - // } 332 - 333 - // can't skip anything, update repo state 334 - if err := db.UpdateRepoState(ctx, s.db, curr.Did, curr.Rkey, models.RepoStateDesynchronized); err != nil { 335 - return err 327 + switch curr.State { 328 + case models.RepoStateResyncing: 329 + if err := db.BufferEvent(ctx, s.db, curr.Did, curr.Rkey, evt.Rkey); err != nil { 330 + return fmt.Errorf("buffering event: %w", err) 331 + } 332 + case models.RepoStateActive, models.RepoStateDesynchronized, models.RepoStateError: 333 + if err := db.MarkDesync(ctx, s.db, curr.Did, curr.Rkey); err != nil { 334 + return fmt.Errorf("marking desync: %w", err) 335 + } 336 + default: 337 + knotstreamEventsSkipped.Inc() 338 + return nil 336 339 } 337 340 338 341 l.Info("event processed", "eventRev", evt.Rkey) 339 - 340 342 knotstreamEventsProcessed.Inc() 341 343 return nil 342 344 }
+137 -80
knotmirror/resyncer.go
··· 31 31 runningJobs map[syntax.ATURI]context.CancelFunc 32 32 runningJobsMu sync.Mutex 33 33 34 + workersWg sync.WaitGroup 35 + 34 36 repoFetchTimeout time.Duration 35 37 manualResyncTimeout time.Duration 36 38 parallelism int ··· 38 40 knotBackoff map[string]time.Time 39 41 knotBackoffMu sync.RWMutex 40 42 } 43 + 44 + const finalizationTimeout = 30 * time.Second 41 45 42 46 func NewResyncer(l *slog.Logger, db *sql.DB, gitm GitMirrorManager, cfg *config.Config) *Resyncer { 43 47 return &Resyncer{ ··· 57 61 } 58 62 59 63 func (r *Resyncer) Start(ctx context.Context) { 60 - for i := 0; i < r.parallelism; i++ { 61 - go r.runResyncWorker(ctx, i) 64 + for i := range r.parallelism { 65 + r.workersWg.Add(1) 66 + go func(id int) { 67 + defer r.workersWg.Done() 68 + r.runResyncWorker(ctx, id) 69 + }(i) 70 + } 71 + } 72 + 73 + func (r *Resyncer) Shutdown(ctx context.Context) error { 74 + r.runningJobsMu.Lock() 75 + for _, cancel := range r.runningJobs { 76 + cancel() 77 + } 78 + r.runningJobsMu.Unlock() 79 + 80 + done := make(chan struct{}) 81 + go func() { 82 + r.workersWg.Wait() 83 + close(done) 84 + }() 85 + select { 86 + case <-done: 87 + return nil 88 + case <-ctx.Done(): 89 + return ctx.Err() 62 90 } 63 91 } 64 92 ··· 71 99 return 72 100 default: 73 101 } 74 - repoAt, found, err := r.claimResyncJob(ctx) 102 + repoAt, jobCtx, found, err := r.claimResyncJob(ctx) 75 103 if err != nil { 104 + if errors.Is(err, context.Canceled) { 105 + return 106 + } 76 107 l.Error("failed to claim resync job", "error", err) 77 - time.Sleep(time.Second) 108 + if !sleepOrDone(ctx, time.Second) { 109 + return 110 + } 78 111 continue 79 112 } 80 113 if !found { 81 - time.Sleep(time.Second) 114 + if !sleepOrDone(ctx, time.Second) { 115 + return 116 + } 82 117 continue 83 118 } 84 119 l.Info("processing resync", "aturi", repoAt) 85 - if err := r.resyncRepo(ctx, repoAt); err != nil { 120 + if err := r.resyncRepo(jobCtx, repoAt); err != nil { 86 121 l.Error("resync failed", "aturi", repoAt, "error", err) 87 122 } 88 123 } 89 124 } 90 125 91 - func (r *Resyncer) registerRunning(repo syntax.ATURI, cancel context.CancelFunc) { 92 - r.runningJobsMu.Lock() 93 - defer r.runningJobsMu.Unlock() 94 - 95 - if _, exists := r.runningJobs[repo]; exists { 96 - return 126 + func sleepOrDone(ctx context.Context, d time.Duration) bool { 127 + t := time.NewTimer(d) 128 + defer t.Stop() 129 + select { 130 + case <-ctx.Done(): 131 + return false 132 + case <-t.C: 133 + return true 97 134 } 98 - r.runningJobs[repo] = cancel 99 135 } 100 136 101 - func (r *Resyncer) unregisterRunning(repo syntax.ATURI) { 137 + func (r *Resyncer) finalizeJob(repo syntax.ATURI) { 102 138 r.runningJobsMu.Lock() 103 139 defer r.runningJobsMu.Unlock() 104 140 105 - delete(r.runningJobs, repo) 141 + if cancel, ok := r.runningJobs[repo]; ok { 142 + cancel() 143 + delete(r.runningJobs, repo) 144 + } 106 145 } 107 146 108 147 func (r *Resyncer) CancelResyncJob(repo syntax.ATURI) { 109 - r.runningJobsMu.Lock() 110 - defer r.runningJobsMu.Unlock() 148 + r.claimJobMu.Lock() 149 + defer r.claimJobMu.Unlock() 111 150 112 - cancel, ok := r.runningJobs[repo] 113 - if !ok { 114 - return 151 + ctx, cancel := context.WithTimeout(context.Background(), finalizationTimeout) 152 + defer cancel() 153 + if err := db.CancelResync(ctx, r.db, repo); err != nil { 154 + r.logger.Error("failed to suspend repo on cancel", "at_uri", repo, "error", err) 115 155 } 116 - delete(r.runningJobs, repo) 117 - cancel() 156 + r.finalizeJob(repo) 118 157 } 119 158 120 159 // TriggerResyncJob manually triggers the resync job 121 160 func (r *Resyncer) TriggerResyncJob(ctx context.Context, repoAt syntax.ATURI) error { 161 + res, err := r.db.ExecContext(ctx, 162 + `update repos 163 + set state = $1, retry_after = $2 164 + where at_uri = $3 and state != $4`, 165 + models.RepoStatePending, 166 + int64(-1), 167 + repoAt, 168 + models.RepoStateResyncing, 169 + ) 170 + if err != nil { 171 + return fmt.Errorf("triggering resync: %w", err) 172 + } 173 + n, err := res.RowsAffected() 174 + if err != nil { 175 + return fmt.Errorf("triggering resync: %w", err) 176 + } 177 + if n > 0 { 178 + return nil 179 + } 180 + 122 181 repo, err := db.GetRepoByAtUri(ctx, r.db, repoAt) 123 182 if err != nil { 124 183 return fmt.Errorf("failed to get repo: %w", err) ··· 126 185 if repo == nil { 127 186 return fmt.Errorf("repo not found: %s", repoAt) 128 187 } 129 - 130 - if repo.State == models.RepoStateResyncing { 131 - return fmt.Errorf("repo already resyncing") 132 - } 133 - 134 - repo.State = models.RepoStatePending 135 - repo.RetryAfter = -1 // resyncer will prioritize this 136 - 137 - if err := db.UpsertRepo(ctx, r.db, repo); err != nil { 138 - return fmt.Errorf("updating repo state to pending %w", err) 139 - } 140 - return nil 188 + return fmt.Errorf("cannot trigger resync: repo in state %s", repo.State) 141 189 } 142 190 143 - func (r *Resyncer) claimResyncJob(ctx context.Context) (syntax.ATURI, bool, error) { 144 - // use mutex to prevent duplicated jobs 191 + func (r *Resyncer) claimResyncJob(ctx context.Context) (syntax.ATURI, context.Context, bool, error) { 145 192 r.claimJobMu.Lock() 146 193 defer r.claimJobMu.Unlock() 147 194 148 - var repoAt syntax.ATURI 149 - now := time.Now().Unix() 150 - if err := r.db.QueryRowContext(ctx, 151 - `update repos 195 + const query = `update repos 152 196 set state = $1 153 197 where at_uri = ( 154 198 select at_uri from repos ··· 160 204 retry_after 161 205 limit 1 162 206 ) 163 - returning at_uri 164 - `, 207 + returning at_uri` 208 + 209 + var repoAt syntax.ATURI 210 + if err := r.db.QueryRowContext(ctx, query, 165 211 models.RepoStateResyncing, 166 212 models.RepoStatePending, models.RepoStateDesynchronized, models.RepoStateError, 167 - now, 213 + time.Now().Unix(), 168 214 ).Scan(&repoAt); err != nil { 169 215 if errors.Is(err, sql.ErrNoRows) { 170 - return "", false, nil 216 + return "", nil, false, nil 171 217 } 172 - return "", false, err 218 + return "", nil, false, err 173 219 } 174 220 175 - return repoAt, true, nil 221 + jobCtx, cancel := context.WithCancel(ctx) 222 + r.runningJobsMu.Lock() 223 + r.runningJobs[repoAt] = cancel 224 + r.runningJobsMu.Unlock() 225 + 226 + return repoAt, jobCtx, true, nil 176 227 } 177 228 178 - func (r *Resyncer) resyncRepo(ctx context.Context, repoAt syntax.ATURI) error { 229 + func (r *Resyncer) resyncRepo(jobCtx context.Context, repoAt syntax.ATURI) error { 179 230 // ctx, span := tracer.Start(ctx, "resyncRepo") 180 231 // span.SetAttributes(attribute.String("aturi", repoAt)) 181 232 // defer span.End() ··· 183 234 resyncsStarted.Inc() 184 235 startTime := time.Now() 185 236 186 - jobCtx, cancel := context.WithCancel(ctx) 187 - r.registerRunning(repoAt, cancel) 188 - defer r.unregisterRunning(repoAt) 237 + defer r.finalizeJob(repoAt) 189 238 190 239 success, err := r.doResync(jobCtx, repoAt) 191 240 if !success { 192 241 resyncsFailed.Inc() 193 242 resyncDuration.Observe(time.Since(startTime).Seconds()) 194 - return r.handleResyncFailure(ctx, repoAt, err) 243 + return r.handleResyncFailure(repoAt, err) 195 244 } 196 245 197 246 resyncsCompleted.Inc() ··· 199 248 return nil 200 249 } 201 250 202 - func (r *Resyncer) doResync(ctx context.Context, repoAt syntax.ATURI) (bool, error) { 251 + func (r *Resyncer) doResync(jobCtx context.Context, repoAt syntax.ATURI) (bool, error) { 203 252 // ctx, span := tracer.Start(ctx, "doResync") 204 253 // span.SetAttributes(attribute.String("aturi", repoAt)) 205 254 // defer span.End() 206 255 207 - repo, err := db.GetRepoByAtUri(ctx, r.db, repoAt) 256 + repo, err := db.GetRepoByAtUri(jobCtx, r.db, repoAt) 208 257 if err != nil { 209 258 return false, fmt.Errorf("failed to get repo: %w", err) 210 259 } ··· 222 271 // HACK: check knot reachability with short timeout before running actual fetch. 223 272 // This is crucial as git-cli doesn't support http connection timeout. 224 273 // `http.lowSpeedTime` is only applied _after_ the connection. 225 - if err := r.checkKnotReachability(ctx, repo); err != nil { 274 + if err := r.checkKnotReachability(jobCtx, repo); err != nil { 226 275 if isRateLimitError(err) { 227 276 r.knotBackoffMu.Lock() 228 277 r.knotBackoff[repo.KnotDomain] = time.Now().Add(10 * time.Second) ··· 237 286 if repo.RetryAfter == -1 { 238 287 timeout = r.manualResyncTimeout 239 288 } 240 - fetchCtx, cancel := context.WithTimeout(ctx, timeout) 289 + fetchCtx, cancel := context.WithTimeout(jobCtx, timeout) 241 290 defer cancel() 242 291 243 292 if err := r.gitm.Sync(fetchCtx, repo); err != nil { ··· 246 295 247 296 // repo.GitRev = <processed git.refUpdate revision> 248 297 // repo.RepoSha = <sha256 sum of git refs> 249 - repo.State = models.RepoStateActive 250 - repo.ErrorMsg = "" 251 - repo.RetryCount = 0 252 - repo.RetryAfter = 0 253 - if err := db.UpsertRepo(ctx, r.db, repo); err != nil { 254 - return false, fmt.Errorf("updating repo state to active %w", err) 298 + finCtx, finCancel := context.WithTimeout(context.Background(), finalizationTimeout) 299 + defer finCancel() 300 + if err := db.CompleteResync(finCtx, r.db, repo.Did, repo.Rkey); err != nil { 301 + return false, fmt.Errorf("completing resync: %w", err) 255 302 } 256 303 return true, nil 257 304 } ··· 316 363 return nil 317 364 } 318 365 319 - func (r *Resyncer) handleResyncFailure(ctx context.Context, repoAt syntax.ATURI, err error) error { 366 + func (r *Resyncer) handleResyncFailure(repoAt syntax.ATURI, err error) error { 320 367 r.logger.Debug("handleResyncFailure", "at_uri", repoAt, "err", err) 368 + 369 + if errors.Is(err, context.Canceled) { 370 + return nil 371 + } 372 + 373 + ctx, cancel := context.WithTimeout(context.Background(), finalizationTimeout) 374 + defer cancel() 375 + 376 + repo, getErr := db.GetRepoByAtUri(ctx, r.db, repoAt) 377 + if getErr != nil { 378 + return fmt.Errorf("failed to get repo: %w", getErr) 379 + } 380 + if repo == nil { 381 + return fmt.Errorf("failed to get repo. repo '%s' doesn't exist in db", repoAt) 382 + } 383 + 384 + if repo.State != models.RepoStateResyncing { 385 + return nil 386 + } 387 + 321 388 var state models.RepoState 322 389 var errMsg string 390 + var retryCount int 391 + var retryAfter int64 323 392 if err == nil { 324 393 state = models.RepoStateDesynchronized 325 - errMsg = "" 394 + retryCount = repo.RetryCount 395 + retryAfter = time.Now().Add(10 * time.Second).Unix() 326 396 } else { 327 397 state = models.RepoStateError 328 398 errMsg = err.Error() 329 - } 330 - 331 - repo, err := db.GetRepoByAtUri(ctx, r.db, repoAt) 332 - if err != nil { 333 - return fmt.Errorf("failed to get repo: %w", err) 334 - } 335 - if repo == nil { 336 - return fmt.Errorf("failed to get repo. repo '%s' doesn't exist in db", repoAt) 399 + retryCount = repo.RetryCount + 1 400 + // start a 1 min & go up to 1 hr between retries 401 + retryAfter = time.Now().Add(backoff(retryCount, 60) * 60).Unix() 337 402 } 338 - 339 - // start a 1 min & go up to 1 hr between retries 340 - var retryCount = repo.RetryCount + 1 341 - var retryAfter = time.Now().Add(backoff(retryCount, 60) * 60).Unix() 342 403 343 404 // remove null bytes 344 405 errMsg = strings.ReplaceAll(errMsg, "\x00", "") 345 406 346 - repo.State = state 347 - repo.ErrorMsg = errMsg 348 - repo.RetryCount = retryCount 349 - repo.RetryAfter = retryAfter 350 - if err := db.UpsertRepo(ctx, r.db, repo); err != nil { 407 + if err := db.FailResync(ctx, r.db, repo.Did, repo.Rkey, state, errMsg, retryCount, retryAfter); err != nil { 351 408 return fmt.Errorf("failed to update repo state: %w", err) 352 409 } 353 410 return nil
+294
knotmirror/resyncer_test.go
··· 1 + package knotmirror_test 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "io" 7 + "log/slog" 8 + "net/http" 9 + "net/http/httptest" 10 + "os" 11 + "sync" 12 + "testing" 13 + "time" 14 + 15 + "github.com/bluesky-social/indigo/atproto/syntax" 16 + "github.com/stretchr/testify/require" 17 + "github.com/testcontainers/testcontainers-go" 18 + "github.com/testcontainers/testcontainers-go/modules/postgres" 19 + "github.com/testcontainers/testcontainers-go/wait" 20 + 21 + "tangled.org/core/api/tangled" 22 + "tangled.org/core/knotmirror" 23 + "tangled.org/core/knotmirror/config" 24 + "tangled.org/core/knotmirror/db" 25 + "tangled.org/core/knotmirror/knotstream" 26 + "tangled.org/core/knotmirror/models" 27 + ) 28 + 29 + type blockingGitm struct { 30 + mu sync.Mutex 31 + calls []syntax.ATURI 32 + started chan struct{} 33 + release chan struct{} 34 + } 35 + 36 + func newBlockingGitm() *blockingGitm { 37 + return &blockingGitm{ 38 + started: make(chan struct{}, 16), 39 + release: make(chan struct{}, 16), 40 + } 41 + } 42 + 43 + func (f *blockingGitm) Exist(repo *models.Repo) (bool, error) { return true, nil } 44 + func (f *blockingGitm) RemoteSetUrl(ctx context.Context, repo *models.Repo) error { return nil } 45 + func (f *blockingGitm) Clone(ctx context.Context, repo *models.Repo) error { return nil } 46 + func (f *blockingGitm) Fetch(ctx context.Context, repo *models.Repo) error { return nil } 47 + 48 + func (f *blockingGitm) Sync(ctx context.Context, repo *models.Repo) error { 49 + f.mu.Lock() 50 + f.calls = append(f.calls, repo.AtUri()) 51 + f.mu.Unlock() 52 + select { 53 + case f.started <- struct{}{}: 54 + case <-ctx.Done(): 55 + return ctx.Err() 56 + } 57 + select { 58 + case <-f.release: 59 + return nil 60 + case <-ctx.Done(): 61 + return ctx.Err() 62 + } 63 + } 64 + 65 + func (f *blockingGitm) callCount() int { 66 + f.mu.Lock() 67 + defer f.mu.Unlock() 68 + return len(f.calls) 69 + } 70 + 71 + var _ knotmirror.GitMirrorManager = (*blockingGitm)(nil) 72 + 73 + func startPostgres(t *testing.T, ctx context.Context) *sql.DB { 74 + t.Helper() 75 + 76 + if url := os.Getenv("TEST_POSTGRES_URL"); url != "" { 77 + database, err := db.Make(ctx, url, 4) 78 + require.NoError(t, err) 79 + _, err = database.ExecContext(ctx, `truncate table repos, hosts, resync_buffer`) 80 + require.NoError(t, err) 81 + t.Cleanup(func() { 82 + _ = database.Close() 83 + }) 84 + return database 85 + } 86 + 87 + pg, err := postgres.Run(ctx, "docker.io/library/postgres:16-alpine", 88 + postgres.WithDatabase("mirror"), 89 + postgres.WithUsername("tnglr"), 90 + postgres.WithPassword("test"), 91 + testcontainers.WithWaitStrategy( 92 + wait.ForLog("database system is ready to accept connections"). 93 + WithOccurrence(2). 94 + WithStartupTimeout(60*time.Second), 95 + ), 96 + ) 97 + if err != nil { 98 + t.Skipf("postgres container unavailable, is podman/docker running: %v", err) 99 + } 100 + t.Cleanup(func() { 101 + _ = pg.Terminate(context.Background()) 102 + }) 103 + 104 + url, err := pg.ConnectionString(ctx, "sslmode=disable") 105 + require.NoError(t, err) 106 + 107 + database, err := db.Make(ctx, url, 4) 108 + require.NoError(t, err) 109 + t.Cleanup(func() { 110 + _ = database.Close() 111 + }) 112 + return database 113 + } 114 + 115 + func testLogger() *slog.Logger { 116 + return slog.New(slog.NewTextHandler(io.Discard, nil)) 117 + } 118 + 119 + func mockKnot(t *testing.T) *httptest.Server { 120 + t.Helper() 121 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 122 + w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") 123 + w.WriteHeader(http.StatusOK) 124 + _, _ = w.Write([]byte("001e# service=git-upload-pack\n")) 125 + })) 126 + t.Cleanup(srv.Close) 127 + return srv 128 + } 129 + 130 + func seedActiveRepo(t *testing.T, ctx context.Context, database *sql.DB, knotURL string) *models.Repo { 131 + t.Helper() 132 + repo := &models.Repo{ 133 + Did: syntax.DID("did:plc:testingtestingtestingtest"), 134 + Rkey: syntax.RecordKey("3kaaaaaaaaaaaa"), 135 + Name: "race-repo", 136 + KnotDomain: knotURL, 137 + State: models.RepoStateActive, 138 + } 139 + require.NoError(t, db.UpsertRepo(ctx, database, repo)) 140 + return repo 141 + } 142 + 143 + func mkEvent(rkey string, oldSha, newSha string, repo *models.Repo) *knotstream.LegacyGitEvent { 144 + owner := repo.Did.String() 145 + repoDid := repo.Did.String() 146 + return &knotstream.LegacyGitEvent{ 147 + Rkey: rkey, 148 + Nsid: tangled.GitRefUpdateNSID, 149 + Event: tangled.GitRefUpdate{ 150 + OwnerDid: &owner, 151 + RepoName: repo.Name, 152 + RepoDid: &repoDid, 153 + OldSha: oldSha, 154 + NewSha: newSha, 155 + Ref: "refs/heads/main", 156 + }, 157 + } 158 + } 159 + 160 + func TestEventDuringResyncTriggersSecondSync(t *testing.T) { 161 + ctx, cancel := context.WithCancel(context.Background()) 162 + t.Cleanup(cancel) 163 + 164 + database := startPostgres(t, ctx) 165 + knotSrv := mockKnot(t) 166 + repo := seedActiveRepo(t, ctx, database, knotSrv.URL) 167 + 168 + gitm := newBlockingGitm() 169 + logger := testLogger() 170 + cfg := &config.Config{ 171 + KnotUseSSL: false, 172 + GitRepoFetchTimeout: 10 * time.Second, 173 + ResyncParallelism: 1, 174 + } 175 + 176 + slurper := knotstream.NewKnotSlurper(logger, database, cfg.Slurper) 177 + resyncer := knotmirror.NewResyncer(logger, database, gitm, cfg) 178 + 179 + eventA := mkEvent("3evtaaaaaaaaaa", "0000000000000000000000000000000000000000", "1111111111111111111111111111111111111111", repo) 180 + require.NoError(t, slurper.ProcessLegacyGitRefUpdate(ctx, "testsrc", eventA)) 181 + 182 + got, err := db.GetRepoByAtUri(ctx, database, repo.AtUri()) 183 + require.NoError(t, err) 184 + require.Equal(t, models.RepoStateDesynchronized, got.State, "event A should mark repo desynchronized") 185 + 186 + resyncer.Start(ctx) 187 + t.Cleanup(func() { 188 + cancel() 189 + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) 190 + defer shutdownCancel() 191 + _ = resyncer.Shutdown(shutdownCtx) 192 + }) 193 + 194 + select { 195 + case <-gitm.started: 196 + case <-time.After(10 * time.Second): 197 + t.Fatal("timeout waiting for first Sync to start") 198 + } 199 + 200 + got, err = db.GetRepoByAtUri(ctx, database, repo.AtUri()) 201 + require.NoError(t, err) 202 + require.Equal(t, models.RepoStateResyncing, got.State, "state should be resyncing while fetch is in flight") 203 + 204 + eventB := mkEvent("3evtbbbbbbbbbb", "1111111111111111111111111111111111111111", "2222222222222222222222222222222222222222", repo) 205 + require.NoError(t, slurper.ProcessLegacyGitRefUpdate(ctx, "testsrc", eventB)) 206 + 207 + gitm.release <- struct{}{} 208 + 209 + select { 210 + case <-gitm.started: 211 + gitm.release <- struct{}{} 212 + case <-time.After(10 * time.Second): 213 + t.Fatalf("event B arriving during resync should trigger a second Sync, got %d total Sync calls", gitm.callCount()) 214 + } 215 + 216 + require.Eventually(t, func() bool { 217 + got, err := db.GetRepoByAtUri(ctx, database, repo.AtUri()) 218 + return err == nil && got.State == models.RepoStateActive 219 + }, 10*time.Second, 50*time.Millisecond, "repo should end in active state") 220 + 221 + require.GreaterOrEqual(t, gitm.callCount(), 2, "expected at least 2 Sync calls") 222 + } 223 + 224 + func TestConcurrentWorkersClaimDistinctRepos(t *testing.T) { 225 + ctx, cancel := context.WithCancel(context.Background()) 226 + t.Cleanup(cancel) 227 + 228 + database := startPostgres(t, ctx) 229 + knotSrv := mockKnot(t) 230 + 231 + repoA := &models.Repo{ 232 + Did: syntax.DID("did:plc:aaaaaaaaaaaaaaaaaaaaaaaa"), 233 + Rkey: syntax.RecordKey("3kaaaaaaaaaaaa"), 234 + Name: "repo-a", 235 + KnotDomain: knotSrv.URL, 236 + State: models.RepoStateDesynchronized, 237 + } 238 + repoB := &models.Repo{ 239 + Did: syntax.DID("did:plc:bbbbbbbbbbbbbbbbbbbbbbbb"), 240 + Rkey: syntax.RecordKey("3kbbbbbbbbbbbb"), 241 + Name: "repo-b", 242 + KnotDomain: knotSrv.URL, 243 + State: models.RepoStateDesynchronized, 244 + } 245 + require.NoError(t, db.UpsertRepo(ctx, database, repoA)) 246 + require.NoError(t, db.UpsertRepo(ctx, database, repoB)) 247 + 248 + gitm := newBlockingGitm() 249 + logger := testLogger() 250 + cfg := &config.Config{ 251 + KnotUseSSL: false, 252 + GitRepoFetchTimeout: 10 * time.Second, 253 + ResyncParallelism: 2, 254 + } 255 + 256 + resyncer := knotmirror.NewResyncer(logger, database, gitm, cfg) 257 + resyncer.Start(ctx) 258 + t.Cleanup(func() { 259 + cancel() 260 + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) 261 + defer shutdownCancel() 262 + _ = resyncer.Shutdown(shutdownCtx) 263 + }) 264 + 265 + for range 2 { 266 + select { 267 + case <-gitm.started: 268 + case <-time.After(10 * time.Second): 269 + t.Fatalf("timeout waiting for concurrent Syncs, got %d calls", gitm.callCount()) 270 + } 271 + } 272 + 273 + gitm.mu.Lock() 274 + claimed := map[syntax.ATURI]int{} 275 + for _, c := range gitm.calls { 276 + claimed[c]++ 277 + } 278 + gitm.mu.Unlock() 279 + require.Len(t, claimed, 2, "two distinct repos should be claimed concurrently") 280 + require.Equal(t, 1, claimed[repoA.AtUri()]) 281 + require.Equal(t, 1, claimed[repoB.AtUri()]) 282 + 283 + gitm.release <- struct{}{} 284 + gitm.release <- struct{}{} 285 + 286 + require.Eventually(t, func() bool { 287 + gotA, errA := db.GetRepoByAtUri(ctx, database, repoA.AtUri()) 288 + gotB, errB := db.GetRepoByAtUri(ctx, database, repoB.AtUri()) 289 + return errA == nil && errB == nil && 290 + gotA != nil && gotB != nil && 291 + gotA.State == models.RepoStateActive && 292 + gotB.State == models.RepoStateActive 293 + }, 10*time.Second, 50*time.Millisecond, "both repos should end in active state") 294 + }
+6 -1
knotmirror/xrpc/proxy.go
··· 49 49 if err == nil && repo != nil { 50 50 if repo.State != models.RepoStatePending && repo.State != models.RepoStateResyncing { 51 51 go func() { 52 - if err := db.UpdateRepoState(context.Background(), x.db, repo.Did, repo.Rkey, models.RepoStatePending); err != nil { 52 + if _, err := x.db.ExecContext(context.Background(), 53 + `update repos set state = $1 54 + where did = $2 and rkey = $3 and state not in ($4, $5)`, 55 + models.RepoStatePending, repo.Did, repo.Rkey, 56 + models.RepoStatePending, models.RepoStateResyncing, 57 + ); err != nil { 53 58 x.logger.Error("failed to mark repo for resync after proxy", "err", err) 54 59 } 55 60 }()