Monorepo for Tangled tangled.org

spindle: engine: consume from knotstream and execute

The engine package currently uses the docker client setup the pipeline
and execute steps. The flow is like so:

- setup pipeline network for all steps to join to
- create a volume for the nix store (to persist packages across steps)
- create a volume for the workspace directory
- build a nixery.dev URL with packages we want in the container
- execute each step command in a new container using the same image

It's pretty unfinished still. Things to be done:

- support for other registries; currently only works with nixpkgs
- custom nixery URL
- ... a lot more that I'm forgetting now

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

anirudh.fi d95215cc baeaadbf

verified
Changed files
+422 -17
spindle
+21 -3
go.mod
··· 14 github.com/casbin/casbin/v2 v2.103.0 15 github.com/cyphar/filepath-securejoin v0.4.1 16 github.com/dgraph-io/ristretto v0.2.0 17 github.com/dustin/go-humanize v1.0.1 18 github.com/gliderlabs/ssh v0.3.8 19 github.com/go-chi/chi/v5 v5.2.0 ··· 28 github.com/mattn/go-sqlite3 v1.14.24 29 github.com/microcosm-cc/bluemonday v1.0.27 30 github.com/posthog/posthog-go v1.5.5 31 github.com/resend/resend-go/v2 v2.15.0 32 github.com/sethvargo/go-envconfig v1.1.0 33 github.com/stretchr/testify v1.10.0 ··· 35 github.com/whyrusleeping/cbor-gen v0.3.1 36 github.com/yuin/goldmark v1.4.13 37 golang.org/x/crypto v0.38.0 38 - golang.org/x/net v0.39.0 39 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 40 gopkg.in/yaml.v3 v3.0.1 41 tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250526154904-3906c5336421 ··· 52 github.com/casbin/govaluate v1.3.0 // indirect 53 github.com/cespare/xxhash/v2 v2.3.0 // indirect 54 github.com/cloudflare/circl v1.6.0 // indirect 55 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 56 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 57 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 58 github.com/dlclark/regexp2 v1.11.5 // indirect 59 github.com/emirpasic/gods v1.18.1 // indirect 60 github.com/felixge/httpsnoop v1.0.4 // indirect 61 github.com/go-enry/go-oniguruma v1.2.1 // indirect ··· 95 github.com/lestrrat-go/option v1.0.1 // indirect 96 github.com/mattn/go-isatty v0.0.20 // indirect 97 github.com/minio/sha256-simd v1.0.1 // indirect 98 github.com/mr-tron/base58 v1.2.0 // indirect 99 github.com/multiformats/go-base32 v0.1.0 // indirect 100 github.com/multiformats/go-base36 v0.2.0 // indirect ··· 102 github.com/multiformats/go-multihash v0.2.3 // indirect 103 github.com/multiformats/go-varint v0.0.7 // indirect 104 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 105 github.com/opentracing/opentracing-go v1.2.0 // indirect 106 github.com/pjbgf/sha1cd v0.3.2 // indirect 107 github.com/pkg/errors v0.9.1 // indirect ··· 111 github.com/prometheus/client_model v0.6.2 // indirect 112 github.com/prometheus/common v0.63.0 // indirect 113 github.com/prometheus/procfs v0.16.1 // indirect 114 - github.com/redis/go-redis/v9 v9.3.0 // indirect 115 github.com/segmentio/asm v1.2.0 // indirect 116 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 117 github.com/spaolacci/murmur3 v1.1.0 // indirect ··· 125 go.opentelemetry.io/otel v1.36.0 // indirect 126 go.opentelemetry.io/otel/metric v1.36.0 // indirect 127 go.opentelemetry.io/otel/trace v1.36.0 // indirect 128 go.uber.org/atomic v1.11.0 // indirect 129 go.uber.org/multierr v1.11.0 // indirect 130 go.uber.org/zap v1.27.0 // indirect 131 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 132 - golang.org/x/sync v0.13.0 // indirect 133 golang.org/x/sys v0.33.0 // indirect 134 golang.org/x/time v0.8.0 // indirect 135 google.golang.org/protobuf v1.36.6 // indirect 136 gopkg.in/warnings.v0 v0.1.2 // indirect 137 lukechampine.com/blake3 v1.4.1 // indirect 138 ) 139
··· 14 github.com/casbin/casbin/v2 v2.103.0 15 github.com/cyphar/filepath-securejoin v0.4.1 16 github.com/dgraph-io/ristretto v0.2.0 17 + github.com/docker/docker v28.2.2+incompatible 18 github.com/dustin/go-humanize v1.0.1 19 github.com/gliderlabs/ssh v0.3.8 20 github.com/go-chi/chi/v5 v5.2.0 ··· 29 github.com/mattn/go-sqlite3 v1.14.24 30 github.com/microcosm-cc/bluemonday v1.0.27 31 github.com/posthog/posthog-go v1.5.5 32 + github.com/redis/go-redis/v9 v9.3.0 33 github.com/resend/resend-go/v2 v2.15.0 34 github.com/sethvargo/go-envconfig v1.1.0 35 github.com/stretchr/testify v1.10.0 ··· 37 github.com/whyrusleeping/cbor-gen v0.3.1 38 github.com/yuin/goldmark v1.4.13 39 golang.org/x/crypto v0.38.0 40 + golang.org/x/net v0.40.0 41 + golang.org/x/sync v0.14.0 42 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 43 gopkg.in/yaml.v3 v3.0.1 44 tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250526154904-3906c5336421 ··· 55 github.com/casbin/govaluate v1.3.0 // indirect 56 github.com/cespare/xxhash/v2 v2.3.0 // indirect 57 github.com/cloudflare/circl v1.6.0 // indirect 58 + github.com/containerd/errdefs v1.0.0 // indirect 59 + github.com/containerd/errdefs/pkg v0.3.0 // indirect 60 + github.com/containerd/log v0.1.0 // indirect 61 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 62 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 63 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 64 + github.com/distribution/reference v0.6.0 // indirect 65 github.com/dlclark/regexp2 v1.11.5 // indirect 66 + github.com/docker/go-connections v0.5.0 // indirect 67 + github.com/docker/go-units v0.5.0 // indirect 68 github.com/emirpasic/gods v1.18.1 // indirect 69 github.com/felixge/httpsnoop v1.0.4 // indirect 70 github.com/go-enry/go-oniguruma v1.2.1 // indirect ··· 104 github.com/lestrrat-go/option v1.0.1 // indirect 105 github.com/mattn/go-isatty v0.0.20 // indirect 106 github.com/minio/sha256-simd v1.0.1 // indirect 107 + github.com/moby/docker-image-spec v1.3.1 // indirect 108 + github.com/moby/sys/atomicwriter v0.1.0 // indirect 109 + github.com/moby/term v0.5.2 // indirect 110 + github.com/morikuni/aec v1.0.0 // indirect 111 github.com/mr-tron/base58 v1.2.0 // indirect 112 github.com/multiformats/go-base32 v0.1.0 // indirect 113 github.com/multiformats/go-base36 v0.2.0 // indirect ··· 115 github.com/multiformats/go-multihash v0.2.3 // indirect 116 github.com/multiformats/go-varint v0.0.7 // indirect 117 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 118 + github.com/opencontainers/go-digest v1.0.0 // indirect 119 + github.com/opencontainers/image-spec v1.1.1 // indirect 120 github.com/opentracing/opentracing-go v1.2.0 // indirect 121 github.com/pjbgf/sha1cd v0.3.2 // indirect 122 github.com/pkg/errors v0.9.1 // indirect ··· 126 github.com/prometheus/client_model v0.6.2 // indirect 127 github.com/prometheus/common v0.63.0 // indirect 128 github.com/prometheus/procfs v0.16.1 // indirect 129 github.com/segmentio/asm v1.2.0 // indirect 130 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 131 github.com/spaolacci/murmur3 v1.1.0 // indirect ··· 139 go.opentelemetry.io/otel v1.36.0 // indirect 140 go.opentelemetry.io/otel/metric v1.36.0 // indirect 141 go.opentelemetry.io/otel/trace v1.36.0 // indirect 142 + go.opentelemetry.io/proto/otlp v1.6.0 // indirect 143 go.uber.org/atomic v1.11.0 // indirect 144 go.uber.org/multierr v1.11.0 // indirect 145 go.uber.org/zap v1.27.0 // indirect 146 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 147 golang.org/x/sys v0.33.0 // indirect 148 golang.org/x/time v0.8.0 // indirect 149 + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect 150 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 151 + google.golang.org/grpc v1.72.1 // indirect 152 google.golang.org/protobuf v1.36.6 // indirect 153 gopkg.in/warnings.v0 v0.1.2 // indirect 154 + gotest.tools/v3 v3.5.2 // indirect 155 lukechampine.com/blake3 v1.4.1 // indirect 156 ) 157
+54 -4
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= 3 github.com/Blank-Xu/sql-adapter v1.1.1 h1:+g7QXU9sl/qT6Po97teMpf3GjAO0X9aFaqgSePXvYko= 4 github.com/Blank-Xu/sql-adapter v1.1.1/go.mod h1:o2g8EZhZ3TudnYEGDkoU+3jCTCgDgx1o/Ig5ajKkaLY= 5 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= ··· 38 github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 39 github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= 40 github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 41 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 42 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 43 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= ··· 47 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 48 github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= 49 github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 50 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 51 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 52 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= ··· 63 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 64 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 65 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 66 github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= 67 github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 68 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 69 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 70 github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= ··· 148 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 149 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 150 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 151 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 152 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 153 github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= ··· 242 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 243 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 244 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 245 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 246 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 247 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= ··· 287 github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= 288 github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 289 github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 290 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 291 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 292 github.com/oppiliappan/chroma/v2 v2.19.0 h1:PN7/pb+6JRKCva30NPTtRJMlrOyzgpPpIroNzy4ekHU= ··· 330 github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= 331 github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= 332 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 333 github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 334 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 335 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= ··· 378 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 379 go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 380 go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 381 go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= 382 go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= 383 go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= ··· 386 go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= 387 go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 388 go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 389 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 390 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 391 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= ··· 435 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 436 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 437 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 438 - golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 439 - golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 440 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 441 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 442 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ··· 444 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 445 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 446 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 447 - golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 448 - golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 449 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 450 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 451 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 512 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 513 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= 514 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 515 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 516 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 517 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= ··· 542 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 543 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 544 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 545 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 546 lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= 547 lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
··· 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= 3 + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 4 + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 github.com/Blank-Xu/sql-adapter v1.1.1 h1:+g7QXU9sl/qT6Po97teMpf3GjAO0X9aFaqgSePXvYko= 6 github.com/Blank-Xu/sql-adapter v1.1.1/go.mod h1:o2g8EZhZ3TudnYEGDkoU+3jCTCgDgx1o/Ig5ajKkaLY= 7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= ··· 40 github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 41 github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= 42 github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= 43 + github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 44 + github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 45 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 46 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 47 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= ··· 51 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 52 github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= 53 github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 54 + github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 55 + github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 56 + github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 57 + github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 58 + github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 59 + github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 60 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 61 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 62 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= ··· 73 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 74 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 75 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 76 + github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 77 + github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 78 github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= 79 github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 80 + github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= 81 + github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 82 + github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 83 + github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 84 + github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 85 + github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 86 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 87 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 88 github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= ··· 166 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 167 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 168 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 169 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= 170 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 171 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 172 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 173 github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= ··· 262 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 263 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 264 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 265 + github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 266 + github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 267 + github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 268 + github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 269 + github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 270 + github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 271 + github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 272 + github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 273 + github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 274 + github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 275 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 276 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 277 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= ··· 317 github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= 318 github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 319 github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 320 + github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 321 + github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 322 + github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 323 + github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 324 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 325 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 326 github.com/oppiliappan/chroma/v2 v2.19.0 h1:PN7/pb+6JRKCva30NPTtRJMlrOyzgpPpIroNzy4ekHU= ··· 364 github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= 365 github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= 366 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 367 + github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 368 + github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 369 github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 370 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 371 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= ··· 414 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 415 go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 416 go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 417 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= 418 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= 419 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= 420 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= 421 go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= 422 go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= 423 go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= ··· 426 go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= 427 go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 428 go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 429 + go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= 430 + go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= 431 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 432 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 433 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= ··· 477 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 478 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 479 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 480 + golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 481 + golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 482 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 483 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 484 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ··· 486 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 487 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 488 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 489 + golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 490 + golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 491 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 492 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 493 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 554 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 555 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= 556 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 557 + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= 558 + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= 559 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= 560 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 561 + google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= 562 + google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 563 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 564 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 565 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= ··· 590 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 591 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 592 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 593 + gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 594 + gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 595 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 596 lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= 597 lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
+263
spindle/engine/engine.go
···
··· 1 + package engine 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "io" 7 + "log/slog" 8 + "os" 9 + "path" 10 + "sync" 11 + 12 + "github.com/docker/docker/api/types/container" 13 + "github.com/docker/docker/api/types/image" 14 + "github.com/docker/docker/api/types/mount" 15 + "github.com/docker/docker/api/types/network" 16 + "github.com/docker/docker/api/types/volume" 17 + "github.com/docker/docker/client" 18 + "github.com/docker/docker/pkg/stdcopy" 19 + "golang.org/x/sync/errgroup" 20 + "tangled.sh/tangled.sh/core/api/tangled" 21 + "tangled.sh/tangled.sh/core/log" 22 + "tangled.sh/tangled.sh/core/spindle/db" 23 + ) 24 + 25 + const ( 26 + workspaceDir = "/tangled/workspace" 27 + ) 28 + 29 + type Engine struct { 30 + docker client.APIClient 31 + l *slog.Logger 32 + db *db.DB 33 + } 34 + 35 + func New(ctx context.Context, db *db.DB) (*Engine, error) { 36 + dcli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 37 + if err != nil { 38 + return nil, err 39 + } 40 + 41 + l := log.FromContext(ctx).With("component", "spindle") 42 + 43 + return &Engine{docker: dcli, l: l, db: db}, nil 44 + } 45 + 46 + // SetupPipeline sets up a new network for the pipeline, and possibly volumes etc. 47 + // in the future. In here also goes other setup steps. 48 + func (e *Engine) SetupPipeline(ctx context.Context, pipeline *tangled.Pipeline, id string) error { 49 + e.l.Info("setting up pipeline", "pipeline", id) 50 + 51 + _, err := e.docker.VolumeCreate(ctx, volume.CreateOptions{ 52 + Name: workspaceVolume(id), 53 + Driver: "local", 54 + }) 55 + if err != nil { 56 + return err 57 + } 58 + 59 + _, err = e.docker.VolumeCreate(ctx, volume.CreateOptions{ 60 + Name: nixVolume(id), 61 + Driver: "local", 62 + }) 63 + if err != nil { 64 + return err 65 + } 66 + 67 + _, err = e.docker.NetworkCreate(ctx, pipelineName(id), network.CreateOptions{ 68 + Driver: "bridge", 69 + }) 70 + if err != nil { 71 + return err 72 + } 73 + 74 + err = e.db.CreatePipeline(id) 75 + return err 76 + } 77 + 78 + func (e *Engine) StartWorkflows(ctx context.Context, pipeline *tangled.Pipeline, id string) error { 79 + e.l.Info("starting all workflows in parallel", "pipeline", id) 80 + 81 + err := e.db.MarkPipelineRunning(id) 82 + if err != nil { 83 + return err 84 + } 85 + 86 + g := errgroup.Group{} 87 + for _, w := range pipeline.Workflows { 88 + g.Go(func() error { 89 + // TODO: actual checks for image/registry etc. 90 + var deps string 91 + for _, d := range w.Dependencies { 92 + if d.Registry == "nixpkgs" { 93 + deps = path.Join(d.Packages...) 94 + } 95 + } 96 + 97 + // load defaults from somewhere else 98 + deps = path.Join(deps, "bash", "git", "coreutils", "nix") 99 + 100 + cimg := path.Join("nixery.dev", deps) 101 + reader, err := e.docker.ImagePull(ctx, cimg, image.PullOptions{}) 102 + if err != nil { 103 + e.l.Error("pipeline failed!", "id", id, "error", err.Error()) 104 + err := e.db.MarkPipelineFailed(id, -1, err.Error()) 105 + if err != nil { 106 + return err 107 + } 108 + return fmt.Errorf("pulling image: %w", err) 109 + } 110 + defer reader.Close() 111 + io.Copy(os.Stdout, reader) 112 + 113 + err = e.StartSteps(ctx, w.Steps, id, cimg) 114 + if err != nil { 115 + e.l.Error("pipeline failed!", "id", id, "error", err.Error()) 116 + return e.db.MarkPipelineFailed(id, -1, err.Error()) 117 + } 118 + 119 + return nil 120 + }) 121 + } 122 + 123 + err = g.Wait() 124 + if err != nil { 125 + e.l.Error("pipeline failed!", "id", id, "error", err.Error()) 126 + return e.db.MarkPipelineFailed(id, -1, err.Error()) 127 + } 128 + 129 + e.l.Info("pipeline success!", "id", id) 130 + return e.db.MarkPipelineSuccess(id) 131 + } 132 + 133 + // StartSteps starts all steps sequentially with the same base image. 134 + // ONLY marks pipeline as failed if container's exit code is non-zero. 135 + // All other errors are bubbled up. 136 + func (e *Engine) StartSteps(ctx context.Context, steps []*tangled.Pipeline_Step, id, image string) error { 137 + for _, step := range steps { 138 + hostConfig := hostConfig(id) 139 + resp, err := e.docker.ContainerCreate(ctx, &container.Config{ 140 + Image: image, 141 + Cmd: []string{"bash", "-c", step.Command}, 142 + WorkingDir: workspaceDir, 143 + Tty: false, 144 + Hostname: "spindle", 145 + Env: []string{"HOME=" + workspaceDir}, 146 + }, hostConfig, nil, nil, "") 147 + if err != nil { 148 + return fmt.Errorf("creating container: %w", err) 149 + } 150 + 151 + err = e.docker.NetworkConnect(ctx, pipelineName(id), resp.ID, nil) 152 + if err != nil { 153 + return fmt.Errorf("connecting network: %w", err) 154 + } 155 + 156 + err = e.docker.ContainerStart(ctx, resp.ID, container.StartOptions{}) 157 + if err != nil { 158 + return err 159 + } 160 + e.l.Info("started container", "name", resp.ID, "step", step.Name) 161 + 162 + wg := sync.WaitGroup{} 163 + 164 + wg.Add(1) 165 + go func() { 166 + defer wg.Done() 167 + err := e.TailStep(ctx, resp.ID) 168 + if err != nil { 169 + e.l.Error("failed to tail container", "container", resp.ID) 170 + return 171 + } 172 + }() 173 + 174 + // wait until all logs are piped 175 + wg.Wait() 176 + 177 + state, err := e.WaitStep(ctx, resp.ID) 178 + if err != nil { 179 + return err 180 + } 181 + 182 + if state.ExitCode != 0 { 183 + e.l.Error("pipeline failed!", "id", id, "error", state.Error, "exit_code", state.ExitCode) 184 + return e.db.MarkPipelineFailed(id, state.ExitCode, state.Error) 185 + } 186 + } 187 + 188 + return nil 189 + 190 + } 191 + 192 + func (e *Engine) WaitStep(ctx context.Context, containerID string) (*container.State, error) { 193 + wait, errCh := e.docker.ContainerWait(ctx, containerID, container.WaitConditionNotRunning) 194 + select { 195 + case err := <-errCh: 196 + if err != nil { 197 + return nil, err 198 + } 199 + case <-wait: 200 + } 201 + 202 + e.l.Info("waited for container", "name", containerID) 203 + 204 + info, err := e.docker.ContainerInspect(ctx, containerID) 205 + if err != nil { 206 + return nil, err 207 + } 208 + 209 + return info.State, nil 210 + } 211 + 212 + func (e *Engine) TailStep(ctx context.Context, containerID string) error { 213 + logs, err := e.docker.ContainerLogs(ctx, containerID, container.LogsOptions{ 214 + Follow: true, 215 + ShowStdout: true, 216 + ShowStderr: true, 217 + Details: false, 218 + Timestamps: false, 219 + }) 220 + if err != nil { 221 + return err 222 + } 223 + 224 + go func() { 225 + _, _ = stdcopy.StdCopy(os.Stdout, os.Stdout, logs) 226 + _ = logs.Close() 227 + }() 228 + return nil 229 + } 230 + 231 + func workspaceVolume(id string) string { 232 + return "workspace-" + id 233 + } 234 + 235 + func nixVolume(id string) string { 236 + return "nix-" + id 237 + } 238 + 239 + func pipelineName(id string) string { 240 + return "pipeline-" + id 241 + } 242 + 243 + func hostConfig(id string) *container.HostConfig { 244 + hostConfig := &container.HostConfig{ 245 + Mounts: []mount.Mount{ 246 + { 247 + Type: mount.TypeVolume, 248 + Source: workspaceVolume(id), 249 + Target: workspaceDir, 250 + }, 251 + { 252 + Type: mount.TypeVolume, 253 + Source: nixVolume(id), 254 + Target: "/nix", 255 + }, 256 + }, 257 + ReadonlyRootfs: true, 258 + CapDrop: []string{"ALL"}, 259 + SecurityOpt: []string{"no-new-privileges"}, 260 + } 261 + 262 + return hostConfig 263 + }
+54
spindle/exec.go
···
··· 1 + package spindle 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + 8 + "tangled.sh/tangled.sh/core/api/tangled" 9 + ) 10 + 11 + func (s *Spindle) exec(ctx context.Context, src string, msg []byte) error { 12 + pipeline := tangled.Pipeline{} 13 + data := map[string]any{} 14 + err := json.Unmarshal(msg, &data) 15 + if err != nil { 16 + fmt.Println("error unmarshalling", err) 17 + return err 18 + } 19 + 20 + if data["nsid"] == tangled.PipelineNSID { 21 + event, ok := data["event"] 22 + if !ok { 23 + s.l.Error("no event in message") 24 + return nil 25 + } 26 + 27 + rawEvent, err := json.Marshal(event) 28 + if err != nil { 29 + return err 30 + } 31 + 32 + err = json.Unmarshal(rawEvent, &pipeline) 33 + if err != nil { 34 + return err 35 + } 36 + 37 + rkey, ok := data["rkey"].(string) 38 + if !ok { 39 + s.l.Error("no rkey in message") 40 + return nil 41 + } 42 + 43 + err = s.eng.SetupPipeline(ctx, &pipeline, rkey) 44 + if err != nil { 45 + return err 46 + } 47 + err = s.eng.StartWorkflows(ctx, &pipeline, rkey) 48 + if err != nil { 49 + return err 50 + } 51 + } 52 + 53 + return nil 54 + }
+30 -10
spindle/server.go
··· 8 "golang.org/x/net/context" 9 "tangled.sh/tangled.sh/core/api/tangled" 10 "tangled.sh/tangled.sh/core/jetstream" 11 "tangled.sh/tangled.sh/core/knotserver/notifier" 12 "tangled.sh/tangled.sh/core/log" 13 "tangled.sh/tangled.sh/core/rbac" 14 "tangled.sh/tangled.sh/core/spindle/config" 15 "tangled.sh/tangled.sh/core/spindle/db" 16 ) 17 18 type Spindle struct { 19 - jc *jetstream.JetstreamClient 20 - db *db.DB 21 - e *rbac.Enforcer 22 - l *slog.Logger 23 - n *notifier.Notifier 24 } 25 26 func Run(ctx context.Context) error { ··· 49 50 n := notifier.New() 51 52 spindle := Spindle{ 53 - jc: jc, 54 - e: e, 55 - db: d, 56 - l: logger, 57 - n: &n, 58 } 59 60 logger.Info("starting spindle server", "address", cfg.Server.ListenAddr) 61 logger.Error("server error", "error", http.ListenAndServe(cfg.Server.ListenAddr, spindle.Router()))
··· 8 "golang.org/x/net/context" 9 "tangled.sh/tangled.sh/core/api/tangled" 10 "tangled.sh/tangled.sh/core/jetstream" 11 + "tangled.sh/tangled.sh/core/knotclient" 12 "tangled.sh/tangled.sh/core/knotserver/notifier" 13 "tangled.sh/tangled.sh/core/log" 14 "tangled.sh/tangled.sh/core/rbac" 15 "tangled.sh/tangled.sh/core/spindle/config" 16 "tangled.sh/tangled.sh/core/spindle/db" 17 + "tangled.sh/tangled.sh/core/spindle/engine" 18 ) 19 20 type Spindle struct { 21 + jc *jetstream.JetstreamClient 22 + db *db.DB 23 + e *rbac.Enforcer 24 + l *slog.Logger 25 + n *notifier.Notifier 26 + eng *engine.Engine 27 } 28 29 func Run(ctx context.Context) error { ··· 52 53 n := notifier.New() 54 55 + eng, err := engine.New(ctx, d) 56 + if err != nil { 57 + return err 58 + } 59 + 60 spindle := Spindle{ 61 + jc: jc, 62 + e: e, 63 + db: d, 64 + l: logger, 65 + n: &n, 66 + eng: eng, 67 } 68 + 69 + go func() { 70 + logger.Info("starting event consumer") 71 + ec := knotclient.NewEventConsumer(knotclient.ConsumerConfig{ 72 + Sources: []string{"ws://localhost:5555/events"}, 73 + Logger: logger, 74 + ProcessFunc: spindle.exec, 75 + }) 76 + 77 + ec.Start(ctx) 78 + }() 79 80 logger.Info("starting spindle server", "address", cfg.Server.ListenAddr) 81 logger.Error("server error", "error", http.ListenAndServe(cfg.Server.ListenAddr, spindle.Router()))