From 49194733973057cd0bb460b68328320eddf98d99 Mon Sep 17 00:00:00 2001 From: Morlay Date: Fri, 27 Sep 2024 10:39:19 +0800 Subject: [PATCH] fix: proxy mode --- Makefile | 16 +- go.mod | 67 +++-- go.sum | 106 ++----- internal/cmd/crkit/serve_registry.go | 7 +- internal/cmd/crkit/zz_generated.runtimedoc.go | 9 +- internal/cmd/tool/main.go | 7 +- pkg/apis/manifest/v1/payload.go | 20 +- .../manifest/v1/zz_generated.runtimedoc.go | 2 - pkg/containerdhost/controller/reconciler.go | 7 +- pkg/content/api/namespace_provider.go | 65 +++++ pkg/content/api/zz_generated.injectable.go | 29 ++ .../api}/zz_generated.runtimedoc.go | 15 +- pkg/content/errors.go | 24 +- pkg/content/fs/api.go | 48 ---- pkg/content/fs/blob_store.go | 11 +- pkg/content/fs/namespace_test.go | 10 +- pkg/content/fs/zz_generated.runtimedoc.go | 15 - pkg/content/named.go | 9 +- pkg/content/namespace.go | 4 +- .../{proxyblobstore.go => blob_store.go} | 13 +- ...xymanifeststore.go => manifest_service.go} | 12 +- .../proxy/{proxyregistry.go => namespace.go} | 14 +- pkg/content/proxy/namespace_test.go | 155 ++++++++++ .../{proxytagservice.go => tag_service.go} | 4 - pkg/content/remote/authn/authn.go | 195 +++++++++++++ pkg/content/remote/authn/errors.go | 16 ++ pkg/content/remote/authn/map.go | 56 ++++ pkg/content/remote/authn/www_authenticate.go | 94 ++++++ .../remote/authn/www_authenticate_test.go | 23 ++ .../remote/authn/zz_generated.runtimedoc.go | 81 ++++++ pkg/content/remote/blob_store.go | 196 +++++++++++++ pkg/content/remote/blobstore.go | 267 ------------------ pkg/content/remote/client.go | 68 +++++ pkg/content/remote/manifest_service.go | 86 ++++++ pkg/content/remote/manifeststore.go | 98 ------- pkg/content/remote/namespace.go | 123 ++------ pkg/content/remote/namespace_test.go | 140 +++++++++ pkg/content/remote/registry.go | 10 + pkg/content/remote/tag_service.go | 81 ++++++ pkg/content/remote/tagservice.go | 60 ---- pkg/content/remote/zz_generated.runtimedoc.go | 54 ++++ pkg/content/repository.go | 4 +- pkg/content/tag_service.go | 9 + pkg/content/zz_generated.injectable.go | 35 +++ pkg/content/zz_generated.runtimedoc.go | 42 ++- pkg/kubepkg/packer.go | 3 +- pkg/kubepkg/util.go | 6 +- pkg/ocitar/write.go | 23 +- pkg/registryhttp/apis/registry/base_url.go | 13 + .../apis/registry/blob__delete.go | 35 +++ pkg/registryhttp/apis/registry/blob__get.go | 17 +- pkg/registryhttp/apis/registry/blob__head.go | 17 +- .../apis/registry/blob__upload.go | 24 +- .../apis/registry/blob__upload_cancel.go | 27 ++ .../apis/registry/blob__upload_patch.go | 30 +- .../apis/registry/blob__upload_put.go | 23 +- .../apis/registry/manifest__delete.go | 47 +++ .../apis/registry/manifest__get.go | 33 +-- .../apis/registry/manifest__head.go | 20 +- .../apis/registry/manifest__put.go | 24 +- .../apis/registry/operator/namescoped.go | 23 -- pkg/registryhttp/apis/registry/tag__list.go | 36 +++ .../apis/registry/zz_generated.injectable.go | 79 ++++++ .../apis/registry/zz_generated.operator.go | 63 ++++- .../apis/registry/zz_generated.runtimedoc.go | 127 +++++++++ pkg/registryhttp/server.go | 10 +- pkg/registryhttp/zz_generated.injectable.go | 20 ++ pkg/uploadcache/mem_upload_cache.go | 115 ++++++++ pkg/uploadcache/upload_cache.go | 115 +------- pkg/uploadcache/zz_generated.injectable.go | 37 +++ 70 files changed, 2334 insertions(+), 1040 deletions(-) create mode 100644 pkg/content/api/namespace_provider.go create mode 100644 pkg/content/api/zz_generated.injectable.go rename pkg/{registryhttp/apis/registry/operator => content/api}/zz_generated.runtimedoc.go (54%) rename pkg/content/proxy/{proxyblobstore.go => blob_store.go} (88%) rename pkg/content/proxy/{proxymanifeststore.go => manifest_service.go} (65%) rename pkg/content/proxy/{proxyregistry.go => namespace.go} (84%) create mode 100644 pkg/content/proxy/namespace_test.go rename pkg/content/proxy/{proxytagservice.go => tag_service.go} (92%) create mode 100644 pkg/content/remote/authn/authn.go create mode 100644 pkg/content/remote/authn/errors.go create mode 100644 pkg/content/remote/authn/map.go create mode 100644 pkg/content/remote/authn/www_authenticate.go create mode 100644 pkg/content/remote/authn/www_authenticate_test.go create mode 100644 pkg/content/remote/authn/zz_generated.runtimedoc.go create mode 100644 pkg/content/remote/blob_store.go delete mode 100644 pkg/content/remote/blobstore.go create mode 100644 pkg/content/remote/client.go create mode 100644 pkg/content/remote/manifest_service.go delete mode 100644 pkg/content/remote/manifeststore.go create mode 100644 pkg/content/remote/namespace_test.go create mode 100644 pkg/content/remote/registry.go create mode 100644 pkg/content/remote/tag_service.go delete mode 100644 pkg/content/remote/tagservice.go create mode 100644 pkg/content/remote/zz_generated.runtimedoc.go create mode 100644 pkg/content/zz_generated.injectable.go create mode 100644 pkg/registryhttp/apis/registry/blob__delete.go create mode 100644 pkg/registryhttp/apis/registry/blob__upload_cancel.go create mode 100644 pkg/registryhttp/apis/registry/manifest__delete.go delete mode 100644 pkg/registryhttp/apis/registry/operator/namescoped.go create mode 100644 pkg/registryhttp/apis/registry/tag__list.go create mode 100644 pkg/registryhttp/apis/registry/zz_generated.injectable.go create mode 100644 pkg/registryhttp/zz_generated.injectable.go create mode 100644 pkg/uploadcache/mem_upload_cache.go create mode 100644 pkg/uploadcache/zz_generated.injectable.go diff --git a/Makefile b/Makefile index 7194e53..03c9fbe 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,23 @@ serve: --log-format=text \ --addr=:5050 +serve.proxy: + $(CRKIT) serve registry -c \ + --log-format=text \ + --remote-endpoint=https://${CONTAINER_REGISTRY} \ + --remote-username=${CONTAINER_REGISTRY_USERNAME} \ + --remote-password=${CONTAINER_REGISTRY_PASSWORD} \ + --addr=:5050 + dump.k8s: $(CRKIT) serve registry --dump-k8s gen: go run ./internal/cmd/tool gen ./internal/cmd/crkit +gen.debug: + go run ./internal/cmd/tool gen ./pkg/content/api + dep: go get -u ./... @@ -33,7 +44,10 @@ ship: $(PIPER) do ship debug.pull: - crane pull --insecure 0.0.0.0:5050/docker.io/library/nginx:latest .tmp/nginx.tar + crane pull --format=oci --insecure 0.0.0.0:5050/docker.io/library/nginx:latest .tmp/nginx.tar + +debug.pull.proxy: + crane pull --format=oci --insecure 0.0.0.0:5050/${CONTAINER_REGISTRY}/ghcr.io/octohelm/crkit:v0.0.0-20240926121153-ee21b4f4c7cd .tmp/crkit.tar debug.push: crane push --insecure .tmp/nginx.tar 0.0.0.0:5050/docker.io/library/nginx:latest diff --git a/go.mod b/go.mod index bd41723..dbc012b 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,28 @@ module github.com/octohelm/crkit -go 1.23.1 +go 1.23.2 require ( - github.com/containerd/containerd v1.7.22 + github.com/containerd/containerd v1.7.23 github.com/containerd/platforms v0.2.1 github.com/distribution/reference v0.6.0 github.com/go-courier/logr v0.3.0 + github.com/go-json-experiment/json v0.0.0-20240815175050-ebd3a8989ca1 github.com/gobwas/glob v0.2.3 github.com/google/go-containerregistry v0.20.2 github.com/google/uuid v1.6.0 - github.com/innoai-tech/infra v0.0.0-20240920044133-bd9089f58588 - github.com/octohelm/courier v0.0.0-20240926074601-b33108060546 - github.com/octohelm/gengo v0.0.0-20240919101245-4bc8a41dd2f4 + github.com/innoai-tech/infra v0.0.0-20241008041626-af0207c05b5b + github.com/octohelm/courier v0.0.0-20241015075216-73c8ee334551 + github.com/octohelm/gengo v0.0.0-20241014043309-2344b8632080 github.com/octohelm/kubekit v0.0.0-20240816091736-f2433647d633 - github.com/octohelm/kubepkgspec v0.0.0-20240910052731-07f208736894 - github.com/octohelm/storage v0.0.0-20240923082032-9c037f4e0c05 + github.com/octohelm/kubepkgspec v0.0.0-20241015012541-484a91b9d42a + github.com/octohelm/storage v0.0.0-20241014040055-4f454a8d6947 github.com/octohelm/unifs v0.0.0-20240906103445-29045af3bc39 - github.com/octohelm/x v0.0.0-20240904081416-42a1ee2d28a9 + github.com/octohelm/x v0.0.0-20241011014327-0fcf864c84d6 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/pelletier/go-toml/v2 v2.2.3 - github.com/pkg/errors v0.9.1 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/sync v0.8.0 k8s.io/api v0.31.1 k8s.io/apimachinery v0.31.1 @@ -35,13 +36,13 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/containerd/errdefs v0.2.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/cli v27.1.1+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect @@ -50,7 +51,6 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-json-experiment/json v0.0.0-20240815175050-ebd3a8989ca1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -68,35 +68,35 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/innoai-tech/openapi-playground v0.0.0-20240920071141-a9f087bad00f // indirect + github.com/innoai-tech/openapi-playground v0.0.0-20240909062817-20f99d67805e // indirect github.com/jlaffaye/ftp v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ansiterm v1.0.0 // indirect - github.com/julienschmidt/httprouter v1.3.0 // indirect - github.com/klauspost/compress v1.17.10 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/minio-go/v7 v7.0.77 // indirect + github.com/minio/minio-go/v7 v7.0.75 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.34.2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.59.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/rs/xid v1.6.0 // indirect + github.com/rs/xid v1.5.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/vbatts/tar-split v0.11.5 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect go.opentelemetry.io/otel v1.30.0 // indirect @@ -111,20 +111,19 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.25.0 // indirect + golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/grpc v1.67.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -132,8 +131,8 @@ require ( k8s.io/apiextensions-apiserver v0.31.1 // indirect k8s.io/client-go v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect - k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + k8s.io/kube-openapi v0.0.0-20240812233141-91dab695df6f // indirect + k8s.io/utils v0.0.0-20240821151609-f90d01438635 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum index bb40acd..56e362e 100644 --- a/go.sum +++ b/go.sum @@ -15,22 +15,16 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/containerd/containerd v1.7.21 h1:USGXRK1eOC/SX0L195YgxTHb0a00anxajOzgfN0qrCA= -github.com/containerd/containerd v1.7.21/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= -github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0= -github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= -github.com/containerd/errdefs v0.2.0 h1:XllDESRfJtVrMwMmR2mCabxyvBK4UlbyyiWI3MvRw0o= -github.com/containerd/errdefs v0.2.0/go.mod h1:C28ixlj3dKhQS9hsQ13b+HIb4X7+s2G4FYhbSPcRDLM= +github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= +github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -41,16 +35,10 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= @@ -120,7 +108,6 @@ github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSF github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -132,12 +119,10 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/innoai-tech/infra v0.0.0-20240920044133-bd9089f58588 h1:z533YRLCmcF+9cM3N7WG9paJKALUaAb5EZ4QQkZ2nAU= -github.com/innoai-tech/infra v0.0.0-20240920044133-bd9089f58588/go.mod h1:HkPC7CHAaE5yRnCeT081SvDCAI1Pz4EaTG5WbyNdrn8= +github.com/innoai-tech/infra v0.0.0-20241008041626-af0207c05b5b h1:0PTpaVBDi/arWXj7gRvBupFriTtNfmg6U8d3czc05fk= +github.com/innoai-tech/infra v0.0.0-20241008041626-af0207c05b5b/go.mod h1:m0kq50sh3/5+M2+ZrUT+osBkBcZaSu5ZDpQCL9klI8I= github.com/innoai-tech/openapi-playground v0.0.0-20240909062817-20f99d67805e h1:2x5pVpGEOBX6mo0IyT+clX++hUVgXgS5RVPzncrIjf4= github.com/innoai-tech/openapi-playground v0.0.0-20240909062817-20f99d67805e/go.mod h1:XF6gAVE9R8xSKHWbG0TtH/7RbWrTh16PH23eGZk69po= -github.com/innoai-tech/openapi-playground v0.0.0-20240920071141-a9f087bad00f h1:7GQQEvaFt+npnJH08AMm6A2UecFhzKI51lsBOUKgXgY= -github.com/innoai-tech/openapi-playground v0.0.0-20240920071141-a9f087bad00f/go.mod h1:XF6gAVE9R8xSKHWbG0TtH/7RbWrTh16PH23eGZk69po= github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= github.com/johannesboyne/gofakes3 v0.0.0-20230506070712-04da935ef877 h1:O7syWuYGzre3s73s+NkgB8e0ZvsIVhT/zxNU7V1gHK8= @@ -148,14 +133,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg= github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= -github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -186,8 +167,6 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.75 h1:0uLrB6u6teY2Jt+cJUVi9cTvDRuBKWSRzSAcznRkwlE= github.com/minio/minio-go/v7 v7.0.75/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= -github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw= -github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -199,20 +178,20 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/octohelm/courier v0.0.0-20240926074601-b33108060546 h1:QSHhZNEoA61sujZ/jLnFrTpOG+qHajOkENzPlLYpqWY= -github.com/octohelm/courier v0.0.0-20240926074601-b33108060546/go.mod h1:WTM484kbV7BFRqWVpG60zAY+e7yJSs76rvmGvGuMhp0= -github.com/octohelm/gengo v0.0.0-20240919101245-4bc8a41dd2f4 h1:id6V5MUVYljRaukW8qm0jT+aUgq21YmOzaiqE+dzTq4= -github.com/octohelm/gengo v0.0.0-20240919101245-4bc8a41dd2f4/go.mod h1:IIloUgLGOyAJJXLgt3oJ5G7NX8Wxv1CBYH4sPcYrMpE= +github.com/octohelm/courier v0.0.0-20241015075216-73c8ee334551 h1:HpeD19dzNrHl8fzBCWiaxxTF9P7en8oDR9gz/e/cjDo= +github.com/octohelm/courier v0.0.0-20241015075216-73c8ee334551/go.mod h1:uHccmYdw4rrly3faZQHLkZGCDYjj3zuwjKrUYo4QiXo= +github.com/octohelm/gengo v0.0.0-20241014043309-2344b8632080 h1:JydhpFfiuBsIXWs4f3GQJDpCmG5LFKgzB7CFkRGWs3s= +github.com/octohelm/gengo v0.0.0-20241014043309-2344b8632080/go.mod h1:7bkbdNmnQEmnVbvdSJdRwvGmm2/3KgvxXSDi/nXCOk8= github.com/octohelm/kubekit v0.0.0-20240816091736-f2433647d633 h1:pLd+NUgPr6suaX8I/rhoRf8iFFoGeRFjiqYAF3mXgYE= github.com/octohelm/kubekit v0.0.0-20240816091736-f2433647d633/go.mod h1:JgyIpmmWjpJztnbNjdAX6pDEgW60gS9d2IJknmseLQY= -github.com/octohelm/kubepkgspec v0.0.0-20240910052731-07f208736894 h1:pYAgNJO4FQO1A5NGPfH13cdrE3Ciedzzw9Es8s1rXuE= -github.com/octohelm/kubepkgspec v0.0.0-20240910052731-07f208736894/go.mod h1:owSjB+3wGNYj97gMF9rxQFlgzDU8gx3n0MpXrHNgHZE= -github.com/octohelm/storage v0.0.0-20240923082032-9c037f4e0c05 h1:kCBdlT2mRwLN3YF2W2iQMCNJuf7c0NJQ7ry8ama9+Rg= -github.com/octohelm/storage v0.0.0-20240923082032-9c037f4e0c05/go.mod h1:IYiOv6MMWkyPqXngyrfMix0ZTbXVAnl5e1mjU3kYw08= +github.com/octohelm/kubepkgspec v0.0.0-20241015012541-484a91b9d42a h1:2sAjkSoVexPmR6yPfPk8p2fK4kEajzf7INlXPOlodsc= +github.com/octohelm/kubepkgspec v0.0.0-20241015012541-484a91b9d42a/go.mod h1:tfKjYcUqh4jjnYNbc6nmqA6hKozWwnyxov8sAyFpRCo= +github.com/octohelm/storage v0.0.0-20241014040055-4f454a8d6947 h1:NrKmBgKHtl0FK3RZMZ9x9Epm6ZYnd97SOhjne46pfQY= +github.com/octohelm/storage v0.0.0-20241014040055-4f454a8d6947/go.mod h1:DfC1lOuB05GgCIdqLIOJ6+A4XPtDUkIKKUXqjKxXTNs= github.com/octohelm/unifs v0.0.0-20240906103445-29045af3bc39 h1:OmHWxPlZ5Vtbvd5SFWx/HVE652rgNBFGZsJOXYayUeQ= github.com/octohelm/unifs v0.0.0-20240906103445-29045af3bc39/go.mod h1:QflhQAyLs37BlcCpg2Bw8JfGEnYjVq//skiZR8AN/jg= -github.com/octohelm/x v0.0.0-20240904081416-42a1ee2d28a9 h1:PlgBSARdazQ9dBicDlI8sWILYXyh54yDa9XI4is4MTM= -github.com/octohelm/x v0.0.0-20240904081416-42a1ee2d28a9/go.mod h1:Tql/GYN4vR5X+2LcjgX1r/Qbjr8epCjkvRZgKg4MH3c= +github.com/octohelm/x v0.0.0-20241011014327-0fcf864c84d6 h1:4royPn66/B49ftj5Jh7ZWuJ8A2B/2l0UTQ6EFLf29Vk= +github.com/octohelm/x v0.0.0-20241011014327-0fcf864c84d6/go.mod h1:c8k4TZNwZXVCclWxFU8dm67PQ98APzKW3f5JJykQ2uo= github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= @@ -221,7 +200,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -245,8 +223,6 @@ github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21 h1:igWZJlu github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21/go.mod h1:RMRJLmBOqWacUkmJHRMiPKh1S1m3PA7Zh4W80/kWPpg= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= @@ -274,8 +250,6 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= -github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= -github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -315,10 +289,10 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= 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/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= @@ -327,12 +301,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -349,22 +321,22 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 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= @@ -373,16 +345,10 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 h1:pAjq8XSSzXoP9ya73v/w+9QEAAJNluLrpmMq5qFJQNY= -google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:O6rP0uBq4k0mdi/b4ZEMAZjkhYWhS815kCvaMha4VN8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -400,32 +366,20 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= -k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= -k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= -k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= -k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= -k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240812233141-91dab695df6f h1:bnWtxXWdAl5bVOCEPoNdvMkyj6cTW3zxHuwKIakuV9w= k8s.io/kube-openapi v0.0.0-20240812233141-91dab695df6f/go.mod h1:G0W3eI9gG219NHRq3h5uQaRBl4pj4ZpwzRP5ti8y770= -k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= -k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= k8s.io/utils v0.0.0-20240821151609-f90d01438635 h1:2wThSvJoW/Ncn9TmQEYXRnevZXi2duqHWf5OX9S3zjI= k8s.io/utils v0.0.0-20240821151609-f90d01438635/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= -k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= diff --git a/internal/cmd/crkit/serve_registry.go b/internal/cmd/crkit/serve_registry.go index f9107bd..125ccb3 100644 --- a/internal/cmd/crkit/serve_registry.go +++ b/internal/cmd/crkit/serve_registry.go @@ -3,22 +3,23 @@ package main import ( "github.com/innoai-tech/infra/pkg/cli" "github.com/innoai-tech/infra/pkg/otel" - contentfs "github.com/octohelm/crkit/pkg/content/fs" "github.com/octohelm/crkit/pkg/registryhttp" "github.com/octohelm/crkit/pkg/uploadcache" + + contentapi "github.com/octohelm/crkit/pkg/content/api" ) func init() { cli.AddTo(Serve, &Registry{}) } -// Container Registry type Registry struct { cli.C `component:"container-registry"` otel.Otel UploadCache uploadcache.MemUploadCache - Content contentfs.NamespaceProvider + + contentapi.NamespaceProvider registryhttp.Server } diff --git a/internal/cmd/crkit/zz_generated.runtimedoc.go b/internal/cmd/crkit/zz_generated.runtimedoc.go index 1745c20..a7f93c1 100644 --- a/internal/cmd/crkit/zz_generated.runtimedoc.go +++ b/internal/cmd/crkit/zz_generated.runtimedoc.go @@ -21,7 +21,7 @@ func (v Registry) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true case "UploadCache": return []string{}, true - case "Content": + case "NamespaceProvider": return []string{}, true case "Server": return []string{}, true @@ -30,13 +30,14 @@ func (v Registry) RuntimeDoc(names ...string) ([]string, bool) { if doc, ok := runtimeDoc(v.Otel, names...); ok { return doc, ok } + if doc, ok := runtimeDoc(v.NamespaceProvider, names...); ok { + return doc, ok + } if doc, ok := runtimeDoc(v.Server, names...); ok { return doc, ok } return nil, false } - return []string{ - "Container Registry", - }, true + return []string{}, true } diff --git a/internal/cmd/tool/main.go b/internal/cmd/tool/main.go index 5e7df3e..4cb06ac 100644 --- a/internal/cmd/tool/main.go +++ b/internal/cmd/tool/main.go @@ -10,6 +10,7 @@ import ( "github.com/innoai-tech/infra/pkg/cli" _ "github.com/octohelm/courier/devpkg/clientgen" + _ "github.com/octohelm/courier/devpkg/injectablegen" _ "github.com/octohelm/courier/devpkg/operatorgen" _ "github.com/octohelm/gengo/devpkg/deepcopygen" _ "github.com/octohelm/gengo/devpkg/runtimedocgen" @@ -19,11 +20,13 @@ import ( var App = cli.NewApp("gengo", "dev") func init() { - cli.AddTo(App, &struct { + c := &struct { cli.C `name:"gen"` otel.Otel gengo.Gengo - }{}) + }{} + + cli.AddTo(App, c) } func main() { diff --git a/pkg/apis/manifest/v1/payload.go b/pkg/apis/manifest/v1/payload.go index 25e6800..6e21875 100644 --- a/pkg/apis/manifest/v1/payload.go +++ b/pkg/apis/manifest/v1/payload.go @@ -1,11 +1,13 @@ package v1 import ( - "encoding/json" - "github.com/octohelm/courier/pkg/openapi/jsonschema/util" + "fmt" + + "github.com/octohelm/courier/pkg/validator" + + "github.com/octohelm/courier/pkg/validator/taggedunion" "github.com/opencontainers/go-digest" specv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" ) type Payload struct { @@ -31,7 +33,7 @@ func From(media Manifest) (*Payload, error) { }, nil } - return nil, errors.Errorf("invalid media %s", media.Type()) + return nil, fmt.Errorf("invalid media %s", media.Type()) } func (Payload) Discriminator() string { @@ -42,9 +44,8 @@ func (Payload) Mapping() map[string]any { return map[string]any{ specv1.MediaTypeImageManifest: Manifest(&OciManifest{}), specv1.MediaTypeImageIndex: Manifest(&OciIndex{}), - - DockerMediaTypeManifest: Manifest(&DockerManifest{}), - DockerMediaTypeManifestList: Manifest(&DockerManifestList{}), + DockerMediaTypeManifest: Manifest(&DockerManifest{}), + DockerMediaTypeManifestList: Manifest(&DockerManifestList{}), } } @@ -57,7 +58,7 @@ func (m *Payload) UnmarshalJSON(data []byte) error { raw: data, dgst: digest.FromBytes(data), } - if err := util.UnmarshalTaggedUnionFromJSON(data, &mm); err != nil { + if err := taggedunion.Unmarshal(data, &mm); err != nil { return err } *m = mm @@ -71,7 +72,7 @@ func (m Payload) MarshalJSON() ([]byte, error) { if m.Manifest == nil { return []byte("{}"), nil } - return json.Marshal(m.Manifest) + return validator.Marshal(m.Manifest) } func (m *Payload) Payload() ([]byte, digest.Digest, error) { @@ -84,5 +85,4 @@ func (m *Payload) Payload() ([]byte, digest.Digest, error) { m.dgst = digest.FromBytes(raw) } return m.raw, m.dgst, nil - } diff --git a/pkg/apis/manifest/v1/zz_generated.runtimedoc.go b/pkg/apis/manifest/v1/zz_generated.runtimedoc.go index bd818bf..5324357 100644 --- a/pkg/apis/manifest/v1/zz_generated.runtimedoc.go +++ b/pkg/apis/manifest/v1/zz_generated.runtimedoc.go @@ -173,8 +173,6 @@ func (v Payload) RuntimeDoc(names ...string) ([]string, bool) { switch names[0] { case "Manifest": return []string{}, true - case "Raw": - return []string{}, true } if doc, ok := runtimeDoc(v.Manifest, names...); ok { diff --git a/pkg/containerdhost/controller/reconciler.go b/pkg/containerdhost/controller/reconciler.go index 9502496..60c3bc4 100644 --- a/pkg/containerdhost/controller/reconciler.go +++ b/pkg/containerdhost/controller/reconciler.go @@ -2,13 +2,12 @@ package controller import ( "context" + "fmt" "io/fs" "os" "path" "github.com/octohelm/kubekit/pkg/operator" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -102,12 +101,12 @@ func (r *Reconciler) sync(ctx context.Context, items []corev1.ConfigMap) error { func (r *Reconciler) syncByConfigMap(ctx context.Context, cm corev1.ConfigMap) (string, error) { if len(cm.Data) == 0 { - return "", errors.Errorf("missing data at %s.%s", cm.Name, cm.Namespace) + return "", fmt.Errorf("missing data at %s.%s", cm.Name, cm.Namespace) } host := cm.Data["host"] if host == "" { - return "", errors.Errorf("missing host value at %s.%s", cm.Name, cm.Namespace) + return "", fmt.Errorf("missing host value at %s.%s", cm.Name, cm.Namespace) } // https://github.com/containerd/containerd/blob/main/docs/hosts.md diff --git a/pkg/content/api/namespace_provider.go b/pkg/content/api/namespace_provider.go new file mode 100644 index 0000000..affdd04 --- /dev/null +++ b/pkg/content/api/namespace_provider.go @@ -0,0 +1,65 @@ +package api + +import ( + "context" + "log/slog" + "os" + "path/filepath" + + "github.com/go-courier/logr" + "github.com/octohelm/crkit/pkg/content" + contentfs "github.com/octohelm/crkit/pkg/content/fs" + contentproxy "github.com/octohelm/crkit/pkg/content/proxy" + contentremote "github.com/octohelm/crkit/pkg/content/remote" + "github.com/octohelm/unifs/pkg/filesystem" + "github.com/octohelm/unifs/pkg/filesystem/api" + "github.com/octohelm/unifs/pkg/strfmt" +) + +// +gengo:injectable:provider github.com/octohelm/crkit/pkg/content.Namespace +type NamespaceProvider struct { + Remote contentremote.Registry + Content api.FileSystemBackend + + content.Namespace `flag:"-"` +} + +func (s *NamespaceProvider) beforeInit(ctx context.Context) error { + if s.Content.Backend.IsZero() { + cwd, err := os.Getwd() + if err != nil { + return err + } + endpoint, _ := strfmt.ParseEndpoint("file://" + filepath.Join(cwd, ".tmp/container-registry")) + s.Content.Backend = *endpoint + } + + return nil +} + +func (s *NamespaceProvider) afterInit(ctx context.Context) error { + if err := filesystem.MkdirAll(ctx, s.Content.FileSystem(), "."); err != nil { + return err + } + + local := contentfs.NewNamespace(s.Content.FileSystem()) + + if s.Remote.Endpoint != "" { + proxy, err := contentproxy.NewProxyFallbackRegistry(ctx, local, s.Remote) + if err != nil { + return err + } + + s.Namespace = proxy + + logr.FromContext(ctx). + WithValues(slog.String("remote", s.Remote.Endpoint)). + Info("proxy") + + return nil + } + + s.Namespace = local + + return nil +} diff --git a/pkg/content/api/zz_generated.injectable.go b/pkg/content/api/zz_generated.injectable.go new file mode 100644 index 0000000..b682b56 --- /dev/null +++ b/pkg/content/api/zz_generated.injectable.go @@ -0,0 +1,29 @@ +/* +Package api GENERATED BY gengo:injectable +DON'T EDIT THIS FILE +*/ +package api + +import ( + context "context" + + content "github.com/octohelm/crkit/pkg/content" +) + +func (p *NamespaceProvider) InjectContext(ctx context.Context) context.Context { + return content.NamespaceInjectContext(ctx, p) +} + +func (v *NamespaceProvider) Init(ctx context.Context) error { + if err := v.beforeInit(ctx); err != nil { + return err + } + if err := v.Content.Init(ctx); err != nil { + return err + } + if err := v.afterInit(ctx); err != nil { + return err + } + + return nil +} diff --git a/pkg/registryhttp/apis/registry/operator/zz_generated.runtimedoc.go b/pkg/content/api/zz_generated.runtimedoc.go similarity index 54% rename from pkg/registryhttp/apis/registry/operator/zz_generated.runtimedoc.go rename to pkg/content/api/zz_generated.runtimedoc.go index e8930f5..0ffd057 100644 --- a/pkg/registryhttp/apis/registry/operator/zz_generated.runtimedoc.go +++ b/pkg/content/api/zz_generated.runtimedoc.go @@ -1,8 +1,8 @@ /* -Package operator GENERATED BY gengo:runtimedoc +Package api GENERATED BY gengo:runtimedoc DON'T EDIT THIS FILE */ -package operator +package api // nolint:deadcode,unused func runtimeDoc(v any, names ...string) ([]string, bool) { @@ -14,13 +14,20 @@ func runtimeDoc(v any, names ...string) ([]string, bool) { return nil, false } -func (v NameScoped) RuntimeDoc(names ...string) ([]string, bool) { +func (v NamespaceProvider) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { - case "Name": + case "Remote": + return []string{}, true + case "Content": + return []string{}, true + case "Namespace": return []string{}, true } + if doc, ok := runtimeDoc(v.Namespace, names...); ok { + return doc, ok + } return nil, false } diff --git a/pkg/content/errors.go b/pkg/content/errors.go index 9d48cdb..7c33578 100644 --- a/pkg/content/errors.go +++ b/pkg/content/errors.go @@ -5,13 +5,27 @@ import ( "github.com/octohelm/courier/pkg/statuserror" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) -var ( - ErrBlobUnknown = errors.New("unknown blob") - ErrBlobInvalidLength = errors.New("blob invalid length") -) +type ErrBlobUnknown struct { + statuserror.NotFound + + Digest digest.Digest +} + +func (err *ErrBlobUnknown) Error() string { + return fmt.Sprintf("unknown blob digest=%s", err.Digest) +} + +type ErrBlobInvalidLength struct { + statuserror.RequestedRangeNotSatisfiable + + Reason string +} + +func (err *ErrBlobInvalidLength) Error() string { + return fmt.Sprintf("blob invalid length: %s", err.Reason) +} type ErrTagUnknown struct { statuserror.NotFound diff --git a/pkg/content/fs/api.go b/pkg/content/fs/api.go index 18f4983..ee66687 100644 --- a/pkg/content/fs/api.go +++ b/pkg/content/fs/api.go @@ -1,49 +1 @@ package fs - -import ( - "context" - "os" - "path/filepath" - - "github.com/octohelm/crkit/pkg/content" - "github.com/octohelm/unifs/pkg/filesystem" - "github.com/octohelm/unifs/pkg/filesystem/api" - "github.com/octohelm/unifs/pkg/strfmt" -) - -type NamespaceProvider struct { - api.FileSystemBackend - - namespace content.Namespace -} - -func (s *NamespaceProvider) Init(ctx context.Context) error { - if s.Backend.IsZero() { - cwd, err := os.Getwd() - if err != nil { - return err - } - endpoint, _ := strfmt.ParseEndpoint("file://" + filepath.Join(cwd, ".tmp/container-registry")) - s.Backend = *endpoint - } - - if err := s.FileSystemBackend.Init(ctx); err != nil { - return err - } - - if err := filesystem.MkdirAll(ctx, s.FileSystem(), "."); err != nil { - return err - } - - s.namespace = NewNamespace(s.FileSystem()) - - return nil -} - -func (s *NamespaceProvider) InjectContext(ctx context.Context) context.Context { - return content.NamespaceContext.Inject(ctx, s.namespace) -} - -func (s *NamespaceProvider) Namespace() content.Namespace { - return s.namespace -} diff --git a/pkg/content/fs/blob_store.go b/pkg/content/fs/blob_store.go index 0827678..ea28809 100644 --- a/pkg/content/fs/blob_store.go +++ b/pkg/content/fs/blob_store.go @@ -8,9 +8,8 @@ import ( "path/filepath" "sync" - "github.com/pkg/errors" - "github.com/google/uuid" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" "github.com/octohelm/unifs/pkg/filesystem" @@ -33,7 +32,7 @@ func (f *blobStore) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.D s, err := f.fs.Stat(ctx, defaultLayout.BlobDataPath(dgst)) if err != nil { if os.IsNotExist(err) { - return nil, &content.ErrManifestBlobUnknown{ + return nil, &content.ErrBlobUnknown{ Digest: dgst, } } @@ -49,7 +48,7 @@ func (f *blobStore) Open(ctx context.Context, dgst digest.Digest) (io.ReadCloser file, err := f.fs.OpenFile(ctx, defaultLayout.BlobDataPath(dgst), os.O_RDONLY, os.ModePerm) if err != nil { if os.IsNotExist(err) { - return nil, &content.ErrManifestBlobUnknown{ + return nil, &content.ErrBlobUnknown{ Digest: dgst, } } @@ -134,7 +133,9 @@ func (w *writer) Commit(ctx context.Context, expected manifestv1.Descriptor) (*m dgst := w.Digest(ctx) if expected.Size > 0 && expected.Size != size { - return nil, errors.Wrapf(content.ErrBlobInvalidLength, "unexpected commit size %d, expected %d", size, expected.Size) + return nil, &content.ErrBlobInvalidLength{ + Reason: fmt.Sprintf("unexpected commit size %d, expected %d", size, expected.Size), + } } if expected.Digest != "" && expected.Digest != dgst { diff --git a/pkg/content/fs/namespace_test.go b/pkg/content/fs/namespace_test.go index 4bbc269..33ef17e 100644 --- a/pkg/content/fs/namespace_test.go +++ b/pkg/content/fs/namespace_test.go @@ -16,7 +16,7 @@ import ( "github.com/octohelm/courier/pkg/courierhttp/handler/httprouter" "github.com/octohelm/crkit/internal/testingutil" "github.com/octohelm/crkit/pkg/content" - contentfs "github.com/octohelm/crkit/pkg/content/fs" + contentapi "github.com/octohelm/crkit/pkg/content/api" "github.com/octohelm/crkit/pkg/registryhttp/apis" "github.com/octohelm/crkit/pkg/uploadcache" "github.com/octohelm/unifs/pkg/strfmt" @@ -27,7 +27,7 @@ func TestNamespace(t *testing.T) { c := &struct { otel.Otel MemUploadCache uploadcache.MemUploadCache - Content contentfs.NamespaceProvider + contentapi.NamespaceProvider }{} tmp := t.TempDir() @@ -74,8 +74,7 @@ func TestNamespace(t *testing.T) { testingx.Expect(t, err, testingx.BeNil[error]()) t.Run("then could do with tags", func(t *testing.T) { - n := c.Content.Namespace() - r, _ := n.Repository(ctx, content.Name("test/manifest")) + r, _ := c.Repository(ctx, content.Name("test/manifest")) tags, err := r.Tags(ctx) testingx.Expect(t, err, testingx.BeNil[error]()) @@ -121,8 +120,7 @@ func TestNamespace(t *testing.T) { testingx.Expect(t, err, testingx.BeNil[error]()) t.Run("then could do with tags", func(t *testing.T) { - n := c.Content.Namespace() - r, _ := n.Repository(ctx, content.Name("test/index")) + r, _ := c.Repository(ctx, content.Name("test/index")) tags, err := r.Tags(ctx) testingx.Expect(t, err, testingx.BeNil[error]()) diff --git a/pkg/content/fs/zz_generated.runtimedoc.go b/pkg/content/fs/zz_generated.runtimedoc.go index 3648348..2a351d2 100644 --- a/pkg/content/fs/zz_generated.runtimedoc.go +++ b/pkg/content/fs/zz_generated.runtimedoc.go @@ -17,18 +17,3 @@ func runtimeDoc(v any, names ...string) ([]string, bool) { func (Layout) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } -func (v NamespaceProvider) RuntimeDoc(names ...string) ([]string, bool) { - if len(names) > 0 { - switch names[0] { - case "FileSystemBackend": - return []string{}, true - - } - if doc, ok := runtimeDoc(v.FileSystemBackend, names...); ok { - return doc, ok - } - - return nil, false - } - return []string{}, true -} diff --git a/pkg/content/named.go b/pkg/content/named.go index 34f5935..eb5f103 100644 --- a/pkg/content/named.go +++ b/pkg/content/named.go @@ -1,8 +1,9 @@ package content import ( + "errors" + "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) type Name string @@ -26,13 +27,13 @@ func (d *Digest) UnmarshalText(t []byte) error { return nil } -type TagOrDigest string +type Reference string -func (tag TagOrDigest) Digest() (digest.Digest, error) { +func (tag Reference) Digest() (digest.Digest, error) { return digest.Parse(string(tag)) } -func (tag TagOrDigest) Tag() (string, error) { +func (tag Reference) Tag() (string, error) { if _, err := tag.Digest(); err != nil { return string(tag), nil } diff --git a/pkg/content/namespace.go b/pkg/content/namespace.go index e7a4245..ce2dfad 100644 --- a/pkg/content/namespace.go +++ b/pkg/content/namespace.go @@ -4,11 +4,9 @@ import ( "context" "github.com/distribution/reference" - contextx "github.com/octohelm/x/context" ) +// +gengo:injectable:provider type Namespace interface { Repository(ctx context.Context, named reference.Named) (Repository, error) } - -var NamespaceContext = contextx.New[Namespace]() diff --git a/pkg/content/proxy/proxyblobstore.go b/pkg/content/proxy/blob_store.go similarity index 88% rename from pkg/content/proxy/proxyblobstore.go rename to pkg/content/proxy/blob_store.go index 8175dfa..24c7060 100644 --- a/pkg/content/proxy/proxyblobstore.go +++ b/pkg/content/proxy/blob_store.go @@ -2,13 +2,14 @@ package proxy import ( "context" + "errors" "io" "github.com/distribution/reference" manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" + "github.com/octohelm/x/ptr" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) type proxyBlobStore struct { @@ -21,7 +22,7 @@ type proxyBlobStore struct { var _ content.BlobStore = &proxyBlobStore{} func (pbs *proxyBlobStore) Writer(ctx context.Context) (content.BlobWriter, error) { - return nil, errors.New("not implements") + return pbs.remoteStore.Writer(ctx) } func (pbs *proxyBlobStore) Remove(ctx context.Context, dgst digest.Digest) error { @@ -49,7 +50,7 @@ func (pbs *proxyBlobStore) Open(ctx context.Context, dgst digest.Digest) (io.Rea io.Closer }{ Reader: io.TeeReader(blob, bw), - Closer: CloserFunc(func() error { + Closer: closerFunc(func() error { defer func() { err = blob.Close() }() @@ -71,13 +72,13 @@ func (pbs *proxyBlobStore) Info(ctx context.Context, dgst digest.Digest) (*manif return desc, err } - if !errors.Is(err, content.ErrBlobUnknown) { + if !errors.As(err, ptr.Ptr(&content.ErrBlobUnknown{})) { return &manifestv1.Descriptor{}, err } d, err := pbs.remoteStore.Info(ctx, dgst) if err != nil { - if errors.Is(err, content.ErrBlobUnknown) { + if errors.As(err, ptr.Ptr(&content.ErrBlobUnknown{})) { // FIXME hack to use open to trigger remote sync // harbor will return 404 when stat, util digest full synced b, err := pbs.remoteStore.Open(ctx, dgst) @@ -105,7 +106,7 @@ func (pbs *proxyBlobStore) Info(ctx context.Context, dgst digest.Digest) (*manif return d, nil } -func CloserFunc(close func() error) io.Closer { +func closerFunc(close func() error) io.Closer { return &closer{close} } diff --git a/pkg/content/proxy/proxymanifeststore.go b/pkg/content/proxy/manifest_service.go similarity index 65% rename from pkg/content/proxy/proxymanifeststore.go rename to pkg/content/proxy/manifest_service.go index 144de02..837b375 100644 --- a/pkg/content/proxy/proxymanifeststore.go +++ b/pkg/content/proxy/manifest_service.go @@ -2,13 +2,13 @@ package proxy import ( "context" + "fmt" "github.com/distribution/reference" "github.com/go-courier/logr" manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) type proxyManifestService struct { @@ -19,11 +19,11 @@ type proxyManifestService struct { var _ content.ManifestService = &proxyManifestService{} -func (pms proxyManifestService) Delete(ctx context.Context, dgst digest.Digest) error { +func (pms *proxyManifestService) Delete(ctx context.Context, dgst digest.Digest) error { return pms.localManifests.Delete(ctx, dgst) } -func (pms proxyManifestService) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { +func (pms *proxyManifestService) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { info, err := pms.localManifests.Info(ctx, dgst) if err == nil { return info, nil @@ -31,7 +31,7 @@ func (pms proxyManifestService) Info(ctx context.Context, dgst digest.Digest) (* return pms.remoteManifests.Info(ctx, dgst) } -func (pms proxyManifestService) Get(ctx context.Context, dgst digest.Digest) (manifestv1.Manifest, error) { +func (pms *proxyManifestService) Get(ctx context.Context, dgst digest.Digest) (manifestv1.Manifest, error) { manifest, err := pms.localManifests.Get(ctx, dgst) if err != nil { manifest, err = pms.remoteManifests.Get(ctx, dgst) @@ -41,13 +41,13 @@ func (pms proxyManifestService) Get(ctx context.Context, dgst digest.Digest) (ma // store local go func() { if _, err := pms.localManifests.Put(ctx, manifest); err != nil { - logr.FromContext(ctx).Error(errors.Wrapf(err, "store manifest to local failed")) + logr.FromContext(ctx).Error(fmt.Errorf("store manifest to local failed: %w", err)) } }() } return manifest, err } -func (pms proxyManifestService) Put(ctx context.Context, manifest manifestv1.Manifest) (digest.Digest, error) { +func (pms *proxyManifestService) Put(ctx context.Context, manifest manifestv1.Manifest) (digest.Digest, error) { return pms.localManifests.Put(ctx, manifest) } diff --git a/pkg/content/proxy/proxyregistry.go b/pkg/content/proxy/namespace.go similarity index 84% rename from pkg/content/proxy/proxyregistry.go rename to pkg/content/proxy/namespace.go index 2a859f4..0184993 100644 --- a/pkg/content/proxy/proxyregistry.go +++ b/pkg/content/proxy/namespace.go @@ -2,10 +2,8 @@ package proxy import ( "context" - "net/url" "github.com/distribution/reference" - "github.com/google/go-containerregistry/pkg/authn" "github.com/octohelm/crkit/pkg/content" "github.com/octohelm/crkit/pkg/content/remote" ) @@ -16,16 +14,8 @@ type namespace struct { remote content.Namespace } -func NewProxyFallbackRegistry(ctx context.Context, registry content.Namespace, config remote.RegistryConfig) (content.Namespace, error) { - remoteURL, err := url.Parse(config.Endpoint) - if err != nil { - return nil, err - } - - r, err := remote.New(remoteURL.String(), remote.WithAuth(authn.FromConfig(authn.AuthConfig{ - Username: config.Username, - Password: config.Password, - }))) +func NewProxyFallbackRegistry(ctx context.Context, registry content.Namespace, remoteRegistry remote.Registry) (content.Namespace, error) { + r, err := remote.New(ctx, remoteRegistry) if err != nil { return nil, err } diff --git a/pkg/content/proxy/namespace_test.go b/pkg/content/proxy/namespace_test.go new file mode 100644 index 0000000..ed8c522 --- /dev/null +++ b/pkg/content/proxy/namespace_test.go @@ -0,0 +1,155 @@ +package proxy_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/innoai-tech/infra/pkg/configuration" + "github.com/innoai-tech/infra/pkg/otel" + "github.com/octohelm/courier/pkg/courierhttp/handler/httprouter" + "github.com/octohelm/crkit/internal/testingutil" + "github.com/octohelm/crkit/pkg/content" + contentapi "github.com/octohelm/crkit/pkg/content/api" + "github.com/octohelm/crkit/pkg/registryhttp/apis" + "github.com/octohelm/crkit/pkg/uploadcache" + "github.com/octohelm/unifs/pkg/strfmt" + testingx "github.com/octohelm/x/testing" +) + +func TestNamespace(t *testing.T) { + rr := httptest.NewServer(registry.New()) + + c := &struct { + otel.Otel + MemUploadCache uploadcache.MemUploadCache + contentapi.NamespaceProvider + }{} + + tmp := t.TempDir() + t.Cleanup(func() { + _ = os.RemoveAll(tmp) + }) + + endpoint, _ := strfmt.ParseEndpoint("file://" + tmp) + c.Content.Backend = *endpoint + c.Remote.Endpoint = rr.URL + + ctx := testingutil.NewContext(t, c) + i := configuration.ContextInjectorFromContext(ctx) + + h, err := httprouter.New(apis.R, "registry") + testingx.Expect(t, err, testingx.BeNil[error]()) + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if strings.HasSuffix(req.URL.Path, "/") { + req.URL.Path = req.URL.Path[0 : len(req.URL.Path)-1] + } + + fmt.Println(req.Method, req.URL.String()) + + h.ServeHTTP(w, req.WithContext(i.InjectContext(req.Context()))) + })) + + reg, err := name.NewRegistry(strings.TrimPrefix(s.URL, "http://"), name.Insecure) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("push manifest", func(t *testing.T) { + img, err := random.Image(2048, 5) + testingx.Expect(t, err, testingx.BeNil[error]()) + + repo := reg.Repo("test", "manifest") + + ref := repo.Tag("latest") + + err = remote.Push(ref, img) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("then pull and push as v1", func(t *testing.T) { + img1, err := remote.Image(ref) + testingx.Expect(t, err, testingx.BeNil[error]()) + + err = remote.Push(repo.Tag("v1"), img1) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("then could do with tags", func(t *testing.T) { + r, _ := c.Repository(ctx, content.Name("test/manifest")) + + tags, err := r.Tags(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("could listed", func(t *testing.T) { + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "latest", "v1", + })) + }) + + t.Run("could remove", func(t *testing.T) { + err := tags.Untag(ctx, "latest") + testingx.Expect(t, err, testingx.BeNil[error]()) + + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "v1", + })) + }) + }) + }) + }) + + t.Run("push index", func(t *testing.T) { + index, err := random.Index(2048, 5, 5) + testingx.Expect(t, err, testingx.BeNil[error]()) + + repo := reg.Repo("test", "index") + + ref := repo.Tag("latest") + + err = remote.Push(ref, index) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("then pull and push as v1", func(t *testing.T) { + index1, err := remote.Index(ref) + testingx.Expect(t, err, testingx.BeNil[error]()) + + err = remote.Push(repo.Tag("v1"), index1) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("then could do with tags", func(t *testing.T) { + r, _ := c.Repository(ctx, content.Name("test/index")) + + tags, err := r.Tags(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("could listed", func(t *testing.T) { + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "latest", "v1", + })) + }) + + t.Run("could remove", func(t *testing.T) { + err := tags.Untag(ctx, "latest") + testingx.Expect(t, err, testingx.BeNil[error]()) + + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "v1", + })) + }) + }) + }) + }) +} diff --git a/pkg/content/proxy/proxytagservice.go b/pkg/content/proxy/tag_service.go similarity index 92% rename from pkg/content/proxy/proxytagservice.go rename to pkg/content/proxy/tag_service.go index c4298c8..64f7154 100644 --- a/pkg/content/proxy/proxytagservice.go +++ b/pkg/content/proxy/tag_service.go @@ -17,10 +17,6 @@ var _ content.TagService = &proxyTagService{} func (pt *proxyTagService) Get(ctx context.Context, tag string) (*manifestv1.Descriptor, error) { desc, err := pt.remoteTags.Get(ctx, tag) if err == nil { - err := pt.localTags.Tag(ctx, tag, *desc) - if err != nil { - return nil, err - } return desc, nil } diff --git a/pkg/content/remote/authn/authn.go b/pkg/content/remote/authn/authn.go new file mode 100644 index 0000000..b36f5a1 --- /dev/null +++ b/pkg/content/remote/authn/authn.go @@ -0,0 +1,195 @@ +package authn + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "github.com/octohelm/courier/pkg/courierhttp/client" +) + +type Token struct { + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in,omitempty"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token,omitempty"` + Scope string `json:"scope,omitempty"` + + ExpiredAt time.Time `json:"-"` +} + +type TokenGetFunc = func() (*Token, error) + +type Authn struct { + CheckEndpoint string + ClientID string + ClientSecret string + + scopeTokens Map[string, TokenGetFunc] +} + +func (a *Authn) exchangeToken(ctx context.Context, realm *url.URL) (*Token, error) { + c := client.GetShortConnClientContext(ctx) + + req, err := http.NewRequest(http.MethodGet, realm.String(), nil) + if err != nil { + return nil, err + } + + req.SetBasicAuth(a.ClientID, a.ClientSecret) + + resp, err := c.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { + tok := &Token{} + if err := json.Unmarshal(data, tok); err != nil { + return nil, err + } + + if tok.AccessToken == "" { + tok2 := &struct { + Token string `json:"token,omitempty"` + }{} + + if err := json.Unmarshal(data, tok2); err != nil { + return nil, err + } + + tok.AccessToken = tok2.Token + } + + if tok.TokenType == "" { + tok.TokenType = "Bearer" + } + + return tok, nil + } + + return nil, &ErrUnauthorized{ + Reason: errors.New(string(data)), + } +} + +func (a *Authn) getToken(ctx context.Context, name string, actions []string) (*Token, error) { + scope := fmt.Sprintf("repository:%s:%s", name, strings.Join(actions, ",")) + + getToken, loaded := a.scopeTokens.LoadOrStore(scope, sync.OnceValues(func() (*Token, error) { + c := client.GetShortConnClientContext(ctx) + + req, err := http.NewRequest(http.MethodGet, a.CheckEndpoint, nil) + if err != nil { + return nil, err + } + resp, err := c.Do(req) + if err != nil { + return nil, err + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusUnauthorized { + if wwwAuthenticate := resp.Header.Get("WWW-Authenticate"); wwwAuthenticate != "" { + parsed, err := ParseWwwAuthenticate(wwwAuthenticate) + if err == nil && parsed.Params != nil { + realm, ok := parsed.Params["realm"] + if ok && realm != "" { + realmUrl, err := url.Parse(realm) + if err == nil { + q := &url.Values{} + q.Set("scope", scope) + + for k, v := range parsed.Params { + if k != "realm" { + q.Set(k, v) + } + } + realmUrl.RawQuery = q.Encode() + + return a.exchangeToken(context.Background(), realmUrl) + } + } + } + } + } + + return nil, &ErrUnauthorized{ + Reason: errors.New(string(data)), + } + })) + + tok, err := getToken() + if err != nil { + return nil, err + } + if !loaded { + tok.ExpiredAt = time.Now().Add(time.Duration(tok.ExpiresIn-60) * time.Second) + } + + if tok.ExpiredAt.Before(time.Now()) { + a.scopeTokens.Delete(scope) + // retry + return a.getToken(ctx, name, actions) + } + + return tok, nil +} + +func (a *Authn) AsHttpTransport() client.HttpTransport { + return client.HttpTransportFunc(func(req *http.Request, next client.RoundTrip) (*http.Response, error) { + repository := "" + actions := make([]string, 0) + + l := strings.Index(req.URL.Path, "/v2/") + if l > -1 { + path := req.URL.Path[l+len("/v2/"):] + + for _, v := range []string{ + "/manifests/", + "/blobs/", + "/tags/", + } { + if r := strings.Index(path, v); r > 0 { + repository = path[0:r] + + actions = append(actions, "pull") + + switch req.Method { + case http.MethodPut, http.MethodPost: + actions = append(actions, "push") + } + + break + } + } + } + + tok, err := a.getToken(req.Context(), repository, actions) + if err != nil { + return nil, err + } + + if tok != nil { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tok.AccessToken)) + } + + return next(req) + }) +} diff --git a/pkg/content/remote/authn/errors.go b/pkg/content/remote/authn/errors.go new file mode 100644 index 0000000..d44663d --- /dev/null +++ b/pkg/content/remote/authn/errors.go @@ -0,0 +1,16 @@ +package authn + +import ( + "fmt" + + "github.com/octohelm/courier/pkg/statuserror" +) + +type ErrUnauthorized struct { + statuserror.Unauthorized + Reason error +} + +func (e *ErrUnauthorized) Error() string { + return fmt.Sprintf("Unauthorized: %s", e.Reason) +} diff --git a/pkg/content/remote/authn/map.go b/pkg/content/remote/authn/map.go new file mode 100644 index 0000000..3e41e38 --- /dev/null +++ b/pkg/content/remote/authn/map.go @@ -0,0 +1,56 @@ +package authn + +import "sync" + +type Map[K comparable, V any] struct { + m sync.Map +} + +func (m *Map[K, V]) typeAssertion(v any, result bool) (V, bool) { + if vv, ok := v.(V); ok { + return vv, result + } + return *new(V), result +} + +func (m *Map[K, V]) LoadOrStore(k K, value V) (V, bool) { + return m.typeAssertion(m.m.LoadOrStore(k, value)) +} + +func (m *Map[K, V]) Delete(k K) { + m.m.Delete(k) +} + +func (m *Map[K, V]) Load(k K) (V, bool) { + return m.typeAssertion(m.m.Load(k)) +} + +func (m *Map[K, V]) LoadAndDelete(k K) (V, bool) { + return m.typeAssertion(m.m.LoadAndDelete(k)) +} + +func (m *Map[K, V]) Swap(k K, value V) (V, bool) { + return m.typeAssertion(m.m.Swap(k, value)) +} + +func (m *Map[K, V]) CompareAndSwap(k K, old V, new V) bool { + return m.m.CompareAndSwap(k, old, new) +} + +func (m *Map[K, V]) CompareAndDelete(k K, old V) bool { + return m.m.CompareAndDelete(k, old) +} + +func (m *Map[K, V]) Store(k K, v V) { + m.m.Store(k, v) +} + +func (m *Map[K, V]) Clear() { + m.m.Clear() +} + +func (m *Map[K, V]) Range(r func(k K, v V) bool) { + m.m.Range(func(key, value any) bool { + return r(key.(K), value.(V)) + }) +} diff --git a/pkg/content/remote/authn/www_authenticate.go b/pkg/content/remote/authn/www_authenticate.go new file mode 100644 index 0000000..1bba24d --- /dev/null +++ b/pkg/content/remote/authn/www_authenticate.go @@ -0,0 +1,94 @@ +package authn + +import ( + "bytes" + "errors" + "sort" + "strconv" + "strings" + "text/scanner" + + "golang.org/x/exp/maps" +) + +var errInvalidWwwAuthenticate = errors.New("invalid www-authenticate") + +func ParseWwwAuthenticate(str string) (*WwwAuthenticate, error) { + authType, paramsStr, ok := strings.Cut(str, " ") + if !ok { + return nil, errInvalidWwwAuthenticate + } + + wwwAuth := &WwwAuthenticate{ + AuthType: authType, + } + + if paramsStr != "" { + wwwAuth.Params = map[string]string{} + + s := &scanner.Scanner{} + s.Init(bytes.NewBufferString(paramsStr)) + + s.Whitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' + + kv := [2]string{} + i := 0 + + commit := func() { + i = 0 + + if kv[0] != "" { + if kv[1] != "" && kv[1][0] == '"' { + v, err := strconv.Unquote(kv[1]) + if err == nil { + wwwAuth.Params[kv[0]] = v + return + } + } + + wwwAuth.Params[kv[0]] = kv[1] + } + } + + for t := s.Scan(); t != scanner.EOF; t = s.Scan() { + switch t { + case ',', ' ': + commit() + continue + case '=': + i = 1 + continue + } + kv[i] = s.TokenText() + } + + commit() + } + + return wwwAuth, nil +} + +type WwwAuthenticate struct { + AuthType string + Params map[string]string +} + +func (v WwwAuthenticate) String() string { + b := &strings.Builder{} + b.WriteString(v.AuthType) + b.WriteString(" ") + + keys := maps.Keys(v.Params) + sort.Strings(keys) + + for i, k := range keys { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(k) + b.WriteString("=") + b.WriteString(strconv.Quote(v.Params[k])) + } + + return b.String() +} diff --git a/pkg/content/remote/authn/www_authenticate_test.go b/pkg/content/remote/authn/www_authenticate_test.go new file mode 100644 index 0000000..6dff37b --- /dev/null +++ b/pkg/content/remote/authn/www_authenticate_test.go @@ -0,0 +1,23 @@ +package authn + +import ( + "testing" + + testingx "github.com/octohelm/x/testing" +) + +func TestParseWwwAuthenticate(t *testing.T) { + a := &WwwAuthenticate{ + AuthType: "Bearer", + Params: map[string]string{ + "realm": "http://localhost/token", + "service": "test", + }, + } + + testingx.Expect(t, a.String(), testingx.Be(`Bearer realm="http://localhost/token", service="test"`)) + + parsed, err := ParseWwwAuthenticate(`Bearer realm="http://localhost/token" service=test`) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, parsed, testingx.Equal(a)) +} diff --git a/pkg/content/remote/authn/zz_generated.runtimedoc.go b/pkg/content/remote/authn/zz_generated.runtimedoc.go new file mode 100644 index 0000000..8453a37 --- /dev/null +++ b/pkg/content/remote/authn/zz_generated.runtimedoc.go @@ -0,0 +1,81 @@ +/* +Package authn GENERATED BY gengo:runtimedoc +DON'T EDIT THIS FILE +*/ +package authn + +// nolint:deadcode,unused +func runtimeDoc(v any, names ...string) ([]string, bool) { + if c, ok := v.(interface { + RuntimeDoc(names ...string) ([]string, bool) + }); ok { + return c.RuntimeDoc(names...) + } + return nil, false +} + +func (v Authn) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "CheckEndpoint": + return []string{}, true + case "ClientID": + return []string{}, true + case "ClientSecret": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} + +func (v ErrUnauthorized) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Reason": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} + +func (v Token) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "TokenType": + return []string{}, true + case "ExpiresIn": + return []string{}, true + case "AccessToken": + return []string{}, true + case "RefreshToken": + return []string{}, true + case "Scope": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} + +func (v WwwAuthenticate) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "AuthType": + return []string{}, true + case "Params": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} diff --git a/pkg/content/remote/blob_store.go b/pkg/content/remote/blob_store.go new file mode 100644 index 0000000..b637305 --- /dev/null +++ b/pkg/content/remote/blob_store.go @@ -0,0 +1,196 @@ +package remote + +import ( + "bytes" + "cmp" + "context" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/distribution/reference" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" + "github.com/octohelm/crkit/pkg/content" + "github.com/octohelm/crkit/pkg/registryhttp/apis/registry" + "github.com/opencontainers/go-digest" +) + +var _ content.BlobStore = &blobStore{} + +type blobStore struct { + named reference.Named + client *Client +} + +func (bs *blobStore) Remove(ctx context.Context, dgst digest.Digest) error { + req := ®istry.DeleteBlob{} + req.Name = content.Name(bs.named.Name()) + req.Digest = content.Digest(dgst) + + _, _, err := Do(ctx, bs.client, req) + return err +} + +func (bs *blobStore) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { + req := ®istry.HeadBlob{} + req.Name = content.Name(bs.named.Name()) + req.Digest = content.Digest(dgst) + + _, meta, err := Do(ctx, bs.client, req) + if err != nil { + return nil, err + } + + i, _ := strconv.ParseInt(meta.Get("Content-Length"), 64, 10) + + return &manifestv1.Descriptor{ + MediaType: meta.Get("Content-Type"), + Digest: digest.Digest(meta.Get("Docker-Content-Digest")), + Size: i, + }, nil +} + +func (bs *blobStore) Open(ctx context.Context, dgst digest.Digest) (r io.ReadCloser, err error) { + req := ®istry.GetBlob{} + req.Name = content.Name(bs.named.Name()) + req.Digest = content.Digest(dgst) + + pr, pw := io.Pipe() + + go func() { + defer pw.Close() + + if _, err := bs.client.Do(ctx, req).Into(pw); err != nil { + return + } + }() + + return pr, nil +} + +func (bs *blobStore) Writer(ctx context.Context) (content.BlobWriter, error) { + req := ®istry.UploadBlob{} + req.Name = content.Name(bs.named.Name()) + + _, meta, err := Do(ctx, bs.client, req) + if err != nil { + return nil, err + } + + location := meta.Get("Location") + + return &blobWriter{ + id: digest.FromString(location).Hex(), + digester: digest.SHA256.Digester(), + blobStore: bs, + location: location, + }, nil +} + +var _ content.BlobWriter = &blobWriter{} + +type blobWriter struct { + *blobStore + + id string + location string + + digester digest.Digester + offset int64 +} + +func (b *blobWriter) ID() string { + return b.id +} + +func (b *blobWriter) Close() error { + req := ®istry.CancelUploadBlob{} + req.Name = content.Name(b.named.Name()) + req.ID = b.id + + return nil +} + +func (b *blobWriter) Size(ctx context.Context) int64 { + return b.offset +} + +func (b *blobWriter) Digest(ctx context.Context) digest.Digest { + return b.digester.Digest() +} + +func (b *blobWriter) endpoint() string { + location := b.location + if strings.HasPrefix(location, "/") { + location = b.client.Endpoint + location + } + return location +} + +func (b *blobWriter) Write(p []byte) (int, error) { + n := len(p) + + req, err := http.NewRequest("PATCH", b.endpoint(), io.NopCloser(bytes.NewBuffer(p[:n]))) + if err != nil { + return -1, err + } + + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", fmt.Sprintf("%d-%d", b.offset, b.offset+int64(n))) + req.Header.Set("Content-Length", fmt.Sprintf("%d", n)) + + meta, err := b.client.Do(context.Background(), req).Into(nil) + if err != nil { + return -1, err + } + + b.offset += int64(n) + b.digester.Hash().Write(p[:n]) + + b.location = meta.Get("Location") + + return n, nil +} + +func (b *blobWriter) Commit(ctx context.Context, expect manifestv1.Descriptor) (*manifestv1.Descriptor, error) { + dgst := b.Digest(ctx) + + req, err := http.NewRequest("PUT", b.endpoint(), nil) + if err != nil { + return nil, err + } + + q := &url.Values{} + q.Set("digest", cmp.Or(string(expect.Digest), string(dgst))) + req.URL.RawQuery = q.Encode() + + if _, err := b.client.Do(ctx, req).Into(nil); err != nil { + return nil, err + } + + d := &manifestv1.Descriptor{} + d.Digest = dgst + d.Size = b.Size(ctx) + + if expect.Size > 0 { + if d.Size != expect.Size { + return nil, &content.ErrBlobInvalidLength{ + Reason: fmt.Sprintf("expect %d, but got %d", expect.Size, d.Size), + } + } + } + + if expect.Digest != "" { + if d.Digest != expect.Digest { + return nil, &content.ErrBlobInvalidDigest{ + Digest: d.Digest, + Reason: fmt.Errorf("not match %s", expect.Digest), + } + } + } + + return d, nil +} diff --git a/pkg/content/remote/blobstore.go b/pkg/content/remote/blobstore.go deleted file mode 100644 index 2f44f01..0000000 --- a/pkg/content/remote/blobstore.go +++ /dev/null @@ -1,267 +0,0 @@ -package remote - -import ( - "context" - "fmt" - "io" - "net/http" - "sync" - "time" - - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/partial" - "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/google/go-containerregistry/pkg/v1/types" - manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" - "github.com/octohelm/crkit/pkg/content" - "github.com/opencontainers/go-digest" - "github.com/pkg/errors" -) - -var _ content.BlobStore = &blobStore{} - -type blobStore struct { - *repository -} - -func (b *blobStore) normalizeError(err error) error { - terr := &transport.Error{} - if errors.As(err, &terr) { - if terr.StatusCode == http.StatusNotFound { - return content.ErrBlobUnknown - } - } - return err -} - -func (b *blobStore) Remove(ctx context.Context, dgst digest.Digest) error { - return errors.New("not supported") -} - -func (b *blobStore) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { - dd, err := b.puller.Layer(ctx, b.repo.Digest(dgst.String())) - if err != nil { - return nil, b.normalizeError(err) - } - - d, err := partial.Descriptor(dd) - if err != nil { - return nil, b.normalizeError(err) - } - - return &manifestv1.Descriptor{ - MediaType: string(d.MediaType), - Digest: digest.NewDigestFromHex(d.Digest.Algorithm, d.Digest.Hex), - Size: d.Size, - Annotations: d.Annotations, - }, nil -} - -func (b *blobStore) Open(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) { - d, err := b.puller.Layer(ctx, b.repo.Digest(dgst.String())) - if err != nil { - return nil, b.normalizeError(err) - } - - r, err := d.Compressed() - if err != nil { - return nil, b.normalizeError(err) - } - - return &nopSeeker{ReadCloser: r}, nil -} - -func (b *blobStore) Writer(ctx context.Context) (content.BlobWriter, error) { - r, w := io.Pipe() - - bw := &blobWriter{ - ctx: ctx, - b: b, - r: r, - w: w, - startedAt: time.Now(), - } - - return bw, nil -} - -type nopSeeker struct { - io.ReadCloser -} - -func (nopSeeker) Seek(offset int64, whence int) (int64, error) { - return 0, nil -} - -var _ content.BlobWriter = &blobWriter{} - -type blobWriter struct { - ctx context.Context - b *blobStore - - layer v1.Layer - - r *io.PipeReader - w *io.PipeWriter - - *sizedDigestWriter - - startedAt time.Time - err error - - once sync.Once - wg sync.WaitGroup - cancel func() -} - -func (b *blobWriter) Close() error { - return b.w.Close() -} - -func (b *blobWriter) Size(ctx context.Context) int64 { - b.initIfNeed() - - return b.sizedDigestWriter.written -} - -func (b *blobWriter) Digest(ctx context.Context) digest.Digest { - b.initIfNeed() - - return b.sizedDigestWriter.Digest() -} - -func (b *blobWriter) initIfNeed() { - b.once.Do(func() { - b.sizedDigestWriter = &sizedDigestWriter{ - Digester: digest.SHA256.Digester(), - Writer: b.w, - } - - ctx, cancel := context.WithCancel(b.ctx) - b.cancel = cancel - - b.wg.Add(1) - go func() { - defer b.wg.Done() - - if err := b.b.pusher.Upload(ctx, b.b.repo, b.AsLayer()); err != nil { - b.err = err - } - }() - }) -} - -func (b *blobWriter) ReadFrom(r io.Reader) (n int64, err error) { - b.initIfNeed() - - return io.Copy(b.sizedDigestWriter, r) -} - -func (b *blobWriter) Write(p []byte) (int, error) { - b.initIfNeed() - - return b.sizedDigestWriter.Write(p) -} - -func (b *blobWriter) StartedAt() time.Time { - return b.startedAt -} - -func (b *blobWriter) ID() string { - return fmt.Sprintf("%d", b.startedAt.Unix()) -} - -func (b *blobWriter) Commit(ctx context.Context, expect manifestv1.Descriptor) (*manifestv1.Descriptor, error) { - if err := b.Close(); err != nil { - return nil, err - } - - b.wg.Wait() - - if err := b.err; err != nil { - return nil, err - } - - d := &manifestv1.Descriptor{} - d.Size = b.Size(ctx) - d.Digest = b.Digest(ctx) - - if expect.Size > 0 { - if d.Size != expect.Size { - return nil, errors.Wrapf(content.ErrBlobInvalidLength, "expect %d, but got %d", expect.Size, d.Size) - } - } - - if expect.Digest != "" { - if d.Digest != expect.Digest { - return nil, &content.ErrBlobInvalidDigest{ - Digest: d.Digest, - Reason: fmt.Errorf("not match %s", expect.Digest), - } - } - } - - return d, nil -} - -func (b *blobWriter) Cancel(ctx context.Context) error { - if b.cancel != nil { - b.cancel() - } - return b.Close() -} - -func (b *blobWriter) AsLayer() v1.Layer { - return &proxyLayer{ - b: b, - } -} - -type proxyLayer struct { - b *blobWriter -} - -func (a *proxyLayer) Digest() (v1.Hash, error) { - d := a.b.Digest(context.Background()) - return v1.Hash{Algorithm: string(d.Algorithm()), Hex: d.Hex()}, nil -} - -func (a *proxyLayer) Size() (int64, error) { - return a.b.Size(context.Background()), nil -} - -func (a *proxyLayer) DiffID() (v1.Hash, error) { - return v1.Hash{}, nil -} - -func (a *proxyLayer) MediaType() (types.MediaType, error) { - return types.DockerLayer, nil -} - -func (a *proxyLayer) Uncompressed() (io.ReadCloser, error) { - return a.b.r, nil -} - -func (a *proxyLayer) Compressed() (io.ReadCloser, error) { - return a.Uncompressed() -} - -type sizedDigestWriter struct { - io.Writer - digest.Digester - written int64 -} - -func (s *sizedDigestWriter) Write(p []byte) (n int, err error) { - if _, err := s.Hash().Write(p); err != nil { - return 0, err - } - - n, err = s.Writer.Write(p) - if err != nil { - return 0, err - } - s.written += int64(n) - - return -} diff --git a/pkg/content/remote/client.go b/pkg/content/remote/client.go new file mode 100644 index 0000000..6eb5cf1 --- /dev/null +++ b/pkg/content/remote/client.go @@ -0,0 +1,68 @@ +package remote + +import ( + "context" + "net/url" + + "github.com/innoai-tech/infra/pkg/http/middleware" + "github.com/octohelm/crkit/pkg/content/remote/authn" + + "github.com/octohelm/courier/pkg/courier" + "github.com/octohelm/courier/pkg/courierhttp/client" +) + +type Client struct { + Registry + + c *client.Client +} + +func (c *Client) Init(ctx context.Context) error { + if c.c == nil { + u, err := url.Parse(c.Endpoint) + if err != nil { + return err + } + u.Path = "/v2" + + if c.Username != "" { + a := &authn.Authn{} + a.CheckEndpoint = u.String() + a.ClientID = c.Username + a.ClientSecret = c.Password + + c.c = &client.Client{ + Endpoint: u.String(), + HttpTransports: []client.HttpTransport{ + middleware.NewLogRoundTripper(), + a.AsHttpTransport(), + }, + } + } else { + c.c = &client.Client{ + Endpoint: u.String(), + HttpTransports: []client.HttpTransport{ + middleware.NewLogRoundTripper(), + }, + } + } + } + + return nil +} + +func (c *Client) Do(ctx context.Context, req any, metas ...courier.Metadata) courier.Result { + return c.c.Do(ctx, req, metas...) +} + +func Do[Data any, Op interface{ ResponseData() *Data }](ctx context.Context, c courier.Client, req Op, metas ...courier.Metadata) (*Data, courier.Metadata, error) { + resp := new(Data) + + if _, ok := any(resp).(*courier.NoContent); ok { + meta, err := c.Do(ctx, req, metas...).Into(nil) + return resp, meta, err + } + + meta, err := c.Do(ctx, req, metas...).Into(resp) + return resp, meta, err +} diff --git a/pkg/content/remote/manifest_service.go b/pkg/content/remote/manifest_service.go new file mode 100644 index 0000000..65c7e9d --- /dev/null +++ b/pkg/content/remote/manifest_service.go @@ -0,0 +1,86 @@ +package remote + +import ( + "context" + "strconv" + "strings" + + "github.com/distribution/reference" + "github.com/octohelm/crkit/pkg/registryhttp/apis/registry" + "golang.org/x/exp/maps" + + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" + "github.com/octohelm/crkit/pkg/content" + "github.com/opencontainers/go-digest" +) + +type manifestService struct { + named reference.Named + client *Client +} + +var _ content.ManifestService = &manifestService{} + +func (ms *manifestService) Delete(ctx context.Context, dgst digest.Digest) error { + req := ®istry.DeleteManifest{} + req.Name = content.Name(ms.named.Name()) + req.Reference = content.Reference(dgst.String()) + + _, _, err := Do(ctx, ms.client, req) + return err +} + +func (ms *manifestService) Put(ctx context.Context, m manifestv1.Manifest) (digest.Digest, error) { + req := ®istry.PutManifest{} + req.Name = content.Name(ms.named.Name()) + p, err := manifestv1.From(m) + if err != nil { + return "", err + } + + _, dgst, err := p.Payload() + if err != nil { + return "", err + } + req.Manifest = *p + req.Reference = content.Reference(dgst.String()) + + _, meta, err := Do(ctx, ms.client, req) + if err != nil { + return "", err + } + return digest.Digest(meta.Get("Docker-Content-Digest")), nil +} + +func (ms *manifestService) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { + req := ®istry.HeadManifest{} + req.Name = content.Name(ms.named.Name()) + req.Accept = strings.Join(maps.Keys((&manifestv1.Payload{}).Mapping()), ",") + req.Reference = content.Reference(dgst.String()) + + _, meta, err := Do(ctx, ms.client, req) + if err != nil { + return nil, err + } + + i, _ := strconv.ParseInt(meta.Get("Content-Length"), 64, 10) + + return &manifestv1.Descriptor{ + MediaType: meta.Get("Content-Type"), + Digest: digest.Digest(meta.Get("Docker-Content-Digest")), + Size: i, + }, nil +} + +func (ms *manifestService) Get(ctx context.Context, dgst digest.Digest) (manifestv1.Manifest, error) { + req := ®istry.GetManifest{} + req.Accept = strings.Join(maps.Keys((&manifestv1.Payload{}).Mapping()), ",") + req.Name = content.Name(ms.named.Name()) + req.Reference = content.Reference(dgst.String()) + + p, _, err := Do(ctx, ms.client, req) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/pkg/content/remote/manifeststore.go b/pkg/content/remote/manifeststore.go deleted file mode 100644 index 7f21c6d..0000000 --- a/pkg/content/remote/manifeststore.go +++ /dev/null @@ -1,98 +0,0 @@ -package remote - -import ( - "context" - "net/http" - - manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" - - "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/octohelm/crkit/pkg/content" - "github.com/pkg/errors" - - "github.com/google/go-containerregistry/pkg/name" - "github.com/opencontainers/go-digest" -) - -type manifestService struct { - *repository -} - -var _ content.ManifestService = &manifestService{} - -func (b *manifestService) normalizeError(dgst digest.Digest, err error) error { - terr := &transport.Error{} - if errors.As(err, &terr) { - if terr.StatusCode == http.StatusNotFound { - return &content.ErrManifestBlobUnknown{ - Digest: dgst, - } - } - } - return err -} - -func (ms *manifestService) Delete(ctx context.Context, dgst digest.Digest) error { - err := ms.pusher.Delete(ctx, ms.repo.Digest(dgst.String())) - return ms.normalizeError(dgst, err) -} - -func (ms *manifestService) Put(ctx context.Context, m manifestv1.Manifest) (digest.Digest, error) { - p, err := manifestv1.From(m) - if err != nil { - return "", err - } - - raw, dgst, err := p.Payload() - if err != nil { - return "", err - } - - var ref name.Reference = ms.repo.Digest(dgst.String()) - - if err := ms.pusher.Push(ctx, ref, &manifest{mediaType: m.Type(), raw: raw}); err != nil { - return "", ms.normalizeError(dgst, err) - } - - return dgst, nil -} - -func (ms *manifestService) Info(ctx context.Context, dgst digest.Digest) (*manifestv1.Descriptor, error) { - d, err := ms.puller.Get(ctx, ms.repo.Digest(dgst.String())) - if err != nil { - return nil, ms.normalizeError(dgst, err) - } - return &manifestv1.Descriptor{ - MediaType: string(d.MediaType), - Digest: digest.NewDigestFromHex(d.Digest.Algorithm, d.Digest.Hex), - Size: d.Size, - Annotations: d.Annotations, - }, nil -} - -func (ms *manifestService) Get(ctx context.Context, dgst digest.Digest) (manifestv1.Manifest, error) { - d, err := ms.puller.Get(ctx, ms.repo.Digest(dgst.String())) - if err != nil { - return nil, ms.normalizeError(dgst, err) - } - - payload := &manifestv1.Payload{} - if err := payload.UnmarshalJSON(d.Manifest); err != nil { - return nil, &content.ErrManifestUnverified{} - } - return payload, nil -} - -type manifest struct { - mediaType string - raw []byte -} - -func (m *manifest) MediaType() (types.MediaType, error) { - return types.MediaType(m.mediaType), nil -} - -func (m *manifest) RawManifest() ([]byte, error) { - return m.raw, nil -} diff --git a/pkg/content/remote/namespace.go b/pkg/content/remote/namespace.go index f294b8e..69a4216 100644 --- a/pkg/content/remote/namespace.go +++ b/pkg/content/remote/namespace.go @@ -2,125 +2,55 @@ package remote import ( "context" - "net/http" "net/url" - "path" "strings" - "github.com/innoai-tech/infra/pkg/http/middleware" - "github.com/octohelm/crkit/pkg/content" - "github.com/distribution/reference" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/octohelm/crkit/pkg/content" ) -type RegistryConfig struct { - // Remote container registry endpoint - Endpoint string `flag:",omitempty"` - // Remote container registry username - Username string `flag:",omitempty"` - // Remote container registry password - Password string `flag:",omitempty,secret"` -} - -type Option = func(n *namespace) - -func WithAuth(auth authn.Authenticator) Option { - return func(n *namespace) { - n.auth = auth - } -} - -type RoundTripperWrapper = func(next http.RoundTripper) http.RoundTripper - -func WithRoundTripperWrappers(wrappers ...RoundTripperWrapper) Option { - return func(n *namespace) { - transport := n.transport - for _, b := range wrappers { - transport = b(transport) - } - n.transport = transport +func New(ctx context.Context, registry Registry) (content.Namespace, error) { + n := &namespace{ + client: &Client{ + Registry: registry, + }, } -} -func New(endpoint string, options ...Option) (content.Namespace, error) { - u, err := url.Parse(endpoint) + remoteURI, err := url.Parse(registry.Endpoint) if err != nil { return nil, err } - n := &namespace{ - endpoint: u, - transport: middleware.NewLogRoundTripper()(remote.DefaultTransport), - } + n.remoteURI = remoteURI - for _, opt := range options { - opt(n) + if err := n.client.Init(ctx); err != nil { + return nil, err } return n, nil } type namespace struct { - endpoint *url.URL - auth authn.Authenticator - transport http.RoundTripper + client *Client + remoteURI *url.URL } func (n *namespace) Repository(ctx context.Context, named reference.Named) (content.Repository, error) { - repoName := named.Name() - if n.endpoint.Host != "docker.io" && !strings.HasPrefix(repoName, n.endpoint.Host) { - repoName = path.Join(n.endpoint.Host, repoName) - } - - opts := make([]name.Option, 0) + name := named.Name() - if n.endpoint.Scheme == "http" { - opts = append(opts, name.Insecure) - } - - repo, err := name.NewRepository(repoName, opts...) - if err != nil { - return nil, err - } - - pusher, err := remote.NewPusher( - remote.WithContext(ctx), - remote.WithAuth(n.auth), - remote.WithTransport(n.transport), - ) - if err != nil { - return nil, err - } - - puller, err := remote.NewPuller( - remote.WithContext(ctx), - remote.WithAuth(n.auth), - remote.WithTransport(n.transport), - ) - if err != nil { - return nil, err + if strings.HasPrefix(name, n.remoteURI.Host) { + named = content.Name(name[len(n.remoteURI.Host)+1:]) } return &repository{ - namespace: n, - repo: repo, - named: named, - pusher: pusher, - puller: puller, + named: named, + client: n.client, }, nil } type repository struct { - namespace *namespace - - named reference.Named - repo name.Repository - - pusher *remote.Pusher - puller *remote.Puller + client *Client + named reference.Named } func (r *repository) Named() reference.Named { @@ -128,13 +58,22 @@ func (r *repository) Named() reference.Named { } func (r *repository) Manifests(ctx context.Context) (content.ManifestService, error) { - return &manifestService{repository: r}, nil + return &manifestService{ + named: r.named, + client: r.client, + }, nil } func (r *repository) Blobs(ctx context.Context) (content.BlobStore, error) { - return &blobStore{repository: r}, nil + return &blobStore{ + named: r.named, + client: r.client, + }, nil } func (r *repository) Tags(ctx context.Context) (content.TagService, error) { - return &tagService{repository: r}, nil + return &tagService{ + named: r.named, + client: r.client, + }, nil } diff --git a/pkg/content/remote/namespace_test.go b/pkg/content/remote/namespace_test.go new file mode 100644 index 0000000..53ba49a --- /dev/null +++ b/pkg/content/remote/namespace_test.go @@ -0,0 +1,140 @@ +package remote_test + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-json-experiment/json" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/octohelm/courier/pkg/courierhttp/handler/httprouter" + "github.com/octohelm/crkit/pkg/content" + contentremote "github.com/octohelm/crkit/pkg/content/remote" + "github.com/octohelm/crkit/pkg/content/remote/authn" + "github.com/octohelm/crkit/pkg/registryhttp/apis" + "github.com/octohelm/crkit/pkg/uploadcache" + testingx "github.com/octohelm/x/testing" +) + +func TestNamespace(t *testing.T) { + rh := registry.New() + + var remoteRegistry *httptest.Server + + remoteRegistry = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + fmt.Println("origin", req.Method, req.URL.String()) + + if req.URL.Path == "/auth/token" { + tok := &authn.Token{} + tok.TokenType = "Bearer" + tok.AccessToken = "test" + tok.ExpiresIn = 1800 + + rw.WriteHeader(http.StatusOK) + _ = json.MarshalWrite(rw, tok) + + return + } + + auth := req.Header.Get("Authorization") + if auth == "" { + rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Bearer realm=%q,service=%s", remoteRegistry.URL+"/auth/token", "test")) + rw.WriteHeader(http.StatusUnauthorized) + _, _ = rw.Write(nil) + return + } + + rh.ServeHTTP(rw, req) + })) + + ctx := context.Background() + + namespace, err := contentremote.New(ctx, contentremote.Registry{ + Endpoint: remoteRegistry.URL, + Username: "test", + Password: "test", + }) + testingx.Expect(t, err, testingx.BeNil[error]()) + + uploadCache := &uploadcache.MemUploadCache{} + uploadCache.SetDefaults() + err = uploadCache.Init(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + + h, err := httprouter.New(apis.R, "registry") + testingx.Expect(t, err, testingx.BeNil[error]()) + + registryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if strings.HasSuffix(req.URL.Path, "/") { + req.URL.Path = req.URL.Path[0 : len(req.URL.Path)-1] + } + + fmt.Println("server", req.Method, req.URL.String()) + + ctx := content.NamespaceInjectContext(req.Context(), namespace) + ctx = uploadcache.UploadCacheInjectContext(ctx, uploadCache) + + h.ServeHTTP(w, req.WithContext(ctx)) + })) + + reg, err := name.NewRegistry( + strings.TrimPrefix(registryServer.URL, "http://"), + name.Insecure, + ) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("push manifest", func(t *testing.T) { + img, err := random.Image(2048, 1) + testingx.Expect(t, err, testingx.BeNil[error]()) + + repo := reg.Repo("test", "x") + + ref := repo.Tag("latest") + + t.Run("could push", func(t *testing.T) { + err = remote.Push(ref, img) + testingx.Expect(t, err, testingx.BeNil[error]()) + }) + + t.Run("then pull and push as v1", func(t *testing.T) { + img1, err := remote.Image(ref) + testingx.Expect(t, err, testingx.BeNil[error]()) + + err = remote.Push(repo.Tag("v1"), img1) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("then could do with tags", func(t *testing.T) { + r, _ := namespace.Repository(ctx, content.Name("test/x")) + + tags, err := r.Tags(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + + t.Run("could listed", func(t *testing.T) { + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "latest", "v1", + })) + }) + + t.Run("could remove", func(t *testing.T) { + err := tags.Untag(ctx, "latest") + testingx.Expect(t, err, testingx.BeNil[error]()) + + tagList, err := tags.All(ctx) + testingx.Expect(t, err, testingx.BeNil[error]()) + testingx.Expect(t, tagList, testingx.Equal([]string{ + "v1", + })) + }) + }) + }) + }) +} diff --git a/pkg/content/remote/registry.go b/pkg/content/remote/registry.go new file mode 100644 index 0000000..f73b7ff --- /dev/null +++ b/pkg/content/remote/registry.go @@ -0,0 +1,10 @@ +package remote + +type Registry struct { + // Remote container registry endpoint + Endpoint string `flag:",omitempty"` + // Remote container registry username + Username string `flag:",omitempty"` + // Remote container registry password + Password string `flag:",omitempty,secret"` +} diff --git a/pkg/content/remote/tag_service.go b/pkg/content/remote/tag_service.go new file mode 100644 index 0000000..b8f90bd --- /dev/null +++ b/pkg/content/remote/tag_service.go @@ -0,0 +1,81 @@ +package remote + +import ( + "context" + "strconv" + + "github.com/opencontainers/go-digest" + + "github.com/distribution/reference" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" + "github.com/octohelm/crkit/pkg/content" + "github.com/octohelm/crkit/pkg/registryhttp/apis/registry" +) + +type tagService struct { + named reference.Named + client *Client +} + +var _ content.TagService = &tagService{} + +func (ts *tagService) Get(ctx context.Context, tag string) (*manifestv1.Descriptor, error) { + req := ®istry.HeadManifest{} + req.Name = content.Name(ts.named.Name()) + req.Reference = content.Reference(tag) + + _, meta, err := Do(ctx, ts.client, req) + if err != nil { + return nil, err + } + + i, _ := strconv.ParseInt(meta.Get("Content-Length"), 64, 10) + + return &manifestv1.Descriptor{ + MediaType: meta.Get("Content-Type"), + Digest: digest.Digest(meta.Get("Docker-Content-Digest")), + Size: i, + }, nil +} + +func (ts *tagService) Tag(ctx context.Context, tag string, desc manifestv1.Descriptor) error { + resolve := ®istry.GetManifest{} + resolve.Name = content.Name(ts.named.Name()) + resolve.Reference = content.Reference(desc.Digest.String()) + + m, _, err := Do(ctx, ts.client, resolve) + if err != nil { + return err + } + + put := ®istry.PutManifest{} + put.Name = content.Name(ts.named.Name()) + put.Reference = content.Reference(tag) + put.Manifest = *m + + if _, _, err := Do(ctx, ts.client, put); err != nil { + return err + } + return nil +} + +func (ts *tagService) Untag(ctx context.Context, tag string) error { + req := ®istry.DeleteManifest{} + req.Name = content.Name(ts.named.Name()) + req.Reference = content.Reference(tag) + + _, _, err := Do(ctx, ts.client, req) + return err +} + +func (ts *tagService) All(ctx context.Context) ([]string, error) { + resolve := ®istry.ListTag{} + resolve.Name = content.Name(ts.named.Name()) + + list, _, err := Do(ctx, ts.client, resolve) + if err != nil { + return nil, err + } + + return list.Tags, nil +} diff --git a/pkg/content/remote/tagservice.go b/pkg/content/remote/tagservice.go deleted file mode 100644 index 40140ec..0000000 --- a/pkg/content/remote/tagservice.go +++ /dev/null @@ -1,60 +0,0 @@ -package remote - -import ( - "context" - "net/http" - - manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" - - "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/octohelm/crkit/pkg/content" - "github.com/opencontainers/go-digest" - "github.com/pkg/errors" -) - -type tagService struct { - *repository -} - -var _ content.TagService = &tagService{} - -func (b *tagService) normalizeError(tag string, err error) error { - terr := &transport.Error{} - if errors.As(err, &terr) { - if terr.StatusCode == http.StatusNotFound { - return &content.ErrTagUnknown{ - Tag: tag, - } - } - } - return err -} - -func (pt *tagService) Get(ctx context.Context, tag string) (*manifestv1.Descriptor, error) { - d, err := pt.puller.Get(ctx, pt.repo.Tag(tag)) - if err != nil { - return nil, pt.normalizeError(tag, err) - } - return &manifestv1.Descriptor{ - MediaType: string(d.MediaType), - Digest: digest.NewDigestFromHex(d.Digest.Algorithm, d.Digest.Hex), - Size: d.Size, - Annotations: d.Annotations, - }, nil -} - -func (pt *tagService) Tag(ctx context.Context, tag string, desc manifestv1.Descriptor) error { - d, err := pt.puller.Get(ctx, pt.repo.Digest(desc.Digest.String())) - if err != nil { - return err - } - return pt.pusher.Push(ctx, pt.repo.Tag(tag), d) -} - -func (pt *tagService) Untag(ctx context.Context, tag string) error { - return pt.pusher.Delete(ctx, pt.repo.Tag(tag)) -} - -func (pt *tagService) All(ctx context.Context) ([]string, error) { - return pt.puller.List(ctx, pt.repo) -} diff --git a/pkg/content/remote/zz_generated.runtimedoc.go b/pkg/content/remote/zz_generated.runtimedoc.go new file mode 100644 index 0000000..9754183 --- /dev/null +++ b/pkg/content/remote/zz_generated.runtimedoc.go @@ -0,0 +1,54 @@ +/* +Package remote GENERATED BY gengo:runtimedoc +DON'T EDIT THIS FILE +*/ +package remote + +// nolint:deadcode,unused +func runtimeDoc(v any, names ...string) ([]string, bool) { + if c, ok := v.(interface { + RuntimeDoc(names ...string) ([]string, bool) + }); ok { + return c.RuntimeDoc(names...) + } + return nil, false +} + +func (v Client) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Registry": + return []string{}, true + + } + if doc, ok := runtimeDoc(v.Registry, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + +func (v Registry) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Endpoint": + return []string{ + "Remote container registry endpoint", + }, true + case "Username": + return []string{ + "Remote container registry username", + }, true + case "Password": + return []string{ + "Remote container registry password", + }, true + + } + + return nil, false + } + return []string{}, true +} diff --git a/pkg/content/repository.go b/pkg/content/repository.go index dd28980..933e03a 100644 --- a/pkg/content/repository.go +++ b/pkg/content/repository.go @@ -4,11 +4,9 @@ import ( "context" "github.com/distribution/reference" - contextx "github.com/octohelm/x/context" ) -var RepositoryContext = contextx.New[Repository]() - +// +gengo:injectable:provider type Repository interface { Named() reference.Named Manifests(ctx context.Context) (ManifestService, error) diff --git a/pkg/content/tag_service.go b/pkg/content/tag_service.go index 750a8a0..8efe63d 100644 --- a/pkg/content/tag_service.go +++ b/pkg/content/tag_service.go @@ -12,3 +12,12 @@ type TagService interface { Untag(ctx context.Context, tag string) error All(ctx context.Context) ([]string, error) } + +type TagList struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +func (TagList) ContentType() string { + return "application/json" +} diff --git a/pkg/content/zz_generated.injectable.go b/pkg/content/zz_generated.injectable.go new file mode 100644 index 0000000..bd29b55 --- /dev/null +++ b/pkg/content/zz_generated.injectable.go @@ -0,0 +1,35 @@ +/* +Package content GENERATED BY gengo:injectable +DON'T EDIT THIS FILE +*/ +package content + +import ( + context "context" +) + +type contextNamespace struct{} + +func NamespaceFromContext(ctx context.Context) (Namespace, bool) { + if v, ok := ctx.Value(contextNamespace{}).(Namespace); ok { + return v, true + } + return nil, false +} + +func NamespaceInjectContext(ctx context.Context, tpe Namespace) context.Context { + return context.WithValue(ctx, contextNamespace{}, tpe) +} + +type contextRepository struct{} + +func RepositoryFromContext(ctx context.Context) (Repository, bool) { + if v, ok := ctx.Value(contextRepository{}).(Repository); ok { + return v, true + } + return nil, false +} + +func RepositoryInjectContext(ctx context.Context, tpe Repository) context.Context { + return context.WithValue(ctx, contextRepository{}, tpe) +} diff --git a/pkg/content/zz_generated.runtimedoc.go b/pkg/content/zz_generated.runtimedoc.go index 04693e2..55e6a7d 100644 --- a/pkg/content/zz_generated.runtimedoc.go +++ b/pkg/content/zz_generated.runtimedoc.go @@ -32,6 +32,32 @@ func (v ErrBlobInvalidDigest) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } +func (v ErrBlobInvalidLength) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Reason": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} + +func (v ErrBlobUnknown) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Digest": + return []string{}, true + + } + + return nil, false + } + return []string{}, true +} + func (v ErrManifestBlobUnknown) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { @@ -145,6 +171,20 @@ func (v ErrTagUnknown) RuntimeDoc(names ...string) ([]string, bool) { func (Name) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } -func (TagOrDigest) RuntimeDoc(names ...string) ([]string, bool) { +func (Reference) RuntimeDoc(names ...string) ([]string, bool) { + return []string{}, true +} +func (v TagList) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Name": + return []string{}, true + case "Tags": + return []string{}, true + + } + + return nil, false + } return []string{}, true } diff --git a/pkg/kubepkg/packer.go b/pkg/kubepkg/packer.go index 1792bdf..d483bd3 100644 --- a/pkg/kubepkg/packer.go +++ b/pkg/kubepkg/packer.go @@ -23,7 +23,6 @@ import ( "github.com/octohelm/kubepkgspec/pkg/object" "github.com/octohelm/kubepkgspec/pkg/workload" specv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" ) const ( @@ -183,7 +182,7 @@ func (p *Packer) PackAsIndex(ctx context.Context, kpkg *kubepkgv1alpha1.KubePkg) for _, imageName := range imageNames { nameAndTag := strings.Split(imageName, ":") if len(nameAndTag) != 2 { - return nil, errors.Errorf("invalid image name %s", nameAndTag) + return nil, fmt.Errorf("invalid image name %s", nameAndTag) } index := imageIndexes[imageName] diff --git a/pkg/kubepkg/util.go b/pkg/kubepkg/util.go index e16f2e1..687210d 100644 --- a/pkg/kubepkg/util.go +++ b/pkg/kubepkg/util.go @@ -1,10 +1,10 @@ package kubepkg import ( + "fmt" "strings" "github.com/gobwas/glob" - "github.com/pkg/errors" ) func Compile(patterns []string) (glob.Glob, error) { @@ -14,7 +14,7 @@ func Compile(patterns []string) (glob.Glob, error) { if strings.HasPrefix(p, "!") { g, err := glob.Compile(p[1:]) if err != nil { - return nil, errors.Wrapf(err, "compile failed %s", p) + return nil, fmt.Errorf("compile failed %s: %w", p, err) } rr = append(rr, rule{ glob: g, @@ -24,7 +24,7 @@ func Compile(patterns []string) (glob.Glob, error) { } g, err := glob.Compile(p) if err != nil { - return nil, errors.Wrapf(err, "compile failed %s", p) + return nil, fmt.Errorf("compile failed %s: %w", p, err) } rr = append(rr, rule{glob: g}) } diff --git a/pkg/ocitar/write.go b/pkg/ocitar/write.go index 1d3b2a1..ff9a3d4 100644 --- a/pkg/ocitar/write.go +++ b/pkg/ocitar/write.go @@ -12,7 +12,6 @@ import ( "github.com/containerd/containerd/images" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/partial" - "github.com/pkg/errors" ) var ociLayoutRaw = []byte(`{"imageLayoutVersion":"1.0.0"}`) @@ -36,7 +35,7 @@ type ociTarWriter struct { func (w *ociTarWriter) writeRootIndex(idx v1.ImageIndex) error { if err := w.writeDeps(idx, nil); err != nil { - return errors.Wrap(err, "write deps failed") + return fmt.Errorf("write deps failed: %w", err) } raw, err := idx.RawManifest() @@ -113,12 +112,12 @@ func (w *ociTarWriter) writeDeps(m manifest, scope *v1.Descriptor) error { func (w *ociTarWriter) writeChildren(idx v1.ImageIndex, scope *v1.Descriptor) error { children, err := partial.Manifests(idx) if err != nil { - return errors.Wrapf(err, "resolve manifests failed, %T", idx) + return fmt.Errorf("resolve manifests failed, %T: %w", idx, err) } index, err := idx.IndexManifest() if err != nil { - return errors.Wrapf(err, "resolve index manifests failed, %T", idx) + return fmt.Errorf("resolve index manifests failed, %T: %w", idx, err) } for i, child := range children { @@ -154,7 +153,7 @@ func (w *ociTarWriter) writeChild(child partial.Describable, scope *v1.Descripto func (w *ociTarWriter) writeLayers(img v1.Image, scope *v1.Descriptor) error { ls, err := img.Layers() if err != nil { - return errors.Wrap(err, "resolve layers failed") + return fmt.Errorf("resolve layers failed: %w", err) } for _, l := range ls { @@ -164,7 +163,7 @@ func (w *ociTarWriter) writeLayers(img v1.Image, scope *v1.Descriptor) error { } cl, err := partial.ConfigLayer(img) if err != nil { - return errors.Wrap(err, "resolve config failed") + return fmt.Errorf("resolve config failed: %w", err) } return w.writeLayer(cl, scope) } @@ -176,12 +175,12 @@ func (w *ociTarWriter) writeManifest(m manifest, scope *v1.Descriptor) error { raw, err := m.RawManifest() if err != nil { - return errors.Wrap(err, "read raw manifest failed") + return fmt.Errorf("read raw manifest failed: %w", err) } dgst, err := m.Digest() if err != nil { - return errors.Wrap(err, "read digest failed") + return fmt.Errorf("read digest failed: %w", err) } return w.writeToTar(tar.Header{ @@ -193,17 +192,17 @@ func (w *ociTarWriter) writeManifest(m manifest, scope *v1.Descriptor) error { func (w *ociTarWriter) writeLayer(layer v1.Layer, scope *v1.Descriptor) error { dgst, err := layer.Digest() if err != nil { - return errors.Wrap(err, "read layer digest failed") + return fmt.Errorf("read layer digest failed: %w", err) } size, err := layer.Size() if err != nil { - return errors.Wrap(err, "read layer digest failed") + return fmt.Errorf("read layer digest failed: %w", err) } r, err := layer.Compressed() if err != nil { - return errors.Wrap(err, "read layer contents") + return fmt.Errorf("read layer contents: %w", err) } defer func() { @@ -211,7 +210,7 @@ func (w *ociTarWriter) writeLayer(layer v1.Layer, scope *v1.Descriptor) error { }() if err := w.writeToTarWithDigest(dgst, size, r, scope); err != nil { - return errors.Wrapf(err, "copy %s failed", dgst) + return fmt.Errorf("copy %s failed: %w", dgst, err) } return nil diff --git a/pkg/registryhttp/apis/registry/base_url.go b/pkg/registryhttp/apis/registry/base_url.go index 411fa46..916f6df 100644 --- a/pkg/registryhttp/apis/registry/base_url.go +++ b/pkg/registryhttp/apis/registry/base_url.go @@ -3,6 +3,8 @@ package registry import ( "context" + "github.com/octohelm/crkit/pkg/content" + "github.com/octohelm/courier/pkg/courierhttp" ) @@ -13,3 +15,14 @@ type BaseURL struct { func (r *BaseURL) Output(ctx context.Context) (any, error) { return map[string]string{}, nil } + +// +gengo:injectable +type NameScoped struct { + Name content.Name `name:"name" in:"path"` + + namespace content.Namespace `inject:""` +} + +func (req *NameScoped) Repository(ctx context.Context) (content.Repository, error) { + return req.namespace.Repository(ctx, req.Name) +} diff --git a/pkg/registryhttp/apis/registry/blob__delete.go b/pkg/registryhttp/apis/registry/blob__delete.go new file mode 100644 index 0000000..be19ee4 --- /dev/null +++ b/pkg/registryhttp/apis/registry/blob__delete.go @@ -0,0 +1,35 @@ +package registry + +import ( + "context" + + "github.com/octohelm/courier/pkg/courierhttp" + "github.com/octohelm/crkit/pkg/content" + "github.com/opencontainers/go-digest" +) + +type DeleteBlob struct { + courierhttp.MethodDelete `path:"/{name...}/blobs/{digest}"` + + NameScoped + + Digest content.Digest `name:"digest" in:"path"` +} + +func (req *DeleteBlob) Output(ctx context.Context) (any, error) { + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } + + blobs, err := repo.Blobs(ctx) + if err != nil { + return nil, err + } + + err = blobs.Remove(ctx, digest.Digest(req.Digest)) + if err != nil { + return nil, err + } + return nil, nil +} diff --git a/pkg/registryhttp/apis/registry/blob__get.go b/pkg/registryhttp/apis/registry/blob__get.go index d1b5ada..ac3bdb7 100644 --- a/pkg/registryhttp/apis/registry/blob__get.go +++ b/pkg/registryhttp/apis/registry/blob__get.go @@ -3,27 +3,24 @@ package registry import ( "context" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" "github.com/opencontainers/go-digest" ) -func (GetBlob) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - type GetBlob struct { - courierhttp.MethodGet `path:"/blobs/{digest}"` + courierhttp.MethodGet `path:"/{name...}/blobs/{digest}"` + + NameScoped Digest content.Digest `name:"digest" in:"path"` } func (req *GetBlob) Output(ctx context.Context) (any, error) { - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } blobs, err := repo.Blobs(ctx) if err != nil { diff --git a/pkg/registryhttp/apis/registry/blob__head.go b/pkg/registryhttp/apis/registry/blob__head.go index dba6ace..70d01f7 100644 --- a/pkg/registryhttp/apis/registry/blob__head.go +++ b/pkg/registryhttp/apis/registry/blob__head.go @@ -6,26 +6,23 @@ import ( "github.com/opencontainers/go-digest" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" ) -func (HeadBlob) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - type HeadBlob struct { - courierhttp.MethodHead `path:"/blobs/{digest}"` + courierhttp.MethodHead `path:"/{name...}/blobs/{digest}"` + + NameScoped Digest content.Digest `name:"digest" in:"path"` } func (req *HeadBlob) Output(ctx context.Context) (any, error) { - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } blobs, err := repo.Blobs(ctx) if err != nil { diff --git a/pkg/registryhttp/apis/registry/blob__upload.go b/pkg/registryhttp/apis/registry/blob__upload.go index 975d3a5..a905009 100644 --- a/pkg/registryhttp/apis/registry/blob__upload.go +++ b/pkg/registryhttp/apis/registry/blob__upload.go @@ -6,34 +6,34 @@ import ( "io" "net/http" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" "github.com/octohelm/crkit/pkg/uploadcache" "github.com/opencontainers/go-digest" ) -func (UploadBlob) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - +// +gengo:injectable type UploadBlob struct { - courierhttp.MethodPost `path:"/blobs/uploads"` + courierhttp.MethodPost `path:"/{name...}/blobs/uploads"` + + NameScoped ContentLength int `name:"Content-Length,omitempty" in:"header"` ContentType string `name:"Content-Type,omitempty" in:"header"` Digest content.Digest `name:"digest,omitempty" in:"query"` Blob io.ReadCloser `in:"body"` + + uploadCache uploadcache.UploadCache `inject:""` } func (req *UploadBlob) Output(ctx context.Context) (any, error) { defer req.Blob.Close() - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post if req.Digest != "" { @@ -64,9 +64,7 @@ func (req *UploadBlob) Output(ctx context.Context) (any, error) { } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#post-then-put - uc := uploadcache.Context.From(ctx) - - w, err := uc.BlobWriter(ctx, repo) + w, err := req.uploadCache.BlobWriter(ctx, repo) if err != nil { return nil, err } diff --git a/pkg/registryhttp/apis/registry/blob__upload_cancel.go b/pkg/registryhttp/apis/registry/blob__upload_cancel.go new file mode 100644 index 0000000..28147b2 --- /dev/null +++ b/pkg/registryhttp/apis/registry/blob__upload_cancel.go @@ -0,0 +1,27 @@ +package registry + +import ( + "context" + + "github.com/octohelm/courier/pkg/courierhttp" + "github.com/octohelm/crkit/pkg/uploadcache" +) + +// +gengo:injectable +type CancelUploadBlob struct { + courierhttp.MethodDelete `path:"/{name...}/blobs/uploads/{id}"` + NameScoped + + ID string `name:"id" in:"path"` + + uploadCache uploadcache.UploadCache `inject:""` +} + +func (req *CancelUploadBlob) Output(ctx context.Context) (any, error) { + w, err := req.uploadCache.Resume(ctx, req.ID) + if err != nil { + return nil, nil + } + _ = w.Close() + return nil, nil +} diff --git a/pkg/registryhttp/apis/registry/blob__upload_patch.go b/pkg/registryhttp/apis/registry/blob__upload_patch.go index afac5ef..6259a1a 100644 --- a/pkg/registryhttp/apis/registry/blob__upload_patch.go +++ b/pkg/registryhttp/apis/registry/blob__upload_patch.go @@ -6,32 +6,32 @@ import ( "io" "net/http" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" - "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" "github.com/octohelm/crkit/pkg/uploadcache" ) -func (UploadPatchBlob) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - +// +gengo:injectable type UploadPatchBlob struct { - courierhttp.MethodPatch `path:"/blobs/uploads/{id}"` - ID string `name:"id" in:"path"` - Chunk io.ReadCloser `in:"body"` + courierhttp.MethodPatch `path:"/{name...}/blobs/uploads/{id}"` + + NameScoped + + ID string `name:"id" in:"path"` + + Chunk io.ReadCloser `in:"body"` + + uploadCache uploadcache.UploadCache `inject:""` } func (req *UploadPatchBlob) Output(ctx context.Context) (any, error) { defer req.Chunk.Close() - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } - uc := uploadcache.Context.From(ctx) - w, err := uc.Resume(ctx, req.ID) + w, err := req.uploadCache.Resume(ctx, req.ID) if err != nil { return nil, err } diff --git a/pkg/registryhttp/apis/registry/blob__upload_put.go b/pkg/registryhttp/apis/registry/blob__upload_put.go index a736fa4..43baba1 100644 --- a/pkg/registryhttp/apis/registry/blob__upload_put.go +++ b/pkg/registryhttp/apis/registry/blob__upload_put.go @@ -6,37 +6,36 @@ import ( "io" "net/http" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" "github.com/octohelm/crkit/pkg/uploadcache" "github.com/opencontainers/go-digest" ) -func (UploadPutBlob) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - +// +gengo:injectable type UploadPutBlob struct { - courierhttp.MethodPut `path:"/blobs/uploads/{id}"` + courierhttp.MethodPut `path:"/{name...}/blobs/uploads/{id}"` + + NameScoped ID string `name:"id" in:"path"` ContentLength int `name:"Content-Length,omitempty" in:"header"` Digest content.Digest `name:"digest" in:"query"` Chunk io.ReadCloser `in:"body"` + + uploadCache uploadcache.UploadCache `inject:""` } func (req *UploadPutBlob) Output(ctx context.Context) (any, error) { defer req.Chunk.Close() - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } - uc := uploadcache.Context.From(ctx) - w, err := uc.Resume(ctx, req.ID) + w, err := req.uploadCache.Resume(ctx, req.ID) if err != nil { return nil, err } diff --git a/pkg/registryhttp/apis/registry/manifest__delete.go b/pkg/registryhttp/apis/registry/manifest__delete.go new file mode 100644 index 0000000..c257588 --- /dev/null +++ b/pkg/registryhttp/apis/registry/manifest__delete.go @@ -0,0 +1,47 @@ +package registry + +import ( + "context" + + "github.com/octohelm/courier/pkg/courierhttp" + "github.com/octohelm/crkit/pkg/content" +) + +type DeleteManifest struct { + courierhttp.MethodDelete `path:"/{name...}/manifests/{reference}"` + + NameScoped + + Reference content.Reference `name:"reference" in:"path"` +} + +func (req *DeleteManifest) Output(ctx context.Context) (any, error) { + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } + + dgst, err := req.Reference.Digest() + if err == nil { + manifests, err := repo.Manifests(ctx) + if err != nil { + return nil, err + } + + if err := manifests.Delete(ctx, dgst); err != nil { + return nil, err + } + + return nil, nil + } + + tag := string(req.Reference) + tags, err := repo.Tags(ctx) + if err != nil { + return nil, err + } + if err := tags.Untag(ctx, tag); err != nil { + return nil, err + } + return nil, nil +} diff --git a/pkg/registryhttp/apis/registry/manifest__get.go b/pkg/registryhttp/apis/registry/manifest__get.go index 0b1fe1d..38880d2 100644 --- a/pkg/registryhttp/apis/registry/manifest__get.go +++ b/pkg/registryhttp/apis/registry/manifest__get.go @@ -1,30 +1,26 @@ package registry import ( - "github.com/octohelm/courier/pkg/courier" + "context" + "github.com/octohelm/courier/pkg/courierhttp" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" -) - -import ( - "context" ) -func (GetManifest) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - type GetManifest struct { - courierhttp.MethodGet `path:"/manifests/{reference}"` + courierhttp.MethodGet `path:"/{name...}/manifests/{reference}"` - Reference content.TagOrDigest `name:"reference" in:"path"` + NameScoped + Accept string `name:"Accept,omitempty" in:"header"` + Reference content.Reference `name:"reference" in:"path"` } func (req *GetManifest) Output(ctx context.Context) (any, error) { - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } dgst, err := req.Reference.Digest() if err != nil { @@ -50,7 +46,12 @@ func (req *GetManifest) Output(ctx context.Context) (any, error) { return nil, err } - return courierhttp.Wrap(m, + p, err := manifestv1.From(m) + if err != nil { + return nil, err + } + + return courierhttp.Wrap(p, courierhttp.WithMetadata("Docker-Content-Digest", dgst.String()), courierhttp.WithMetadata("Content-Type", m.Type()), ), nil diff --git a/pkg/registryhttp/apis/registry/manifest__head.go b/pkg/registryhttp/apis/registry/manifest__head.go index f243ebe..c67cdcc 100644 --- a/pkg/registryhttp/apis/registry/manifest__head.go +++ b/pkg/registryhttp/apis/registry/manifest__head.go @@ -4,26 +4,24 @@ import ( "context" "fmt" - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" ) -func (HeadManifest) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - type HeadManifest struct { - courierhttp.MethodHead `path:"/manifests/{reference}"` + courierhttp.MethodHead `path:"/{name...}/manifests/{reference}"` + + NameScoped - Reference content.TagOrDigest `name:"reference" in:"path"` + Accept string `name:"Accept,omitempty" in:"header"` + Reference content.Reference `name:"reference" in:"path"` } func (req *HeadManifest) Output(ctx context.Context) (any, error) { - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } dgst, err := req.Reference.Digest() if err != nil { diff --git a/pkg/registryhttp/apis/registry/manifest__put.go b/pkg/registryhttp/apis/registry/manifest__put.go index cc7ca67..0938dd1 100644 --- a/pkg/registryhttp/apis/registry/manifest__put.go +++ b/pkg/registryhttp/apis/registry/manifest__put.go @@ -3,29 +3,25 @@ package registry import ( "context" - manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" - - "github.com/octohelm/courier/pkg/courier" "github.com/octohelm/courier/pkg/courierhttp" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" - registryoperator "github.com/octohelm/crkit/pkg/registryhttp/apis/registry/operator" ) -func (PutManifest) MiddleOperators() courier.MiddleOperators { - return courier.MiddleOperators{ - ®istryoperator.NameScoped{}, - } -} - type PutManifest struct { - courierhttp.MethodPut `path:"/manifests/{reference}"` + courierhttp.MethodPut `path:"/{name...}/manifests/{reference}"` + + NameScoped - Reference content.TagOrDigest `name:"reference" in:"path"` - Manifest manifestv1.Payload `in:"body"` + Reference content.Reference `name:"reference" in:"path"` + Manifest manifestv1.Payload `in:"body"` } func (req *PutManifest) Output(ctx context.Context) (any, error) { - repo := content.RepositoryContext.From(ctx) + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } manifests, err := repo.Manifests(ctx) if err != nil { diff --git a/pkg/registryhttp/apis/registry/operator/namescoped.go b/pkg/registryhttp/apis/registry/operator/namescoped.go deleted file mode 100644 index 232f832..0000000 --- a/pkg/registryhttp/apis/registry/operator/namescoped.go +++ /dev/null @@ -1,23 +0,0 @@ -package operator - -import ( - "context" - - "github.com/octohelm/courier/pkg/courierhttp" - "github.com/octohelm/crkit/pkg/content" -) - -type NameScoped struct { - courierhttp.Method `path:"/{name...}"` - - Name content.Name `name:"name" in:"path"` -} - -func (req *NameScoped) Output(ctx context.Context) (any, error) { - n := content.NamespaceContext.From(ctx) - repo, err := n.Repository(ctx, req.Name) - if err != nil { - return nil, err - } - return content.RepositoryContext.Inject(ctx, repo), nil -} diff --git a/pkg/registryhttp/apis/registry/tag__list.go b/pkg/registryhttp/apis/registry/tag__list.go new file mode 100644 index 0000000..443d295 --- /dev/null +++ b/pkg/registryhttp/apis/registry/tag__list.go @@ -0,0 +1,36 @@ +package registry + +import ( + "context" + + "github.com/octohelm/courier/pkg/courierhttp" + "github.com/octohelm/crkit/pkg/content" +) + +type ListTag struct { + courierhttp.MethodGet `path:"/{name...}/tags/list"` + + NameScoped +} + +func (req *ListTag) Output(ctx context.Context) (any, error) { + repo, err := req.Repository(ctx) + if err != nil { + return nil, err + } + + tags, err := repo.Tags(ctx) + if err != nil { + return nil, err + } + + tagList, err := tags.All(ctx) + if err != nil { + return nil, err + } + + return &content.TagList{ + Name: req.Name.Name(), + Tags: tagList, + }, nil +} diff --git a/pkg/registryhttp/apis/registry/zz_generated.injectable.go b/pkg/registryhttp/apis/registry/zz_generated.injectable.go new file mode 100644 index 0000000..7c9b3b5 --- /dev/null +++ b/pkg/registryhttp/apis/registry/zz_generated.injectable.go @@ -0,0 +1,79 @@ +/* +Package registry GENERATED BY gengo:injectable +DON'T EDIT THIS FILE +*/ +package registry + +import ( + context "context" + fmt "fmt" + + content "github.com/octohelm/crkit/pkg/content" + uploadcache "github.com/octohelm/crkit/pkg/uploadcache" +) + +func (v *CancelUploadBlob) Init(ctx context.Context) error { + if value, ok := uploadcache.UploadCacheFromContext(ctx); ok { + v.uploadCache = value + } else { + return fmt.Errorf("missing provider %T", v.uploadCache) + } + + if err := v.NameScoped.Init(ctx); err != nil { + return err + } + + return nil +} + +func (v *NameScoped) Init(ctx context.Context) error { + if value, ok := content.NamespaceFromContext(ctx); ok { + v.namespace = value + } else { + return fmt.Errorf("missing provider %T", v.namespace) + } + + return nil +} + +func (v *UploadBlob) Init(ctx context.Context) error { + if value, ok := uploadcache.UploadCacheFromContext(ctx); ok { + v.uploadCache = value + } else { + return fmt.Errorf("missing provider %T", v.uploadCache) + } + + if err := v.NameScoped.Init(ctx); err != nil { + return err + } + + return nil +} + +func (v *UploadPatchBlob) Init(ctx context.Context) error { + if value, ok := uploadcache.UploadCacheFromContext(ctx); ok { + v.uploadCache = value + } else { + return fmt.Errorf("missing provider %T", v.uploadCache) + } + + if err := v.NameScoped.Init(ctx); err != nil { + return err + } + + return nil +} + +func (v *UploadPutBlob) Init(ctx context.Context) error { + if value, ok := uploadcache.UploadCacheFromContext(ctx); ok { + v.uploadCache = value + } else { + return fmt.Errorf("missing provider %T", v.uploadCache) + } + + if err := v.NameScoped.Init(ctx); err != nil { + return err + } + + return nil +} diff --git a/pkg/registryhttp/apis/registry/zz_generated.operator.go b/pkg/registryhttp/apis/registry/zz_generated.operator.go index a67fc7b..36514a1 100644 --- a/pkg/registryhttp/apis/registry/zz_generated.operator.go +++ b/pkg/registryhttp/apis/registry/zz_generated.operator.go @@ -9,6 +9,7 @@ import ( courier "github.com/octohelm/courier/pkg/courier" manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" + content "github.com/octohelm/crkit/pkg/content" ) func init() { @@ -22,6 +23,43 @@ func (*BaseURL) ResponseContent() any { func (*BaseURL) ResponseData() *map[string]string { return new(map[string]string) } + +func init() { + R.Register(courier.NewRouter(&CancelUploadBlob{})) +} + +func (*CancelUploadBlob) ResponseContent() any { + return nil +} + +func (*CancelUploadBlob) ResponseData() *courier.NoContent { + return new(courier.NoContent) +} + +func init() { + R.Register(courier.NewRouter(&DeleteBlob{})) +} + +func (*DeleteBlob) ResponseContent() any { + return nil +} + +func (*DeleteBlob) ResponseData() *courier.NoContent { + return new(courier.NoContent) +} + +func init() { + R.Register(courier.NewRouter(&DeleteManifest{})) +} + +func (*DeleteManifest) ResponseContent() any { + return nil +} + +func (*DeleteManifest) ResponseData() *courier.NoContent { + return new(courier.NoContent) +} + func init() { R.Register(courier.NewRouter(&GetBlob{})) } @@ -33,17 +71,19 @@ func (*GetBlob) ResponseContent() any { func (*GetBlob) ResponseData() *io.ReadCloser { return new(io.ReadCloser) } + func init() { R.Register(courier.NewRouter(&GetManifest{})) } func (*GetManifest) ResponseContent() any { - return new(manifestv1.Manifest) + return new(manifestv1.Payload) } -func (*GetManifest) ResponseData() *manifestv1.Manifest { - return new(manifestv1.Manifest) +func (*GetManifest) ResponseData() *manifestv1.Payload { + return new(manifestv1.Payload) } + func init() { R.Register(courier.NewRouter(&HeadBlob{})) } @@ -55,6 +95,7 @@ func (*HeadBlob) ResponseContent() any { func (*HeadBlob) ResponseData() *any { return new(any) } + func init() { R.Register(courier.NewRouter(&HeadManifest{})) } @@ -66,6 +107,19 @@ func (*HeadManifest) ResponseContent() any { func (*HeadManifest) ResponseData() *any { return new(any) } + +func init() { + R.Register(courier.NewRouter(&ListTag{})) +} + +func (*ListTag) ResponseContent() any { + return new(content.TagList) +} + +func (*ListTag) ResponseData() *content.TagList { + return new(content.TagList) +} + func init() { R.Register(courier.NewRouter(&PutManifest{})) } @@ -77,6 +131,7 @@ func (*PutManifest) ResponseContent() any { func (*PutManifest) ResponseData() *any { return new(any) } + func init() { R.Register(courier.NewRouter(&UploadBlob{})) } @@ -88,6 +143,7 @@ func (*UploadBlob) ResponseContent() any { func (*UploadBlob) ResponseData() *any { return new(any) } + func init() { R.Register(courier.NewRouter(&UploadPatchBlob{})) } @@ -99,6 +155,7 @@ func (*UploadPatchBlob) ResponseContent() any { func (*UploadPatchBlob) ResponseData() *any { return new(any) } + func init() { R.Register(courier.NewRouter(&UploadPutBlob{})) } diff --git a/pkg/registryhttp/apis/registry/zz_generated.runtimedoc.go b/pkg/registryhttp/apis/registry/zz_generated.runtimedoc.go index d9b2575..c698fce 100644 --- a/pkg/registryhttp/apis/registry/zz_generated.runtimedoc.go +++ b/pkg/registryhttp/apis/registry/zz_generated.runtimedoc.go @@ -25,13 +25,72 @@ func (v BaseURL) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } +func (v CancelUploadBlob) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "NameScoped": + return []string{}, true + case "ID": + return []string{}, true + + } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + +func (v DeleteBlob) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "NameScoped": + return []string{}, true + case "Digest": + return []string{}, true + + } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + +func (v DeleteManifest) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "NameScoped": + return []string{}, true + case "Reference": + return []string{}, true + + } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + func (v GetBlob) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "Digest": return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -41,10 +100,17 @@ func (v GetBlob) RuntimeDoc(names ...string) ([]string, bool) { func (v GetManifest) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true + case "Accept": + return []string{}, true case "Reference": return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -54,10 +120,15 @@ func (v GetManifest) RuntimeDoc(names ...string) ([]string, bool) { func (v HeadBlob) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "Digest": return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -67,9 +138,45 @@ func (v HeadBlob) RuntimeDoc(names ...string) ([]string, bool) { func (v HeadManifest) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true + case "Accept": + return []string{}, true case "Reference": return []string{}, true + } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + +func (v ListTag) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "NameScoped": + return []string{}, true + + } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } + + return nil, false + } + return []string{}, true +} + +func (v NameScoped) RuntimeDoc(names ...string) ([]string, bool) { + if len(names) > 0 { + switch names[0] { + case "Name": + return []string{}, true + } return nil, false @@ -80,12 +187,17 @@ func (v HeadManifest) RuntimeDoc(names ...string) ([]string, bool) { func (v PutManifest) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "Reference": return []string{}, true case "Manifest": return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -95,6 +207,8 @@ func (v PutManifest) RuntimeDoc(names ...string) ([]string, bool) { func (v UploadBlob) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "ContentLength": return []string{}, true case "ContentType": @@ -105,6 +219,9 @@ func (v UploadBlob) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -114,12 +231,17 @@ func (v UploadBlob) RuntimeDoc(names ...string) ([]string, bool) { func (v UploadPatchBlob) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "ID": return []string{}, true case "Chunk": return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } @@ -129,6 +251,8 @@ func (v UploadPatchBlob) RuntimeDoc(names ...string) ([]string, bool) { func (v UploadPutBlob) RuntimeDoc(names ...string) ([]string, bool) { if len(names) > 0 { switch names[0] { + case "NameScoped": + return []string{}, true case "ID": return []string{}, true case "ContentLength": @@ -139,6 +263,9 @@ func (v UploadPutBlob) RuntimeDoc(names ...string) ([]string, bool) { return []string{}, true } + if doc, ok := runtimeDoc(v.NameScoped, names...); ok { + return doc, ok + } return nil, false } diff --git a/pkg/registryhttp/server.go b/pkg/registryhttp/server.go index f6ee388..3f21a97 100644 --- a/pkg/registryhttp/server.go +++ b/pkg/registryhttp/server.go @@ -5,11 +5,11 @@ import ( "net/http" "strings" - infraconfiguration "github.com/innoai-tech/infra/pkg/configuration" infrahttp "github.com/innoai-tech/infra/pkg/http" "github.com/octohelm/crkit/pkg/registryhttp/apis" ) +// +gengo:injectable type Server struct { infrahttp.Server } @@ -20,7 +20,7 @@ func (s *Server) SetDefaults() { } } -func (s *Server) Init(ctx context.Context) error { +func (s *Server) beforeInit(ctx context.Context) error { s.ApplyRouter(apis.R) s.ApplyGlobalHandlers(func(h http.Handler) http.Handler { @@ -34,9 +34,5 @@ func (s *Server) Init(ctx context.Context) error { }) }) - return infraconfiguration.TypedInit(ctx, &s.Server) -} - -func (s *Server) InjectContext(ctx context.Context) context.Context { - return infraconfiguration.InjectContext(ctx) + return nil } diff --git a/pkg/registryhttp/zz_generated.injectable.go b/pkg/registryhttp/zz_generated.injectable.go new file mode 100644 index 0000000..1876ff5 --- /dev/null +++ b/pkg/registryhttp/zz_generated.injectable.go @@ -0,0 +1,20 @@ +/* +Package registryhttp GENERATED BY gengo:injectable +DON'T EDIT THIS FILE +*/ +package registryhttp + +import ( + context "context" +) + +func (v *Server) Init(ctx context.Context) error { + if err := v.beforeInit(ctx); err != nil { + return err + } + if err := v.Server.Init(ctx); err != nil { + return err + } + + return nil +} diff --git a/pkg/uploadcache/mem_upload_cache.go b/pkg/uploadcache/mem_upload_cache.go new file mode 100644 index 0000000..e22a45f --- /dev/null +++ b/pkg/uploadcache/mem_upload_cache.go @@ -0,0 +1,115 @@ +package uploadcache + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/go-courier/logr" + + "github.com/innoai-tech/infra/pkg/cron" + manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" + "github.com/octohelm/crkit/pkg/content" +) + +// +gengo:injectable:provider UploadCache +type MemUploadCache struct { + cron.Job + + m sync.Map +} + +func (c *MemUploadCache) SetDefaults() { + if c.Job.Cron == "" { + c.Job.Cron = "@every 3s" + } +} + +func (c *MemUploadCache) beforeInit(ctx context.Context) error { + c.ApplyAction("upload pruning", func(ctx context.Context) { + if err := c.cleanup(ctx); err != nil { + logr.FromContext(ctx).Error(err) + } + }) + return nil +} + +func (c *MemUploadCache) cleanup(ctx context.Context) error { + now := time.Now() + + expiredWriters := make([]string, 0) + + for _, v := range c.m.Range { + w := v.(*writer) + + if w.expiresAt.Before(now) { + expiredWriters = append(expiredWriters, w.ID()) + } + } + + for _, id := range expiredWriters { + v, ok := c.m.LoadAndDelete(id) + if ok { + w := v.(*writer) + _ = w.Close() + } + } + + return nil +} + +func (c *MemUploadCache) remove(id string) { + c.m.Delete(id) +} + +func (c *MemUploadCache) BlobWriter(ctx context.Context, repo content.Repository) (content.BlobWriter, error) { + blobs, err := repo.Blobs(ctx) + if err != nil { + return nil, err + } + w, err := blobs.Writer(ctx) + if err != nil { + return nil, err + } + + ww := &writer{ + c: c, + BlobWriter: w, + } + ww.expires() + + c.m.Store(ww.ID(), ww) + return ww, nil +} + +func (c *MemUploadCache) Resume(ctx context.Context, id string) (content.BlobWriter, error) { + v, ok := c.m.Load(id) + if ok { + return v.(*writer), nil + } + + return nil, fmt.Errorf("invalid upload session %s", id) +} + +type writer struct { + c *MemUploadCache + expiresAt time.Time + content.BlobWriter +} + +func (w *writer) expires() { + w.expiresAt = time.Now().Add(30 * time.Second) +} + +func (w *writer) Write(p []byte) (int, error) { + defer w.expires() + + return w.BlobWriter.Write(p) +} + +func (w *writer) Commit(ctx context.Context, expected manifestv1.Descriptor) (*manifestv1.Descriptor, error) { + defer w.c.remove(w.ID()) + + return w.BlobWriter.Commit(ctx, expected) +} diff --git a/pkg/uploadcache/upload_cache.go b/pkg/uploadcache/upload_cache.go index 7ef43c5..236cacc 100644 --- a/pkg/uploadcache/upload_cache.go +++ b/pkg/uploadcache/upload_cache.go @@ -2,125 +2,12 @@ package uploadcache import ( "context" - "sync" - "time" - "github.com/go-courier/logr" - "github.com/innoai-tech/infra/pkg/cron" - manifestv1 "github.com/octohelm/crkit/pkg/apis/manifest/v1" "github.com/octohelm/crkit/pkg/content" - contextx "github.com/octohelm/x/context" - "github.com/pkg/errors" ) -var Context = contextx.New[UploadCache]() - +// +gengo:injectable:provider type UploadCache interface { BlobWriter(ctx context.Context, repo content.Repository) (content.BlobWriter, error) Resume(ctx context.Context, id string) (content.BlobWriter, error) } - -type MemUploadCache struct { - cron.Job - - m sync.Map -} - -func (c *MemUploadCache) SetDefaults() { - if c.Job.Cron == "" { - c.Job.Cron = "@every 1s" - } -} - -func (c *MemUploadCache) cleanup(ctx context.Context) error { - now := time.Now() - - expiredWriters := make([]string, 0) - - for _, v := range c.m.Range { - w := v.(*writer) - - if w.expiresAt.Before(now) { - expiredWriters = append(expiredWriters, w.ID()) - } - } - - for _, id := range expiredWriters { - v, ok := c.m.LoadAndDelete(id) - if ok { - w := v.(*writer) - _ = w.Close() - } - } - - return nil -} - -func (c *MemUploadCache) Init(ctx context.Context) error { - c.ApplyAction("upload cache cleanup gc", func(ctx context.Context) { - if err := c.cleanup(ctx); err != nil { - logr.FromContext(ctx).Error(err) - } - }) - - return c.Job.Init(ctx) -} - -func (c *MemUploadCache) remove(id string) { - c.m.Delete(id) -} - -func (c *MemUploadCache) InjectContext(ctx context.Context) context.Context { - return Context.Inject(ctx, c) -} - -func (c *MemUploadCache) BlobWriter(ctx context.Context, repo content.Repository) (content.BlobWriter, error) { - blobs, err := repo.Blobs(ctx) - if err != nil { - return nil, err - } - w, err := blobs.Writer(ctx) - if err != nil { - return nil, err - } - - ww := &writer{ - c: c, - BlobWriter: w, - } - ww.expires() - - c.m.Store(ww.ID(), ww) - return ww, nil -} - -func (c *MemUploadCache) Resume(ctx context.Context, id string) (content.BlobWriter, error) { - v, ok := c.m.Load(id) - if ok { - return v.(*writer), nil - } - - return nil, errors.Errorf("invalid upload session %s", id) -} - -type writer struct { - c *MemUploadCache - expiresAt time.Time - content.BlobWriter -} - -func (w *writer) expires() { - w.expiresAt = time.Now().Add(30 * time.Second) -} - -func (w *writer) Write(p []byte) (int, error) { - defer w.expires() - - return w.BlobWriter.Write(p) -} - -func (w *writer) Commit(ctx context.Context, expected manifestv1.Descriptor) (*manifestv1.Descriptor, error) { - defer w.c.remove(w.ID()) - - return w.BlobWriter.Commit(ctx, expected) -} diff --git a/pkg/uploadcache/zz_generated.injectable.go b/pkg/uploadcache/zz_generated.injectable.go new file mode 100644 index 0000000..636f140 --- /dev/null +++ b/pkg/uploadcache/zz_generated.injectable.go @@ -0,0 +1,37 @@ +/* +Package uploadcache GENERATED BY gengo:injectable +DON'T EDIT THIS FILE +*/ +package uploadcache + +import ( + context "context" +) + +func (p *MemUploadCache) InjectContext(ctx context.Context) context.Context { + return UploadCacheInjectContext(ctx, p) +} + +func (v *MemUploadCache) Init(ctx context.Context) error { + if err := v.beforeInit(ctx); err != nil { + return err + } + if err := v.Job.Init(ctx); err != nil { + return err + } + + return nil +} + +type contextUploadCache struct{} + +func UploadCacheFromContext(ctx context.Context) (UploadCache, bool) { + if v, ok := ctx.Value(contextUploadCache{}).(UploadCache); ok { + return v, true + } + return nil, false +} + +func UploadCacheInjectContext(ctx context.Context, tpe UploadCache) context.Context { + return context.WithValue(ctx, contextUploadCache{}, tpe) +}