diff --git a/.golangci.yml b/.golangci.yml index 1529035..cb221b7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,7 @@ run: # https://golangci-lint.run/usage/linters/ linters: enable: + - depguard - exhaustive - exportloopref - gci @@ -53,6 +54,12 @@ linters: - whitespace linters-settings: + depguard: + rules: + main: + deny: + - pkg: github.com/docker/docker + msg: https://github.com/ustclug/Yuki/issues/44 gci: sections: - standard # Standard section: captures all standard packages. diff --git a/README.md b/README.md index 83f0ec7..c396118 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ README ======= -[![Build Status](https://github.com/ustclug/Yuki/workflows/pr-presubmit-checks/badge.svg)](https://github.com/ustclug/Yuki/actions) +[![Presubmit Checks](https://github.com/ustclug/Yuki/actions/workflows/pr-presubmit-checks.yml/badge.svg)](https://github.com/ustclug/Yuki/actions/workflows/pr-presubmit-checks.yml) [![Go Report](https://goreportcard.com/badge/github.com/ustclug/Yuki)](https://goreportcard.com/report/github.com/ustclug/Yuki) - [Requirements](#requirements) @@ -54,7 +54,7 @@ Setup repository: # The repository directory must be created in advance mkdir /tmp/repos/docker-ce -# Sync docker-ce repository from mirrors.bfsu.edu.cn +# Sync docker-ce repository from rsync.mirrors.ustc.edu.cn cat < /tmp/repo-configs/docker-ce.yaml name: docker-ce # every 1 hour @@ -63,7 +63,7 @@ storageDir: /tmp/repos/docker-ce image: ustcmirror/rsync:latest logRotCycle: 2 envs: - RSYNC_HOST: mirrors.bfsu.edu.cn + RSYNC_HOST: rsync.mirrors.ustc.edu.cn RSYNC_PATH: docker-ce/ RSYNC_EXCLUDE: --exclude=.~tmp~/ RSYNC_EXTRA: --size-only diff --git a/go.mod b/go.mod index c9cd7c8..8eecd0e 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/ustclug/Yuki go 1.21 require ( - // https://github.com/moby/moby/issues/46621 - github.com/docker/docker v25.0.0-beta.3+incompatible + github.com/cpuguy83/go-docker v0.3.0 github.com/docker/go-units v0.5.0 github.com/go-playground/validator/v10 v10.16.0 github.com/go-resty/resty/v2 v2.11.0 @@ -15,7 +14,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 - golang.org/x/sync v0.5.0 + golang.org/x/sync v0.6.0 gorm.io/driver/sqlite v1.5.4 gorm.io/gorm v1.25.5 sigs.k8s.io/yaml v1.4.0 @@ -23,19 +22,13 @@ require ( require ( github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-logr/logr v1.3.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -47,12 +40,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -62,18 +50,12 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/sdk v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.1 // indirect diff --git a/go.sum b/go.sum index 16c7897..61d97b4 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,20 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-docker v0.3.0 h1:O88rocdycYvY+pUYYp0i1rRDANXHurNir3VE0F/PH3g= +github.com/cpuguy83/go-docker v0.3.0/go.mod h1:R2HgB/m54W+2dhYc70Xm78yS6o775SfN09bGIPSfQZQ= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.0-beta.3+incompatible h1:aoDCVh2PkQ4M/XfvKIJ6MoTPR+RFvwMVfkLqFZKCXS4= -github.com/docker/docker v25.0.0-beta.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -42,17 +25,11 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -61,8 +38,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -84,20 +59,12 @@ github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbW github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -110,8 +77,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -138,46 +103,21 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -186,15 +126,11 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -204,8 +140,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -224,25 +160,11 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/docker/cli.go b/pkg/docker/cli.go index c9ed195..de38525 100644 --- a/pkg/docker/cli.go +++ b/pkg/docker/cli.go @@ -3,159 +3,227 @@ package docker import ( "context" "fmt" - "io" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/docker/errdefs" + "github.com/cpuguy83/go-docker" + "github.com/cpuguy83/go-docker/container" + "github.com/cpuguy83/go-docker/container/containerapi" + "github.com/cpuguy83/go-docker/container/containerapi/mount" + "github.com/cpuguy83/go-docker/errdefs" + "github.com/cpuguy83/go-docker/image" + "github.com/cpuguy83/go-docker/image/imageapi" + "github.com/cpuguy83/go-docker/transport" + "golang.org/x/sync/errgroup" "github.com/ustclug/Yuki/pkg/api" ) +type RunContainerConfig struct { + // ContainerConfig + Labels map[string]string + Env []string + Image string + Name string + + // HostConfig + SecurityOpt []string + Binds []string + + // NetworkingConfig + Network string +} + +type ContainerSummary struct { + ID string + Labels map[string]string +} + type Client interface { // RunContainer creates and starts a container with the given config. // The specified image will be pulled automatically if it does not exist. - RunContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (id string, err error) - PullImage(ctx context.Context, image string) error + RunContainer(ctx context.Context, config RunContainerConfig) (id string, err error) WaitContainerWithTimeout(id string, timeout time.Duration) (int, error) RemoveContainerWithTimeout(id string, timeout time.Duration) error - ListContainersWithTimeout(running bool, timeout time.Duration) ([]types.Container, error) - RemoveDanglingImages() error + ListContainersWithTimeout(running bool, timeout time.Duration) ([]ContainerSummary, error) + UpgradeImages(refs []string) error } func NewClient(endpoint string) (Client, error) { - d, err := client.NewClientWithOpts( - client.WithHost(endpoint), - client.WithAPIVersionNegotiation(), - ) + tr, err := transport.FromConnectionString(endpoint) if err != nil { - return nil, fmt.Errorf("connect to the Docker daemon at %s", endpoint) + return nil, err } - return &clientImpl{d}, nil + return &clientImpl{ + client: docker.NewClient(docker.WithTransport(tr)), + }, nil } -type clientImpl struct { - client *client.Client +func getTimeoutContext(timeout time.Duration) (context.Context, context.CancelFunc) { + if timeout == 0 { + return context.WithCancel(context.Background()) + } + return context.WithTimeout(context.Background(), timeout) } -func (c *clientImpl) listImages(timeout time.Duration) ([]image.Summary, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return c.client.ImageList(ctx, types.ImageListOptions{ - All: true, - Filters: filters.NewArgs( - filters.Arg("label", api.LabelImages), - filters.Arg("dangling", "true"), - ), - }) +type clientImpl struct { + client *docker.Client } -func (c *clientImpl) removeImage(id string, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - _, err := c.client.ImageRemove(ctx, id, types.ImageRemoveOptions{ - PruneChildren: true, - }) - return err -} +func (c *clientImpl) RunContainer(ctx context.Context, config RunContainerConfig) (id string, err error) { + setCfg := func(cfg *container.CreateConfig) { + cfg.Name = config.Name + cfg.Spec.Config = containerapi.Config{ + Image: config.Image, + OpenStdin: true, + Env: config.Env, + Labels: config.Labels, + } -func (c *clientImpl) RemoveDanglingImages() error { - images, err := c.listImages(time.Second * 5) - if err != nil { - return fmt.Errorf("list images: %w", err) + cfg.Spec.HostConfig = containerapi.HostConfig{ + Binds: config.Binds, + SecurityOpt: config.SecurityOpt, + } + cfg.Spec.HostConfig.Mounts = []mount.Mount{ + { + Type: mount.TypeTmpfs, + Target: "/tmp", + }, + } + + cfg.Spec.NetworkConfig.EndpointsConfig = make(map[string]*containerapi.EndpointSettings) + switch config.Network { + case "host", "": + cfg.Spec.HostConfig.NetworkMode = "host" + default: + cfg.Spec.NetworkConfig.EndpointsConfig[config.Network] = &containerapi.EndpointSettings{} + } } - for _, image := range images { - err = c.removeImage(image.ID, time.Second*20) + ct, err := c.client.ContainerService().Create(ctx, "", setCfg) + if err != nil { + if errdefs.IsNotFound(err) { + err = c.pullImage(ctx, config.Image) + if err != nil { + return "", fmt.Errorf("pull image: %w", err) + } + ct, err = c.client.ContainerService().Create(ctx, "", setCfg) + } if err != nil { - return fmt.Errorf("remove image: %q: %w", image.ID, err) + return "", fmt.Errorf("create container: %w", err) } } - return nil + err = ct.Start(ctx) + if err != nil { + return "", fmt.Errorf("start container: %w", err) + } + return ct.ID(), nil } -func (c *clientImpl) ListContainersWithTimeout(running bool, timeout time.Duration) ([]types.Container, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) +func (c *clientImpl) ListContainersWithTimeout(running bool, timeout time.Duration) ([]ContainerSummary, error) { + ctx, cancel := getTimeoutContext(timeout) defer cancel() - args := filters.NewArgs(filters.Arg("label", api.LabelRepoName)) var statuses []string if running { statuses = append(statuses, "running") } else { statuses = append(statuses, "exited", "created", "dead") } - for _, status := range statuses { - args.Add("status", status) - } - return c.client.ContainerList(ctx, container.ListOptions{ - All: true, - Filters: args, + cts, err := c.client.ContainerService().List(ctx, func(config *container.ListConfig) { + config.Filter = container.ListFilter{ + Status: statuses, + Label: []string{api.LabelRepoName}, + } }) + if err != nil { + return nil, err + } + + result := make([]ContainerSummary, len(cts)) + for i, ct := range cts { + result[i] = ContainerSummary{ + ID: ct.ID, + Labels: ct.Labels, + } + } + return result, nil } func (c *clientImpl) RemoveContainerWithTimeout(id string, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := getTimeoutContext(timeout) defer cancel() - return c.client.ContainerRemove(ctx, id, container.RemoveOptions{ - RemoveVolumes: true, - Force: true, + return c.client.ContainerService().Remove(ctx, id, func(cfg *container.RemoveConfig) { + cfg.RemoveVolumes = true + cfg.Force = true }) } func (c *clientImpl) WaitContainerWithTimeout(id string, timeout time.Duration) (int, error) { - ctx := context.Background() - if timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, timeout) - defer cancel() - } - stream, errCh := c.client.ContainerWait(ctx, id, container.WaitConditionNotRunning) - select { - case err := <-errCh: - return -1, err - case resp := <-stream: - return int(resp.StatusCode), nil + ctx, cancel := getTimeoutContext(timeout) + defer cancel() + ct := c.client.ContainerService().NewContainer(ctx, id) + status, err := ct.Wait(ctx, container.WithWaitCondition(container.WaitConditionNotRunning)) + if err != nil { + return 0, fmt.Errorf("wait container: %w", err) } + return status.ExitCode() } -func (c *clientImpl) PullImage(ctx context.Context, image string) error { - stream, err := c.client.ImagePull(ctx, image, types.ImagePullOptions{}) - if err != nil { - return fmt.Errorf("invoke ImagePull: %w", err) - } - defer stream.Close() - _, err = io.Copy(io.Discard, stream) +func (c *clientImpl) pullImage(ctx context.Context, ref string) error { + remote, err := image.ParseRef(ref) if err != nil { - return fmt.Errorf("read stream: %w", err) + return fmt.Errorf("invalid image ref: %w", err) } + return c.client.ImageService().Pull(ctx, remote) +} + +func (c *clientImpl) removeImage(id string, timeout time.Duration) error { + ctx, cancel := getTimeoutContext(timeout) + defer cancel() + _, err := c.client.ImageService().Remove(ctx, id) return err } -func (c *clientImpl) RunContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (string, error) { - ct, err := c.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) +func (c *clientImpl) removeDanglingImages() error { + images, err := c.listDanglingImages(time.Second * 5) if err != nil { - if errdefs.IsNotFound(err) { - err = c.PullImage(ctx, config.Image) - if err != nil { - return "", fmt.Errorf("pull image: %w", err) - } - ct, err = c.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName) - } + return fmt.Errorf("list images: %w", err) + } + for _, img := range images { + err = c.removeImage(img.ID, time.Second*20) if err != nil { - return "", fmt.Errorf("create container: %w", err) + return fmt.Errorf("remove image: %q: %w", img.ID, err) } } + return nil +} - err = c.client.ContainerStart(ctx, ct.ID, container.StartOptions{}) +func (c *clientImpl) listDanglingImages(timeout time.Duration) ([]imageapi.Image, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return c.client.ImageService().List(ctx, func(cfg *image.ListConfig) { + cfg.Filter = image.ListFilter{ + Label: []string{api.LabelImages}, + Dangling: []string{"true"}, + } + }) +} + +func (c *clientImpl) UpgradeImages(refs []string) error { + eg, ctx := errgroup.WithContext(context.Background()) + eg.SetLimit(5) + for _, ref := range refs { + img := ref + eg.Go(func() error { + pullCtx, cancel := context.WithTimeout(ctx, time.Minute*10) + defer cancel() + return c.pullImage(pullCtx, img) + }) + } + err := eg.Wait() if err != nil { - return "", fmt.Errorf("start container: %w", err) + return err } - return ct.ID, nil + return c.removeDanglingImages() } diff --git a/pkg/docker/fake/cli.go b/pkg/docker/fake/cli.go index 0ce4123..31e5d81 100644 --- a/pkg/docker/fake/cli.go +++ b/pkg/docker/fake/cli.go @@ -2,43 +2,32 @@ package fake import ( "context" - "errors" "fmt" "sync" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/errdefs" + "github.com/cpuguy83/go-docker/errdefs" "github.com/ustclug/Yuki/pkg/docker" ) type Client struct { mu sync.Mutex - containers map[string]types.Container + containers map[string]docker.ContainerSummary } -func (f *Client) RunContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (id string, err error) { +func (f *Client) RunContainer(ctx context.Context, config docker.RunContainerConfig) (id string, err error) { f.mu.Lock() defer f.mu.Unlock() - _, ok := f.containers[containerName] + _, ok := f.containers[config.Name] if ok { - return "", errdefs.Conflict(errors.New("container already exists")) + return "", errdefs.Conflict("container already exists") } - f.containers[containerName] = types.Container{ - ID: containerName, - Names: []string{containerName}, + f.containers[config.Name] = docker.ContainerSummary{ + ID: config.Name, Labels: config.Labels, - Status: "running", } - return containerName, nil -} - -func (f *Client) PullImage(ctx context.Context, image string) error { - // TODO implement me - panic("implement me") + return config.Name, nil } func (f *Client) WaitContainerWithTimeout(id string, timeout time.Duration) (int, error) { @@ -55,7 +44,6 @@ func (f *Client) WaitContainerWithTimeout(id string, timeout time.Duration) (int return 0, context.DeadlineExceeded } time.Sleep(delay) - ct.Status = "exited" f.containers[id] = ct return 0, nil } @@ -67,22 +55,22 @@ func (f *Client) RemoveContainerWithTimeout(id string, timeout time.Duration) er return nil } -func (f *Client) ListContainersWithTimeout(running bool, timeout time.Duration) ([]types.Container, error) { +func (f *Client) ListContainersWithTimeout(running bool, timeout time.Duration) ([]docker.ContainerSummary, error) { f.mu.Lock() defer f.mu.Unlock() - l := make([]types.Container, 0, len(f.containers)) + l := make([]docker.ContainerSummary, 0, len(f.containers)) for _, ct := range f.containers { l = append(l, ct) } return l, nil } -func (f *Client) RemoveDanglingImages() error { - return nil +func (f *Client) UpgradeImages(refs []string) error { + panic("not implemented") } func NewClient() docker.Client { return &Client{ - containers: make(map[string]types.Container), + containers: make(map[string]docker.ContainerSummary), } } diff --git a/pkg/server/main.go b/pkg/server/main.go index 57c733d..5bfa490 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -8,6 +8,7 @@ import ( "log/slog" "net/http" "os" + "strings" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" @@ -53,10 +54,13 @@ func New(configPath string) (*Server, error) { } func NewWithConfig(cfg Config) (*Server, error) { - // TODO: enforce shared cache mode? - db, err := gorm.Open(sqlite.Open(cfg.DbURL), &gorm.Config{ - QueryFields: true, - SkipDefaultTransaction: true, + dbURL := cfg.DbURL + if !strings.ContainsRune(dbURL, '?') { + // enable WAL mode by default to improve performance + dbURL += "?_journal_mode=WAL" + } + db, err := gorm.Open(sqlite.Open(dbURL), &gorm.Config{ + QueryFields: true, }) if err != nil { return nil, fmt.Errorf("open db: %w", err) diff --git a/pkg/server/main_test.go b/pkg/server/main_test.go index 11a575f..8db51a1 100644 --- a/pkg/server/main_test.go +++ b/pkg/server/main_test.go @@ -56,16 +56,11 @@ func NewTestEnv(t *testing.T) *TestEnv { _ = dbFile.Close() _ = os.Remove(dbFile.Name()) }) - db, err := gorm.Open(sqlite.Open(dbFile.Name()), &gorm.Config{ - QueryFields: true, - SkipDefaultTransaction: true, + // Switch to WAL mode to avoid "database is locked" error. + db, err := gorm.Open(sqlite.Open(dbFile.Name()+"?_journal_mode=WAL"), &gorm.Config{ + QueryFields: true, }) require.NoError(t, err) - sqlDB, err := db.DB() - require.NoError(t, err) - // To resolve the "database is locked" error. - // See also https://github.com/mattn/go-sqlite3/issues/209 - sqlDB.SetMaxOpenConns(1) require.NoError(t, model.AutoMigrate(db)) s := &Server{ diff --git a/pkg/server/repo_handlers.go b/pkg/server/repo_handlers.go index 6614c6d..4a7efba 100644 --- a/pkg/server/repo_handlers.go +++ b/pkg/server/repo_handlers.go @@ -10,7 +10,8 @@ import ( "strings" "time" - "github.com/docker/docker/errdefs" + "github.com/cpuguy83/go-docker/errdefs" + "github.com/cpuguy83/go-docker/image" "github.com/labstack/echo/v4" "github.com/robfig/cron/v3" "gorm.io/gorm/clause" @@ -118,10 +119,16 @@ func (s *Server) loadRepo(c echo.Context, logger *slog.Logger, dirs []string, fi } } - if err := s.e.Validator.Validate(&repo); err != nil { + err := s.e.Validator.Validate(&repo) + if err != nil { return nil, newHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid config: %q: %v", file, err)) } + _, err = image.ParseRef(repo.Image) + if err != nil { + return nil, newHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid image: %q: %v", repo.Image, err)) + } + schedule, err := cron.ParseStandard(repo.Cron) if err != nil { return nil, newHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid cron: %q: %v", repo.Cron, err)) @@ -253,9 +260,7 @@ func (s *Server) handlerSyncRepo(c echo.Context) error { if errors.Is(err, errNotFound) { return newHTTPError(http.StatusNotFound, "Repo not found") } - // https://github.com/moby/moby/issues/47018 - var dkErr errdefs.ErrConflict - if errors.As(err, &dkErr) { + if errdefs.IsConflict(err) { return newHTTPError(http.StatusConflict, "Repo is syncing") } const msg = "Fail to sync Repo" diff --git a/pkg/server/utils.go b/pkg/server/utils.go index cf641e2..c1b54f6 100644 --- a/pkg/server/utils.go +++ b/pkg/server/utils.go @@ -13,17 +13,14 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/errdefs" + "github.com/cpuguy83/go-docker/errdefs" "github.com/labstack/echo/v4" "github.com/robfig/cron/v3" - "golang.org/x/sync/errgroup" "gorm.io/gorm" "gorm.io/gorm/clause" "github.com/ustclug/Yuki/pkg/api" + "github.com/ustclug/Yuki/pkg/docker" "github.com/ustclug/Yuki/pkg/model" ) @@ -273,27 +270,9 @@ func (s *Server) upgradeImages() { logger.Error("Fail to query images", slogErrAttr(err)) return } - eg, egCtx := errgroup.WithContext(context.Background()) - eg.SetLimit(5) - for _, i := range images { - img := i - eg.Go(func() error { - pullCtx, cancel := context.WithTimeout(egCtx, time.Minute*5) - defer cancel() - err := s.dockerCli.PullImage(pullCtx, img) - if err != nil { - logger.Error("Fail to pull image", slogErrAttr(err), slog.String("image", img)) - } - return nil - }) - } - _ = eg.Wait() - - logger.Debug("Removing dangling images") - - err = s.dockerCli.RemoveDanglingImages() + err = s.dockerCli.UpgradeImages(images) if err != nil { - logger.Error("Fail to remove dangling images", slogErrAttr(err)) + logger.Error("Fail to upgrade images", slogErrAttr(err)) } } @@ -310,8 +289,7 @@ func (s *Server) scheduleTasks(ctx context.Context) { l := s.logger.With(slog.String("repo", name)) err := s.syncRepo(context.Background(), name, false) if err != nil { - var dkErr errdefs.ErrConflict - if errors.As(err, &dkErr) { + if errdefs.IsConflict(err) { l.Warn("Still syncing") } else { l.Error("Fail to sync", slogErrAttr(err)) @@ -439,47 +417,22 @@ func (s *Server) syncRepo(ctx context.Context, name string, debug bool) error { for k, v := range repo.Volumes { binds = append(binds, k+":"+v) } - - containerConfig := &container.Config{ - Image: repo.Image, - OpenStdin: true, - Env: envs, - Labels: map[string]string{ - api.LabelRepoName: repo.Name, - api.LabelStorageDir: repo.StorageDir, - }, - } - hostConfig := &container.HostConfig{ - SecurityOpt: securityOpt, - // NOTE: difference between "-v" and "--mount": - // https://docs.docker.com/storage/bind-mounts/#choose-the--v-or---mount-flag - Mounts: []mount.Mount{ - { - // TODO: make it configurable? - Type: mount.TypeTmpfs, - Target: "/tmp", - }, - }, - Binds: binds, - } - networkingConfig := &network.NetworkingConfig{ - EndpointsConfig: make(map[string]*network.EndpointSettings, 1), - } - switch repo.Network { - case "", "host": - hostConfig.NetworkMode = "host" - default: - // https://github.com/moby/moby/blob/master/daemon/create_test.go#L15 - networkingConfig.EndpointsConfig[repo.Network] = &network.EndpointSettings{} - } ctName := s.config.NamePrefix + name ctID, err := s.dockerCli.RunContainer( ctx, - containerConfig, - hostConfig, - networkingConfig, - ctName, + docker.RunContainerConfig{ + Labels: map[string]string{ + api.LabelRepoName: repo.Name, + api.LabelStorageDir: repo.StorageDir, + }, + Env: envs, + Image: repo.Image, + Name: ctName, + SecurityOpt: securityOpt, + Binds: binds, + Network: repo.Network, + }, ) if err != nil { return fmt.Errorf("run container: %w", err) diff --git a/pkg/server/utils_test.go b/pkg/server/utils_test.go index 9e0c730..f0d0dbf 100644 --- a/pkg/server/utils_test.go +++ b/pkg/server/utils_test.go @@ -6,8 +6,6 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" "github.com/stretchr/testify/require" "github.com/ustclug/Yuki/pkg/api" @@ -53,8 +51,14 @@ type fakeImageClient struct { pullImage func(ctx context.Context, image string) error } -func (f *fakeImageClient) PullImage(ctx context.Context, image string) error { - return f.pullImage(ctx, image) +func (f *fakeImageClient) UpgradeImages(refs []string) error { + for _, ref := range refs { + err := f.pullImage(context.Background(), ref) + if err != nil { + return err + } + } + return nil } func TestUpgradeImages(t *testing.T) { @@ -102,15 +106,13 @@ func TestWaitRunningContainers(t *testing.T) { }).Error) _, err := te.server.dockerCli.RunContainer( context.TODO(), - &container.Config{ + docker.RunContainerConfig{ + Name: "sync-repo0", Labels: map[string]string{ api.LabelRepoName: "repo0", api.LabelStorageDir: "/data", }, }, - &container.HostConfig{}, - &network.NetworkingConfig{}, - "sync-repo0", ) require.NoError(t, err) require.NoError(t, te.server.waitRunningContainers())