From ada947fb1e3e75ae2b8bbfa3a822603c2fa683ff Mon Sep 17 00:00:00 2001 From: cuisongliu Date: Mon, 21 Aug 2023 10:11:11 +0800 Subject: [PATCH] feature(main): delete registry feature using sreg code replace (#3714) Signed-off-by: cuisongliu --- cmd/sealctl/cmd/registry.go | 3 +- cmd/sealctl/cmd/root.go | 3 + cmd/sealos/cmd/registry.go | 10 +- cmd/sealos/cmd/root.go | 8 +- go.mod | 1 + go.sum | 2 + pkg/buildah/imagesaver.go | 7 +- pkg/buildah/merge.go | 6 +- pkg/buildimage/chart.go | 148 --- pkg/buildimage/chart_test.go | 53 - pkg/buildimage/images.go | 123 --- pkg/buildimage/images_test.go | 28 - pkg/buildimage/manifests/manifests.go | 42 - pkg/buildimage/manifests/manifests_test.go | 55 - pkg/buildimage/manifests/utils.go | 75 -- pkg/buildimage/test/.gitignore | 1 - pkg/checker/cri_shim_checker.go | 3 +- pkg/checker/registry_checker.go | 2 +- pkg/filesystem/registry/sync.go | 5 +- pkg/{buildimage => image}/merge.go | 2 +- pkg/{buildimage => image}/merge_test.go | 2 +- pkg/registry/commands/copy.go | 113 --- pkg/registry/commands/flags.go | 64 -- pkg/registry/commands/save.go | 79 -- pkg/registry/commands/serve.go | 119 --- pkg/registry/commands/sync.go | 91 -- pkg/registry/crane/crane.go | 102 -- pkg/registry/crane/crane_test.go | 109 -- pkg/registry/crane/keychain.go | 79 -- pkg/registry/crane/registry.go | 94 -- pkg/registry/crane/registry_test.go | 98 -- pkg/registry/handler/handler.go | 123 --- pkg/registry/password/password.go | 3 +- pkg/registry/save/interface.go | 79 -- .../client/auth/api_version.go | 72 -- .../client/auth/challenge/addr.go | 41 - .../client/auth/challenge/authchallenge.go | 251 ----- .../distributionpkg/client/auth/session.go | 549 ---------- .../lib/distributionpkg/client/blob_writer.go | 179 ---- .../save/lib/distributionpkg/client/errors.go | 153 --- .../lib/distributionpkg/client/repository.go | 960 ------------------ .../client/transport/http_reader.go | 268 ----- .../client/transport/transport.go | 169 --- .../lib/distributionpkg/proxy/proxyauth.go | 135 --- .../distributionpkg/proxy/proxyblobstore.go | 237 ----- .../proxy/proxymanifeststore.go | 109 -- .../lib/distributionpkg/proxy/proxymetrics.go | 87 -- .../distributionpkg/proxy/proxyregistry.go | 238 ----- .../distributionpkg/proxy/proxytagservice.go | 80 -- .../proxy/scheduler/scheduler.go | 273 ----- .../save/lib/imagemanifest/blobmanifest.go | 50 - .../save/lib/imagemanifest/imagemanifest.go | 81 -- pkg/registry/save/registry_save.go | 91 -- pkg/registry/save/save.go | 407 -------- pkg/registry/save/save_test.go | 66 -- pkg/registry/save/testdata/config.json | 7 - pkg/registry/sync/sync.go | 263 ----- pkg/{ => utils}/passwd/passwd.go | 0 pkg/utils/tmpl/tmpl.go | 27 - .../image-cri-shim/pkg/server/utils.go | 2 +- .../image-cri-shim/pkg/types/config.go | 2 +- 61 files changed, 37 insertions(+), 6492 deletions(-) delete mode 100644 pkg/buildimage/chart.go delete mode 100644 pkg/buildimage/chart_test.go delete mode 100644 pkg/buildimage/images.go delete mode 100644 pkg/buildimage/images_test.go delete mode 100644 pkg/buildimage/manifests/manifests.go delete mode 100644 pkg/buildimage/manifests/manifests_test.go delete mode 100644 pkg/buildimage/manifests/utils.go delete mode 100644 pkg/buildimage/test/.gitignore rename pkg/{buildimage => image}/merge.go (99%) rename pkg/{buildimage => image}/merge_test.go (99%) delete mode 100644 pkg/registry/commands/copy.go delete mode 100644 pkg/registry/commands/flags.go delete mode 100644 pkg/registry/commands/save.go delete mode 100644 pkg/registry/commands/serve.go delete mode 100644 pkg/registry/commands/sync.go delete mode 100644 pkg/registry/crane/crane.go delete mode 100644 pkg/registry/crane/crane_test.go delete mode 100644 pkg/registry/crane/keychain.go delete mode 100644 pkg/registry/crane/registry.go delete mode 100644 pkg/registry/crane/registry_test.go delete mode 100644 pkg/registry/handler/handler.go delete mode 100644 pkg/registry/save/interface.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/auth/api_version.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/auth/challenge/addr.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/auth/challenge/authchallenge.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/auth/session.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/blob_writer.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/errors.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/repository.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/transport/http_reader.go delete mode 100644 pkg/registry/save/lib/distributionpkg/client/transport/transport.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxyauth.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxyblobstore.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxymanifeststore.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxymetrics.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxyregistry.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/proxytagservice.go delete mode 100644 pkg/registry/save/lib/distributionpkg/proxy/scheduler/scheduler.go delete mode 100644 pkg/registry/save/lib/imagemanifest/blobmanifest.go delete mode 100644 pkg/registry/save/lib/imagemanifest/imagemanifest.go delete mode 100644 pkg/registry/save/registry_save.go delete mode 100644 pkg/registry/save/save.go delete mode 100644 pkg/registry/save/save_test.go delete mode 100644 pkg/registry/save/testdata/config.json delete mode 100644 pkg/registry/sync/sync.go rename pkg/{ => utils}/passwd/passwd.go (100%) delete mode 100644 pkg/utils/tmpl/tmpl.go diff --git a/cmd/sealctl/cmd/registry.go b/cmd/sealctl/cmd/registry.go index 37b10b3c853..34beb126391 100644 --- a/cmd/sealctl/cmd/registry.go +++ b/cmd/sealctl/cmd/registry.go @@ -16,9 +16,8 @@ limitations under the License. package cmd import ( + "github.com/labring/sreg/pkg/registry/commands" "github.com/spf13/cobra" - - "github.com/labring/sealos/pkg/registry/commands" ) func newRegistryCmd() *cobra.Command { diff --git a/cmd/sealctl/cmd/root.go b/cmd/sealctl/cmd/root.go index 2522c0eeb9e..93193eeacef 100644 --- a/cmd/sealctl/cmd/root.go +++ b/cmd/sealctl/cmd/root.go @@ -18,6 +18,8 @@ import ( "fmt" "os" + sreglog "github.com/labring/sreg/pkg/utils/logger" + "github.com/spf13/cobra" "k8s.io/kubectl/pkg/util/templates" @@ -52,6 +54,7 @@ func Execute() { func init() { cobra.OnInitialize(func() { logger.CfgConsoleLogger(debug, false) + sreglog.CfgConsoleLogger(debug, false) }) rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logger") diff --git a/cmd/sealos/cmd/registry.go b/cmd/sealos/cmd/registry.go index 0ea3cb05300..9bddecefe13 100644 --- a/cmd/sealos/cmd/registry.go +++ b/cmd/sealos/cmd/registry.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + sregcmd "github.com/labring/sreg/pkg/registry/commands" "github.com/spf13/cobra" "github.com/labring/sealos/pkg/registry/commands" @@ -27,10 +28,11 @@ func newRegistryCmd(examplePrefix string) *cobra.Command { Use: "registry", Short: "registry related", } + examplePrefix = examplePrefix + " registry" cmd.AddCommand(commands.NewRegistryPasswdCmd()) - cmd.AddCommand(commands.NewServeRegistryCommand()) - cmd.AddCommand(commands.NewRegistryImageSaveCmd(examplePrefix)) - cmd.AddCommand(commands.NewSyncRegistryCommand(examplePrefix)) - cmd.AddCommand(commands.NewCopyRegistryCommand(examplePrefix)) + cmd.AddCommand(sregcmd.NewServeRegistryCommand()) + cmd.AddCommand(sregcmd.NewRegistryImageSaveCmd(examplePrefix)) + cmd.AddCommand(sregcmd.NewSyncRegistryCommand(examplePrefix)) + cmd.AddCommand(sregcmd.NewCopyRegistryCommand(examplePrefix)) return cmd } diff --git a/cmd/sealos/cmd/root.go b/cmd/sealos/cmd/root.go index 280fc777dd5..331503585f9 100644 --- a/cmd/sealos/cmd/root.go +++ b/cmd/sealos/cmd/root.go @@ -19,14 +19,15 @@ import ( "io" "os" + sreglog "github.com/labring/sreg/pkg/utils/logger" + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" + "github.com/labring/sealos/pkg/buildah" "github.com/labring/sealos/pkg/constants" "github.com/labring/sealos/pkg/system" "github.com/labring/sealos/pkg/utils/file" "github.com/labring/sealos/pkg/utils/logger" - - "github.com/spf13/cobra" - "k8s.io/kubectl/pkg/util/templates" ) var ( @@ -108,6 +109,7 @@ func setRequireBuildahAnnotation(cmd *cobra.Command) { func onBootOnDie() { logger.CfgConsoleAndFileLogger(debug, constants.LogPath(), "sealos", false) + sreglog.CfgConsoleAndFileLogger(debug, constants.LogPath(), "sealos", false) val, err := system.Get(system.DataRootConfigKey) errExit(err) constants.DefaultClusterRootFsDir = val diff --git a/go.mod b/go.mod index 88d7d42b1fc..5f65faf0032 100644 --- a/go.mod +++ b/go.mod @@ -159,6 +159,7 @@ require ( github.com/klauspost/pgzip v1.2.6 // indirect github.com/kr/fs v0.1.0 // indirect github.com/labring/sealos/controllers/user v0.0.0 // indirect + github.com/labring/sreg v0.1.1 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect diff --git a/go.sum b/go.sum index a80095466ca..5dca808bb3f 100644 --- a/go.sum +++ b/go.sum @@ -387,6 +387,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labring/sreg v0.1.1 h1:JVI2FwdSQMPgQGRrTdsM6SEfy7eKd0F93VkweoNp3b4= +github.com/labring/sreg v0.1.1/go.mod h1:d519C7n2ekJJX6KxsUV78MeKO5Zon+atP4FkfwB++DM= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6 h1:unJdfS94Y3k85TKy+mvKzjW5R9rIC+Lv4KGbE7uNu0I= diff --git a/pkg/buildah/imagesaver.go b/pkg/buildah/imagesaver.go index fbd192c6b83..1e219c55d0d 100644 --- a/pkg/buildah/imagesaver.go +++ b/pkg/buildah/imagesaver.go @@ -20,8 +20,8 @@ import ( "runtime" "strings" - "github.com/labring/sealos/pkg/registry/crane" - "github.com/labring/sealos/pkg/registry/save" + "github.com/labring/sreg/pkg/registry/crane" + "github.com/labring/sreg/pkg/registry/save" "github.com/containerd/containerd/platforms" "github.com/containers/buildah/pkg/parse" @@ -30,7 +30,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/labring/sealos/pkg/buildimage" + "github.com/labring/sreg/pkg/buildimage" + "github.com/labring/sealos/pkg/constants" "github.com/labring/sealos/pkg/utils/logger" ) diff --git a/pkg/buildah/merge.go b/pkg/buildah/merge.go index 7f4930468ca..75878b90d05 100644 --- a/pkg/buildah/merge.go +++ b/pkg/buildah/merge.go @@ -22,6 +22,8 @@ import ( "path" "strings" + "github.com/labring/sealos/pkg/image" + "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah" @@ -30,8 +32,6 @@ import ( v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" - "github.com/labring/sealos/pkg/buildimage" - "github.com/labring/sealos/pkg/utils/logger" "github.com/labring/sealos/pkg/utils/rand" ) @@ -153,7 +153,7 @@ func mergeImagesWithScratchContainer(newImageName string, images []string, sette } } - dockerfile, err := buildimage.MergeDockerfileFromImages(imageObjList) + dockerfile, err := image.MergeDockerfileFromImages(imageObjList) if err != nil { return nil, err } diff --git a/pkg/buildimage/chart.go b/pkg/buildimage/chart.go deleted file mode 100644 index 50b51dd3831..00000000000 --- a/pkg/buildimage/chart.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright © 2022 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buildimage - -import ( - "fmt" - "os" - "path" - "strings" - - "github.com/labring/sealos/pkg/buildimage/manifests" - "github.com/labring/sealos/pkg/utils/file" - "github.com/labring/sealos/pkg/utils/logger" - "github.com/labring/sealos/pkg/utils/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "k8s.io/apimachinery/pkg/util/sets" -) - -func ParseChartImages(chartPath string) ([]string, error) { - logger.Debug("lookup in path", chartPath) - - if !file.IsExist(chartPath) { - logger.Debug("path %s is not exists, skip", chartPath) - return nil, nil - } - subChartPaths, _ := getChartSub1Paths(chartPath) - if len(subChartPaths) == 0 { - return []string{}, nil // if chartPath not exist, return [] - } - allImages := sets.NewString() - for _, subChartPath := range subChartPaths { - logger.Debug("sub chart is", subChartPath) - c := Chart{ - Path: chartPath + "/" + subChartPath, - } - images, err := c.GetImages() - if err != nil { - return nil, err - } - if len(images) > 0 { - allImages = allImages.Insert(images...) - } - } - return allImages.List(), nil -} - -type Chart struct { - File string - Path string -} - -// LoadPath loads from a path. -func (c Chart) loadPath() (*chart.Chart, error) { - logger.Debug("trying to load chart %s", c.Path) - ccc, err := loader.Load(c.Path) - if err != nil { - return nil, err - } - return ccc, nil -} - -// return {filename:content,...} -func (c Chart) getRenderContent() (map[string]string, error) { - ccc, err := c.loadPath() - if err != nil { - return nil, err - } - - // todo: remove hardcode - valuesFile := path.Join(path.Dir(c.Path), fmt.Sprintf("%s.values.yaml", ccc.Metadata.Name)) - values := make(map[string]interface{}) - if file.IsExist(valuesFile) { - logger.Debug("found named customize values file %s", valuesFile) - if err = yaml.UnmarshalYamlFromFile(valuesFile, &values); err != nil { - return nil, err - } - } - if err := chartutil.ProcessDependencies(ccc, values); err != nil { - return nil, err - } - - options := chartutil.ReleaseOptions{ - Name: "dryrun", - } - valuesToRender, err := chartutil.ToRenderValues(ccc, values, options, nil) - if err != nil { - return nil, err - } - content, err := engine.Render(ccc, valuesToRender) - if err != nil { - return nil, err - } - return content, nil -} - -func (c Chart) GetImages() ([]string, error) { - content, err := c.getRenderContent() - if err != nil { - return nil, err - } - list := sets.NewString() - delLF := func(a string) string { - return strings.Replace(a, "\n", "", -1) - } - - for _, v := range content { - if delLF(v) == "" { // Text has no content - continue - } - images, err := manifests.ParseImages(v) - if err != nil { - return nil, err - } - list = list.Insert(images...) - } - return list.List(), nil -} - -// from charts dir get 1 sub path,not nested -func getChartSub1Paths(pp string) ([]string, error) { - var paths []string - dir, err := os.ReadDir(pp) - if err != nil { - return nil, err - } - for _, d := range dir { - if d.IsDir() || strings.HasSuffix(d.Name(), "tgz") { - paths = append(paths, d.Name()) - } - } - return paths, nil -} diff --git a/pkg/buildimage/chart_test.go b/pkg/buildimage/chart_test.go deleted file mode 100644 index a8ee3de1552..00000000000 --- a/pkg/buildimage/chart_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2022 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buildimage - -import ( - "reflect" - "testing" -) - -func TestParseChartImages(t *testing.T) { - type args struct { - chartPath string - } - tests := []struct { - name string - args args - want []string - wantErr bool - }{ - { - name: "chart", - args: args{ - chartPath: "test/charts", - }, - want: []string{"docker.io/cilium/istio_proxy", "quay.io/cilium/cilium:v1.12.0", "quay.io/cilium/operator-generic:v1.12.0"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseChartImages(tt.args.chartPath) - if (err != nil) != tt.wantErr { - t.Errorf("ParseChartImages() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseChartImages() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/buildimage/images.go b/pkg/buildimage/images.go deleted file mode 100644 index 9cba1a05d1c..00000000000 --- a/pkg/buildimage/images.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright © 2021 Alibaba Group Holding Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buildimage - -import ( - "fmt" - "io/fs" - "path" - "path/filepath" - "strings" - - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/labring/sealos/pkg/buildimage/manifests" - "github.com/labring/sealos/pkg/constants" - "github.com/labring/sealos/pkg/utils/file" - "github.com/labring/sealos/pkg/utils/logger" - strutil "github.com/labring/sealos/pkg/utils/strings" - "github.com/labring/sealos/pkg/utils/tmpl" - "github.com/labring/sealos/pkg/utils/yaml" -) - -func ParseYamlImages(dir string) ([]string, error) { - if !file.IsExist(dir) { - logger.Debug("path %s is not exists,skip", dir) - return nil, nil - } - list := sets.NewString() - imageSearcher, err := manifests.NewManifests() - if err != nil { - return nil, err - } - - err = filepath.Walk(dir, func(path string, f fs.FileInfo, err error) error { - if err != nil { - return err - } - if f.IsDir() || (!yaml.Matcher(f.Name()) && !tmpl.Matcher(f.Name())) { - return nil - } - ima, err := imageSearcher.ListImages(path) - if err != nil { - return err - } - list = list.Insert(ima...) - return nil - }) - - if err != nil { - return nil, err - } - return formalizeImages(list.List()), nil -} - -func formalizeImages(images []string) (res []string) { - for i := range images { - img := strings.TrimSpace(images[i]) - if img == "" || strings.HasPrefix(img, "#") { - continue - } - res = append(res, strutil.TrimQuotes(img)) - } - return -} - -func ParseShimImages(dir string) ([]string, error) { - if dir == "" || !file.IsExist(dir) { - logger.Debug("path %s is not exists,skip", dir) - return nil, nil - } - list := sets.NewString() - paths, err := file.GetFiles(dir) - if err != nil { - return nil, fmt.Errorf("load image list files error: %w", err) - } - for _, p := range paths { - images, err := file.ReadLines(p) - if err != nil { - return nil, fmt.Errorf("load image list error: %w", err) - } - list = list.Insert(images...) - } - imageList := formalizeImages(list.List()) - return imageList, nil -} - -func List(dir string) ([]string, error) { - wrapGetImageErr := func(err error, s string) error { - return fmt.Errorf("failed to get images in %s: %w", s, err) - } - chrtDir := path.Join(dir, constants.ChartsDirName) - chrtImgs, err := ParseChartImages(chrtDir) - if err != nil { - return nil, wrapGetImageErr(err, chrtDir) - } - - yamlDir := path.Join(dir, constants.ManifestsDirName) - yamlImgs, err := ParseYamlImages(yamlDir) - if err != nil { - return nil, wrapGetImageErr(err, yamlDir) - } - shimDir := path.Join(dir, constants.ImagesDirName, constants.ImageShimDirName) - shimImgs, err := ParseShimImages(shimDir) - if err != nil { - return nil, wrapGetImageErr(err, shimDir) - } - list := sets.NewString(chrtImgs...) - list = list.Insert(yamlImgs...) - list = list.Insert(shimImgs...) - return list.List(), nil -} diff --git a/pkg/buildimage/images_test.go b/pkg/buildimage/images_test.go deleted file mode 100644 index bc4783aba55..00000000000 --- a/pkg/buildimage/images_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2021 Alibaba Group Holding Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buildimage - -import ( - "testing" -) - -func TestParseYamlImages(t *testing.T) { - data, err := ParseYamlImages("/Users/cuisongliu/Workspaces/go/src/github.com/sealyun/cloud-kernel/runtime/cni/31901/etc") - if err != nil { - t.Errorf(err.Error()) - return - } - t.Logf("%v", data) -} diff --git a/pkg/buildimage/manifests/manifests.go b/pkg/buildimage/manifests/manifests.go deleted file mode 100644 index b6ac323084e..00000000000 --- a/pkg/buildimage/manifests/manifests.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2021 Alibaba Group Holding Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manifests - -import ( - "fmt" - "os" - "path/filepath" -) - -type Manifests struct{} - -// ListImages List all the containers images in manifest files -func (manifests *Manifests) ListImages(yamlFile string) ([]string, error) { - yamlBytes, err := os.ReadFile(filepath.Clean(yamlFile)) - if err != nil { - return nil, fmt.Errorf("read file failed %s", err) - } - - images, err := ParseImages(string(yamlBytes)) - if err != nil { - return nil, fmt.Errorf("failed to parse images from file %s", err) - } - - return images, nil -} - -func NewManifests() (*Manifests, error) { - return &Manifests{}, nil -} diff --git a/pkg/buildimage/manifests/manifests_test.go b/pkg/buildimage/manifests/manifests_test.go deleted file mode 100644 index 9ec551bab12..00000000000 --- a/pkg/buildimage/manifests/manifests_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2021 Alibaba Group Holding Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manifests - -import ( - "reflect" - "sort" - "testing" -) - -func TestListImages(t *testing.T) { - type args struct { - clusterName string - } - tests := []struct { - name string - args args - want []string - wantErr bool - }{ - { - "test list manifests images", - args{"my_cluster"}, - []string{"k8s.gcr.io/etcd:3.4.13-0", "k8s.gcr.io/kube-apiserver:v1.19.7", "k8s.gcr.io/kube-controller-manager:v1.19.7", "k8s.gcr.io/kube-scheduler:v1.19.7"}, - false, - }, - } - manifests, _ := NewManifests() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := manifests.ListImages(tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("ListImages() error = %v, wantErr %v", err, tt.wantErr) - return - } - sort.Strings(got) - sort.Strings(tt.want) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ListImages() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/buildimage/manifests/utils.go b/pkg/buildimage/manifests/utils.go deleted file mode 100644 index e31a17b9ebf..00000000000 --- a/pkg/buildimage/manifests/utils.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2021 Alibaba Group Holding Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manifests - -import ( - "bufio" - "io" - "strings" - - "github.com/containers/image/v5/docker/reference" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/labring/sealos/pkg/utils/logger" - strutil "github.com/labring/sealos/pkg/utils/strings" -) - -// ParseImages parse image from yaml content -func ParseImages(body string) ([]string, error) { - list := sets.NewString() - rd := bufio.NewReader(strings.NewReader(body)) - for { - line, _, err := rd.ReadLine() - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - l := parseImageRefFromLine(string(line)) - if l != "" { - list = list.Insert(l) - } - } - return list.List(), nil -} - -const imageIdentity = "image:" - -// parseImageRefFromLine return valid image ref from line or null -func parseImageRefFromLine(s string) string { - s = strings.TrimSpace(s) - if strings.HasPrefix(s, "#") { - return "" - } - idx := strings.Index(s, imageIdentity) - if idx < 0 { - return "" - } - imageStr := strutil.TrimQuotes(strings.TrimSpace(s[idx+len(imageIdentity):])) - if imageStr == "" { - return "" - } - named, err := reference.ParseNormalizedNamed(imageStr) - if err != nil { - logger.Error("failed to parse image name %s: %v", s, err) - return "" - } - // return namedtag only - if namedTag, ok := named.(reference.NamedTagged); ok { - return namedTag.Name() + ":" + namedTag.Tag() - } - return named.String() -} diff --git a/pkg/buildimage/test/.gitignore b/pkg/buildimage/test/.gitignore deleted file mode 100644 index 711a39c541a..00000000000 --- a/pkg/buildimage/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -charts/ \ No newline at end of file diff --git a/pkg/checker/cri_shim_checker.go b/pkg/checker/cri_shim_checker.go index 66410325bec..988ee895434 100644 --- a/pkg/checker/cri_shim_checker.go +++ b/pkg/checker/cri_shim_checker.go @@ -23,7 +23,8 @@ import ( "github.com/labring/image-cri-shim/pkg/types" - "github.com/labring/sealos/pkg/buildimage" + "github.com/labring/sreg/pkg/buildimage" + "github.com/labring/sealos/pkg/template" v2 "github.com/labring/sealos/pkg/types/v1beta1" "github.com/labring/sealos/pkg/utils/logger" diff --git a/pkg/checker/registry_checker.go b/pkg/checker/registry_checker.go index fe786067caa..bfcf15617a1 100644 --- a/pkg/checker/registry_checker.go +++ b/pkg/checker/registry_checker.go @@ -21,7 +21,7 @@ import ( "fmt" "os" - "github.com/labring/sealos/pkg/registry/crane" + "github.com/labring/sreg/pkg/registry/crane" "github.com/labring/sealos/pkg/registry/helpers" diff --git a/pkg/filesystem/registry/sync.go b/pkg/filesystem/registry/sync.go index adf44ae967d..bf43e6a32b3 100644 --- a/pkg/filesystem/registry/sync.go +++ b/pkg/filesystem/registry/sync.go @@ -30,10 +30,11 @@ import ( "github.com/containers/image/v5/types" "golang.org/x/sync/errgroup" + "github.com/labring/sreg/pkg/registry/handler" + "github.com/labring/sreg/pkg/registry/sync" + "github.com/labring/sealos/pkg/constants" "github.com/labring/sealos/pkg/filesystem" - "github.com/labring/sealos/pkg/registry/handler" - "github.com/labring/sealos/pkg/registry/sync" "github.com/labring/sealos/pkg/ssh" v2 "github.com/labring/sealos/pkg/types/v1beta1" "github.com/labring/sealos/pkg/utils/file" diff --git a/pkg/buildimage/merge.go b/pkg/image/merge.go similarity index 99% rename from pkg/buildimage/merge.go rename to pkg/image/merge.go index 96df1e87fb6..adad5c096ef 100644 --- a/pkg/buildimage/merge.go +++ b/pkg/image/merge.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package buildimage +package image // nosemgrep: go.lang.security.audit.xss.import-text-template.import-text-template import ( diff --git a/pkg/buildimage/merge_test.go b/pkg/image/merge_test.go similarity index 99% rename from pkg/buildimage/merge_test.go rename to pkg/image/merge_test.go index e4b7fbae1a5..8877f69e128 100644 --- a/pkg/buildimage/merge_test.go +++ b/pkg/image/merge_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package buildimage +package image import ( "testing" diff --git a/pkg/registry/commands/copy.go b/pkg/registry/commands/copy.go deleted file mode 100644 index 625082d192d..00000000000 --- a/pkg/registry/commands/copy.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2023 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package commands - -import ( - "context" - "fmt" - "time" - - imagecopy "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/types" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "golang.org/x/sync/errgroup" - - "github.com/labring/sealos/pkg/registry/crane" - "github.com/labring/sealos/pkg/registry/sync" - httputils "github.com/labring/sealos/pkg/utils/http" -) - -type globalOptions struct { - overrideArch string // Architecture to use for choosing images, instead of the runtime one - overrideOS string // OS to use for choosing images, instead of the runtime one - overrideVariant string // Architecture variant to use for choosing images, instead of the runtime one - all bool // Sync all images if SOURCE-IMAGE is a list -} - -func (opts *globalOptions) RegisterFlags(fs *pflag.FlagSet) { - fs.StringVar(&opts.overrideArch, "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images") - fs.StringVar(&opts.overrideOS, "override-os", "", "use `OS` instead of the running OS for choosing images") - fs.StringVar(&opts.overrideVariant, "override-variant", "", "use `VARIANT` instead of the running architecture variant for choosing images") - fs.BoolVarP(&opts.all, "all", "a", false, "Sync all images if SOURCE-IMAGE is a list") -} - -func (opts *globalOptions) newSystemContext() *types.SystemContext { - ctx := &types.SystemContext{ - ArchitectureChoice: opts.overrideArch, - OSChoice: opts.overrideOS, - VariantChoice: opts.overrideVariant, - DockerInsecureSkipTLSVerify: types.OptionalBoolTrue, - } - return ctx -} - -func NewCopyRegistryCommand(examplePrefix string) *cobra.Command { - opts := globalOptions{} - cmd := &cobra.Command{ - Use: "copy SOURCE_IMAGE DST_REGISTRY", - Short: "copy single one image to registry", - Args: cobra.ExactArgs(2), - Example: fmt.Sprintf(`%[1]s registry copy docker.io/labring/kubernetes:v1.25.0 sealos.hub:5000 -%[1]s registry copy -a docker.io/labring/kubernetes:v1.25.0 sealos.hub:5000`, examplePrefix), - RunE: func(cmd *cobra.Command, args []string) error { - return runCopy(cmd, args[0], args[1], opts) - }, - } - fs := cmd.Flags() - fs.SetInterspersed(false) - opts.RegisterFlags(fs) - return cmd -} - -func runCopy(cmd *cobra.Command, source, dst string, opts globalOptions) error { - ctx := cmd.Context() - out := cmd.OutOrStdout() - imageListSelection := imagecopy.CopySystemImage - if opts.all { - imageListSelection = imagecopy.CopyAllImages - } - - auths, err := crane.GetAuthInfo(nil) - if err != nil { - return err - } - - sys := opts.newSystemContext() - dep := sync.ParseRegistryAddress(dst) - - eg, _ := errgroup.WithContext(ctx) - probeCtx, cancel := context.WithTimeout(ctx, 3*time.Second) - defer cancel() - - eg.Go(func() error { - return httputils.WaitUntilEndpointAlive(probeCtx, dep) - }) - if err := eg.Wait(); err != nil { - return err - } - - srcRef, err := sync.ImageNameToReference(sys, source, auths) - if err != nil { - return err - } - if err := sync.ToImage(ctx, sys, srcRef, dep, imageListSelection); err != nil { - return err - } - fmt.Fprintln(out, "Copy image completed") - return nil -} diff --git a/pkg/registry/commands/flags.go b/pkg/registry/commands/flags.go deleted file mode 100644 index 4f3fa8675f2..00000000000 --- a/pkg/registry/commands/flags.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2023 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package commands - -import ( - "fmt" - "os" - "runtime" - - "github.com/labring/sealos/pkg/registry/crane" - - "github.com/docker/docker/api/types" - "github.com/spf13/pflag" - - "github.com/labring/sealos/pkg/utils/file" -) - -type registrySaveResults struct { - registryPullRegistryDir string - registryPullArch string - registryPullMaxPullProcs int -} - -func (opts *registrySaveResults) RegisterFlags(fs *pflag.FlagSet) { - fs.SetInterspersed(false) - fs.StringVar(&opts.registryPullArch, "arch", runtime.GOARCH, "pull images arch") - fs.StringVar(&opts.registryPullRegistryDir, "registry-dir", "/var/lib/registry", "registry data dir path") - fs.IntVar(&opts.registryPullMaxPullProcs, "max-pull-procs", 5, "maximum number of goroutines for pulling") -} - -func (opts *registrySaveResults) CheckAuth() (map[string]types.AuthConfig, error) { - if !file.IsExist(opts.registryPullRegistryDir) { - _ = os.MkdirAll(opts.registryPullRegistryDir, 0755) - } - cfg, err := crane.GetAuthInfo(nil) - if err != nil { - return nil, fmt.Errorf("auth info is error: %w", err) - } - return cfg, nil -} - -type registrySaveRawResults struct { - *registrySaveResults - images []string -} - -func (opts *registrySaveRawResults) RegisterFlags(fs *pflag.FlagSet) { - opts.registrySaveResults.RegisterFlags(fs) - fs.StringSliceVar(&opts.images, "images", []string{}, "images list") -} diff --git a/pkg/registry/commands/save.go b/pkg/registry/commands/save.go deleted file mode 100644 index 89937a5228c..00000000000 --- a/pkg/registry/commands/save.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2022 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package commands - -import ( - "context" - "errors" - "fmt" - - "github.com/labring/sealos/pkg/registry/save" - - "github.com/docker/docker/api/types" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/spf13/cobra" - - "github.com/labring/sealos/pkg/buildimage" - "github.com/labring/sealos/pkg/utils/logger" -) - -func NewRegistryImageSaveCmd(examplePrefix string) *cobra.Command { - var auth map[string]types.AuthConfig - var images []string - flagsResults := registrySaveRawResults{ - registrySaveResults: new(registrySaveResults), - } - cmd := &cobra.Command{ - Use: "save", - Short: "save images to local registry dir", - Example: fmt.Sprintf(` -%[1]s registry save --registry-dir=/tmp/registry . -%[1]s registry save --registry-dir=/tmp/registry --images=docker.io/library/busybox:latest`, examplePrefix), - RunE: func(cmd *cobra.Command, args []string) error { - is := save.NewImageSaver(context.Background(), flagsResults.registryPullMaxPullProcs, auth) - outImages, err := is.SaveImages(images, flagsResults.registryPullRegistryDir, v1.Platform{OS: "linux", Architecture: flagsResults.registryPullArch}) - if err != nil { - return err - } - logger.Info("images pulled: %+v", outImages) - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 && len(flagsResults.images) == 0 { - return errors.New("'--images' and args cannot be empty at the same time") - } - var err error - if len(flagsResults.images) > 0 { - images = flagsResults.images - } else { - images, err = buildimage.List(args[0]) - } - if err != nil { - return err - } - auth, err = flagsResults.CheckAuth() - if err != nil { - return err - } - return nil - }, - } - fs := cmd.Flags() - fs.SetInterspersed(false) - flagsResults.RegisterFlags(fs) - return cmd -} diff --git a/pkg/registry/commands/serve.go b/pkg/registry/commands/serve.go deleted file mode 100644 index 340fde961e9..00000000000 --- a/pkg/registry/commands/serve.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright 2023 fengxsong@outlook.com - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package commands - -import ( - "errors" - "fmt" - "net" - "net/http" - "os" - "time" - - "github.com/distribution/distribution/v3/configuration" - "github.com/google/go-containerregistry/pkg/registry" - "github.com/spf13/cobra" - - "github.com/labring/sealos/pkg/registry/handler" - "github.com/labring/sealos/pkg/utils/logger" -) - -func newRegistryServeFilesystemCommand() *cobra.Command { - var ( - port int - disableLogging bool - logLevel string - ) - cmd := &cobra.Command{ - Use: "filesystem", - Aliases: []string{"fs"}, - Short: "run a docker distribution registry server for dir", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - config, err := handler.NewConfig(args[0], port) - if err != nil { - return err - } - if !disableLogging { - logger.Info("serving on %s", config.HTTP.Addr) - } - config.Log.Level = configuration.Loglevel(logLevel) - config.Log.AccessLog.Disabled = disableLogging - errCh := handler.Run(cmd.Context(), config) - return <-errCh - }, - } - cmd.Flags().IntVarP(&port, "port", "p", 0, "listening port, default is random unused port") - cmd.Flags().BoolVar(&disableLogging, "disable-logging", false, "disable logging output") - cmd.Flags().StringVar(&logLevel, "log-level", "error", "configure logging level") - return cmd -} - -func newRegistryServeInMemCommand() *cobra.Command { - return &cobra.Command{ - Use: "inmem", - Short: "Serve an in-memory registry implementation", - Long: `This sub-command serves an in-memory registry implementation on port :8080 (or $PORT) - -The command blocks while the server accepts pushes and pulls. - -Contents are only stored in memory, and when the process exits, pushed data is lost.`, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - port := os.Getenv("PORT") - if port == "" { - port = "0" - } - listener, err := net.Listen("tcp", "localhost:"+port) - if err != nil { - return err - } - porti := listener.Addr().(*net.TCPAddr).Port - port = fmt.Sprintf("%d", porti) - - s := &http.Server{ - ReadHeaderTimeout: 5 * time.Second, // prevent slowloris, quiet linter - Handler: registry.New(), - } - logger.Info("serving on port %s", port) - - errCh := make(chan error) - go func() { errCh <- s.Serve(listener) }() - - <-ctx.Done() - logger.Info("shutting down...") - if err := s.Shutdown(ctx); err != nil { - return err - } - - if err := <-errCh; !errors.Is(err, http.ErrServerClosed) { - return err - } - return nil - }, - } -} - -func NewServeRegistryCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - Short: "run a docker distribution registry server", - } - cmd.AddCommand(newRegistryServeFilesystemCommand()) - cmd.AddCommand(newRegistryServeInMemCommand()) - return cmd -} diff --git a/pkg/registry/commands/sync.go b/pkg/registry/commands/sync.go deleted file mode 100644 index b8f54e69aa8..00000000000 --- a/pkg/registry/commands/sync.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 2023 fengxsong@outlook.com - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package commands - -import ( - "context" - "fmt" - "time" - - imagecopy "github.com/containers/image/v5/copy" - "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" - - "github.com/labring/sealos/pkg/registry/sync" - httputils "github.com/labring/sealos/pkg/utils/http" -) - -func NewSyncRegistryCommand(examplePrefix string) *cobra.Command { - opts := globalOptions{} - cmd := &cobra.Command{ - Use: "sync SOURCE_REGISTRY DST_REGISTRY", - Short: "sync all images from one registry to another", - Args: cobra.ExactArgs(2), - Example: fmt.Sprintf(`%[1]s registry sync 127.0.0.1:9090 sealos.hub:5000 -%[1]s registry sync -a 127.0.0.1:9090 sealos.hub:5000`, examplePrefix), - RunE: func(cmd *cobra.Command, args []string) error { - return runSync(cmd, args[0], args[1], opts) - }, - } - fs := cmd.Flags() - fs.SetInterspersed(false) - opts.RegisterFlags(fs) - return cmd -} - -func runSync(cmd *cobra.Command, source, dst string, opts globalOptions) error { - ctx := cmd.Context() - out := cmd.OutOrStdout() - - imageListSelection := imagecopy.CopySystemImage - if opts.all { - imageListSelection = imagecopy.CopyAllImages - } - - sep := sync.ParseRegistryAddress(source) - dep := sync.ParseRegistryAddress(dst) - - eg, _ := errgroup.WithContext(ctx) - probeCtx, cancel := context.WithTimeout(ctx, 3*time.Second) - defer cancel() - - eg.Go(func() error { - return httputils.WaitUntilEndpointAlive(probeCtx, sep) - }) - eg.Go(func() error { - return httputils.WaitUntilEndpointAlive(probeCtx, dep) - }) - if err := eg.Wait(); err != nil { - return err - } - - syncOpts := &sync.Options{ - SystemContext: opts.newSystemContext(), - Source: sep, - Target: dst, - ReportWriter: out, - SelectionOptions: []imagecopy.ImageListSelection{ - imageListSelection, - }, - OmitError: true, - } - if err := sync.ToRegistry(ctx, syncOpts); err != nil { - return err - } - fmt.Fprintln(out, "Sync completed") - return nil -} diff --git a/pkg/registry/crane/crane.go b/pkg/registry/crane/crane.go deleted file mode 100644 index b39198d81f0..00000000000 --- a/pkg/registry/crane/crane.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crane - -import ( - "runtime" - "strings" - - name2 "github.com/google/go-containerregistry/pkg/name" - - v1 "github.com/google/go-containerregistry/pkg/v1" - - "github.com/docker/docker/api/types" - "github.com/google/go-containerregistry/pkg/crane" - - "github.com/labring/sealos/pkg/utils/http" -) - -func GetCraneOptions(authConfig map[string]types.AuthConfig) []crane.Option { - return []crane.Option{crane.WithAuthFromKeychain(NewDefaultKeychain(authConfig)), crane.WithTransport(http.DefaultSkipVerify)} -} - -func GetImageManifestFromAuth(image string, authConfig map[string]types.AuthConfig) (newImage string, data []byte, cfg *types.AuthConfig, err error) { - newImage = image - if authConfig == nil { - authConfig, err = GetAuthInfo(nil) - if err != nil { - return newImage, nil, nil, err - } - for domain, c := range authConfig { - if NormalizeRegistry(domain) != domain { - authConfig[NormalizeRegistry(domain)] = c - delete(authConfig, domain) - } - } - } - craneOptsBase := GetCraneOptions(authConfig) - var ref name2.Reference - var repo string - ref, err = name2.ParseReference(image) - if err != nil { - return newImage, nil, nil, err - } - parts := strings.SplitN(ref.Name(), "/", 2) - if len(parts) == 2 && (strings.ContainsRune(parts[0], '.') || strings.ContainsRune(parts[0], ':')) { - // The first part of the repository is treated as the registry domain - // iff it contains a '.' or ':' character, otherwise it is all repository - // and the domain defaults to Docker Hub. - _ = parts[0] - repo = parts[1] - } - craneOptsBase = append(craneOptsBase, crane.WithPlatform(&v1.Platform{ - OS: "linux", - Architecture: runtime.GOARCH, - })) - for domain, c := range authConfig { - craneOpts := craneOptsBase[:] - newImage = strings.Join([]string{domain, repo}, "/") - ref, err = name2.ParseReference(newImage) - if url, ook := http.IsURL(c.ServerAddress); ook { - if url.Scheme == "http" { - ref, err = name2.ParseReference(newImage, name2.Insecure) - craneOpts = append(craneOpts, crane.Insecure) - } - } - //logs.Debug.SetOutput(os.Stderr) - if err != nil { - continue - } - data, err = crane.Manifest(ref.Name(), craneOpts...) - if err == nil { - return newImage, data, &c, nil - } - } - return image, nil, nil, err -} - -func NormalizeRegistry(registry string) string { - switch registry { - case "registry-1.docker.io", "docker.io", "index.docker.io": - return "index.docker.io" - } - return registry -} - -func GetRegistryDomain(registry string) string { - s := strings.TrimPrefix(registry, "https://") - s = strings.TrimPrefix(s, "http://") - return strings.Split(s, "/")[0] -} diff --git a/pkg/registry/crane/crane_test.go b/pkg/registry/crane/crane_test.go deleted file mode 100644 index f72aa2b0bb2..00000000000 --- a/pkg/registry/crane/crane_test.go +++ /dev/null @@ -1,109 +0,0 @@ -/* -Copyright 2022 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package crane - -import ( - "path/filepath" - "testing" - - "github.com/docker/docker/api/types" -) - -func TestGetImageDigestFromAuth(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join("testdata")) - type args struct { - image string - authConfig map[string]types.AuthConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "", - args: args{ - image: "cuisongliu/alpine:dlv", - authConfig: nil, - }, - wantErr: false, - }, - { - name: "", - args: args{ - image: "cuisongliu/alpine@sha256:190ba1723ba8062867ea51afd52c4d485a31ebf18d5968e8b6aebafdf64e5d86", - authConfig: nil, - }, - wantErr: false, - }, - { - name: "", - args: args{ - image: "nginx@sha256:2982b930fa3a93a57bd4666311665f9cf681d8e46145c07f1b5d2007968150c3", - authConfig: nil, - }, - wantErr: false, - }, - { - name: "", - args: args{ - image: "registry.k8s.io/etcd:3.5.1-0", - authConfig: map[string]types.AuthConfig{"sealos.hub:5000": { - Username: "admin", - Password: "passw0rd", - ServerAddress: "http://sealos.hub:5000", - }}, - }, - wantErr: false, - }, - { - name: "", - args: args{ - image: "registry.k8s.io/etcd:3.5.1-1", - authConfig: map[string]types.AuthConfig{"sealos.hub:5000": { - Username: "admin", - Password: "passw0rd", - ServerAddress: "http://sealos.hub:5000", - }}, - }, - wantErr: true, - }, - { - name: "", - args: args{ - image: "registry.k8s.io/etcd@sha256:64b9ea357325d5db9f8a723dcf503b5a449177b17ac87d69481e126bb724c263", - authConfig: map[string]types.AuthConfig{"sealos.hub:5000": { - Username: "admin", - Password: "passw0rd", - ServerAddress: "http://sealos.hub:5000", - }}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - newImage, got, _, err := GetImageManifestFromAuth(tt.args.image, tt.args.authConfig) - t.Logf("GetImageManifestFromAuth() newImage = %v", newImage) - if (err != nil) != tt.wantErr { - t.Errorf("GetImageManifestFromAuth() error = %v, wantErr %v", err, tt.wantErr) - return - } - t.Logf("GetImageManifestFromAuth() got = %s", string(got)) - }) - } -} diff --git a/pkg/registry/crane/keychain.go b/pkg/registry/crane/keychain.go deleted file mode 100644 index a9a7a342f63..00000000000 --- a/pkg/registry/crane/keychain.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 Google LLC All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crane - -import ( - "sync" - - "github.com/docker/cli/cli/config/types" - dockertypes "github.com/docker/docker/api/types" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" -) - -// defaultKeychain implements Keychain with the semantics of the standard Docker -// credential keychain. -type defaultKeychain struct { - mu sync.Mutex - auths map[string]dockertypes.AuthConfig -} - -const ( - // DefaultAuthKey is the key used for dockerhub in config files, which - // is hardcoded for historical reasons. - DefaultAuthKey = "https://" + name.DefaultRegistry + "/v1/" -) - -func NewDefaultKeychain(auths map[string]dockertypes.AuthConfig) authn.Keychain { - return &defaultKeychain{auths: auths} -} - -// Resolve implements Keychain. -func (dk *defaultKeychain) Resolve(target authn.Resource) (authn.Authenticator, error) { - dk.mu.Lock() - defer dk.mu.Unlock() - - // See: - // https://github.com/google/ko/issues/90 - // https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404 - var cfg, empty types.AuthConfig - for _, key := range []string{ - target.String(), - target.RegistryStr(), - } { - if auth, ok := dk.auths[key]; ok { - cfg = types.AuthConfig(auth) - break - } - // cf.GetAuthConfig automatically sets the ServerAddress attribute. Since - // we don't make use of it, clear the value for a proper "is-empty" test. - // See: https://github.com/google/go-containerregistry/issues/1510 - cfg.ServerAddress = "" - if cfg != empty { - break - } - } - if cfg == empty { - return authn.Anonymous, nil - } - - return authn.FromConfig(authn.AuthConfig{ - Username: cfg.Username, - Password: cfg.Password, - Auth: cfg.Auth, - IdentityToken: cfg.IdentityToken, - RegistryToken: cfg.RegistryToken, - }), nil -} diff --git a/pkg/registry/crane/registry.go b/pkg/registry/crane/registry.go deleted file mode 100644 index 6f92992ed7d..00000000000 --- a/pkg/registry/crane/registry.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2022 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package crane - -import ( - "context" - "fmt" - - "github.com/containers/image/v5/pkg/docker/config" - types2 "github.com/containers/image/v5/types" - "github.com/docker/docker/api/types" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote/transport" - - "github.com/labring/sealos/pkg/utils/logger" - - "github.com/labring/sealos/pkg/utils/http" -) - -func NewRegistry(domain string, authConfig types.AuthConfig) (name.Registry, error) { - domain = GetRegistryDomain(domain) - domain = NormalizeRegistry(domain) - ping := func(v name.Registry) error { - au, _ := NewDefaultKeychain(map[string]types.AuthConfig{domain: authConfig}).Resolve(v) - _, err := transport.NewWithContext(context.Background(), v, au, http.DefaultSkipVerify, nil) - return err - } - - var hub name.Registry - var hubList []name.Registry - var err error - - hub, err = name.NewRegistry(domain) - if err != nil { - return name.Registry{}, err - } - hubList = append(hubList, hub) - - hub, err = name.NewRegistry(domain, name.Insecure) - if err != nil { - return name.Registry{}, err - } - hubList = append(hubList, hub) - for i := range hubList { - if err = ping(hubList[i]); err == nil { - return hubList[i], nil - } - } - return name.Registry{}, fmt.Errorf("not found registry: %+v", err) -} - -func ToAuthConfig(cfg types2.DockerAuthConfig) types.AuthConfig { - return types.AuthConfig{ - Username: cfg.Username, - Password: cfg.Password, - IdentityToken: cfg.IdentityToken, - } -} - -func GetAuthInfo(sys *types2.SystemContext) (map[string]types.AuthConfig, error) { - creds, err := config.GetAllCredentials(sys) - if err != nil { - return nil, err - } - auths := make(map[string]types.AuthConfig, 0) - - for domain, cred := range creds { - logger.Debug("GetAuthInfo getCredentials domain: %s, username: %s", domain, cred.Username) - reg, err := NewRegistry(domain, ToAuthConfig(cred)) - if err == nil { - auths[domain] = types.AuthConfig{ - Username: cred.Username, - Password: cred.Password, - ServerAddress: fmt.Sprintf("%s://%s", reg.Scheme(), reg.RegistryStr()), - IdentityToken: cred.IdentityToken, - } - } - } - return auths, nil -} diff --git a/pkg/registry/crane/registry_test.go b/pkg/registry/crane/registry_test.go deleted file mode 100644 index b6a8b33fa67..00000000000 --- a/pkg/registry/crane/registry_test.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2023 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package crane - -import ( - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/docker/docker/api/types" -) - -func TestNewRegistry(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join("testdata")) - type args struct { - domain string - authConfig types.AuthConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "sealos.hub", - args: args{ - domain: "sealos.hub:5000", - authConfig: types.AuthConfig{ - Username: "admin", - Password: "passw0rd", - }, - }, - wantErr: false, - }, - { - name: "sealos.hub", - args: args{ - domain: "http://sealos.hub:5000", - authConfig: types.AuthConfig{ - Username: "admin", - Password: "passw0rd", - }, - }, - wantErr: false, - }, - { - name: "sealos.hub", - args: args{ - domain: "sealos.hub", - authConfig: types.AuthConfig{ - Username: "admin", - Password: "passw0rd", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewRegistry(tt.args.domain, tt.args.authConfig) - if (err != nil) != tt.wantErr { - t.Errorf("NewRegistry() error = %v, wantErr %v", err, tt.wantErr) - return - } - t.Logf("NewRegistry() got = %v", got) - }) - } -} - -func TestGetAuthInfo(t *testing.T) { - t.Run("no credentials found", func(t *testing.T) { - authConfigs, err := GetAuthInfo(nil) - require.NoError(t, err) - require.Empty(t, authConfigs) - }) - t.Run("has credentials found", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join("testdata")) - authConfigs, err := GetAuthInfo(nil) - require.NoError(t, err) - require.NotEmpty(t, authConfigs) - t.Logf("%+v", authConfigs) - }) -} diff --git a/pkg/registry/handler/handler.go b/pkg/registry/handler/handler.go deleted file mode 100644 index 806192611cc..00000000000 --- a/pkg/registry/handler/handler.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2023 fengxsong@outlook.com - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package handler - -import ( - "bytes" - "context" - "fmt" - "io" - "net" - "net/http" - - "github.com/distribution/distribution/v3/configuration" - dcontext "github.com/distribution/distribution/v3/context" - "github.com/distribution/distribution/v3/registry/handlers" - "github.com/distribution/distribution/v3/registry/listener" - - // registry filesystem driver - _ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem" - log "github.com/sirupsen/logrus" -) - -const tpl = ` -version: 0.1 -storage: - filesystem: - rootdirectory: %s - maintenance: - uploadpurging: - enabled: false -http: - addr: :%d - secret: asecretforlocaldevelopment -` - -func getFreePort() (port int, err error) { - var a *net.TCPAddr - if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { - var l *net.TCPListener - if l, err = net.ListenTCP("tcp", a); err == nil { - defer l.Close() - return l.Addr().(*net.TCPAddr).Port, nil - } - } - return -} - -func NewConfig(root string, port int) (*configuration.Configuration, error) { - if port <= 0 { - var err error - port, err = getFreePort() - if err != nil { - return nil, err - } - } - - s := fmt.Sprintf(tpl, root, port) - rd := bytes.NewReader([]byte(s)) - return configuration.Parse(rd) -} - -func configureLogging(ctx context.Context, config *configuration.Configuration) context.Context { - // disable logging output or increase logging level - logger := log.New() - if config.Log.AccessLog.Disabled { - logger.Out = io.Discard - } - var logLevel log.Level - lvl, err := log.ParseLevel(string(config.Log.Level)) - if err != nil { - logLevel = log.ErrorLevel - } else { - logLevel = lvl - } - logger.SetLevel(logLevel) - logEntry := log.NewEntry(logger) - ctx = dcontext.WithLogger(ctx, logEntry) - dcontext.SetDefaultLogger(logEntry) - return ctx -} - -func New(ctx context.Context, config *configuration.Configuration) (*http.Server, error) { - ctx = configureLogging(ctx, config) - return &http.Server{ - Handler: handlers.NewApp(ctx, config), - }, nil -} - -func Run(ctx context.Context, config *configuration.Configuration) chan error { - errCh := make(chan error, 1) - srv, err := New(ctx, config) - if err != nil { - errCh <- err - return errCh - } - ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr) - if err != nil { - errCh <- err - return errCh - } - go func() { - errCh <- srv.Serve(ln) - }() - go func() { - // once receive nil or error then server will shutdown - <-errCh - _ = srv.Shutdown(ctx) - }() - return errCh -} diff --git a/pkg/registry/password/password.go b/pkg/registry/password/password.go index 2b9610b9600..db3abbaba71 100644 --- a/pkg/registry/password/password.go +++ b/pkg/registry/password/password.go @@ -20,6 +20,8 @@ import ( "fmt" "path" + "github.com/labring/sealos/pkg/utils/passwd" + "github.com/labring/sealos/pkg/registry/helpers" "github.com/labring/image-cri-shim/pkg/types" @@ -27,7 +29,6 @@ import ( "sigs.k8s.io/yaml" "github.com/labring/sealos/pkg/constants" - "github.com/labring/sealos/pkg/passwd" "github.com/labring/sealos/pkg/ssh" "github.com/labring/sealos/pkg/types/v1beta1" "github.com/labring/sealos/pkg/utils/file" diff --git a/pkg/registry/save/interface.go b/pkg/registry/save/interface.go deleted file mode 100644 index b99a01d0413..00000000000 --- a/pkg/registry/save/interface.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package save - -import ( - "context" - - "github.com/google/go-containerregistry/pkg/name" - - "github.com/docker/docker/api/types" - - "github.com/docker/docker/pkg/progress" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -// Registry can save a list of images of the specified platform -type Registry interface { - // SaveImages is not concurrently safe - SaveImages(images []string, dir string, platform v1.Platform) ([]string, error) -} - -type defaultImage struct { - ctx context.Context - domainToImages map[string][]name.Reference - progressOut progress.Output - maxPullProcs int - auths map[string]types.AuthConfig -} - -type tmpRegistryImage struct { - ctx context.Context - maxPullProcs int - auths map[string]types.AuthConfig -} - -func NewImageSaver(ctx context.Context, maxPullProcs int, auths map[string]types.AuthConfig) Registry { - return newTmpRegistrySaver(ctx, maxPullProcs, auths) -} - -func newDefaultRegistrySaver(ctx context.Context, maxPullProcs int, auths map[string]types.AuthConfig) Registry { - if ctx == nil { - ctx = context.Background() - } - if auths == nil { - auths = make(map[string]types.AuthConfig) - } - return &defaultImage{ - ctx: ctx, - domainToImages: make(map[string][]name.Reference), - maxPullProcs: maxPullProcs, - auths: auths, - } -} - -func newTmpRegistrySaver(ctx context.Context, maxPullProcs int, auths map[string]types.AuthConfig) Registry { - if ctx == nil { - ctx = context.Background() - } - if auths == nil { - auths = make(map[string]types.AuthConfig) - } - return &tmpRegistryImage{ - ctx: ctx, - maxPullProcs: maxPullProcs, - auths: auths, - } -} diff --git a/pkg/registry/save/lib/distributionpkg/client/auth/api_version.go b/pkg/registry/save/lib/distributionpkg/client/auth/api_version.go deleted file mode 100644 index cddb9deeafc..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/auth/api_version.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package auth - -import ( - "net/http" - "strings" -) - -// APIVersion represents a version of an API including its -// type and version number. -type APIVersion struct { - // Type refers to the name of a specific API specification - // such as "registry" - Type string - - // Version is the version of the API specification implemented, - // This may omit the revision number and only include - // the major and minor version, such as "2.0" - Version string -} - -// String returns the string formatted API Version -func (v APIVersion) String() string { - return v.Type + "/" + v.Version -} - -// APIVersions gets the API versions out of an HTTP response using the provided -// version header as the key for the HTTP header. -func APIVersions(resp *http.Response, versionHeader string) []APIVersion { - versions := []APIVersion{} - if versionHeader != "" { - for _, supportedVersions := range resp.Header[http.CanonicalHeaderKey(versionHeader)] { - for _, version := range strings.Fields(supportedVersions) { - versions = append(versions, ParseAPIVersion(version)) - } - } - } - return versions -} - -// ParseAPIVersion parses an API version string into an APIVersion -// Format (Expected, not enforced): -// API version string = '/' -// API type = [a-z][a-z0-9]* -// API version = [0-9]+(\.[0-9]+)? -// TODO(dmcgowan): Enforce format, add error condition, remove unknown type -func ParseAPIVersion(versionStr string) APIVersion { - idx := strings.IndexRune(versionStr, '/') - if idx == -1 { - return APIVersion{ - Type: "unknown", - Version: versionStr, - } - } - return APIVersion{ - Type: strings.ToLower(versionStr[:idx]), - Version: versionStr[idx+1:], - } -} diff --git a/pkg/registry/save/lib/distributionpkg/client/auth/challenge/addr.go b/pkg/registry/save/lib/distributionpkg/client/auth/challenge/addr.go deleted file mode 100644 index 744b9113e1c..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/auth/challenge/addr.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package challenge - -import ( - "net/url" - "strings" -) - -// FROM: https://golang.org/src/net/http/http.go -// Given a string of the form "host", "host:port", or "[ipv6::address]:port", -// return true if the string includes a port. -func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } - -// FROM: http://golang.org/src/net/http/transport.go -var portMap = map[string]string{ - "http": "80", - "https": "443", -} - -// canonicalAddr returns url.Host but always with a ":port" suffix -// FROM: http://golang.org/src/net/http/transport.go -func canonicalAddr(url *url.URL) string { - addr := url.Host - if !hasPort(addr) { - return addr + ":" + portMap[url.Scheme] - } - return addr -} diff --git a/pkg/registry/save/lib/distributionpkg/client/auth/challenge/authchallenge.go b/pkg/registry/save/lib/distributionpkg/client/auth/challenge/authchallenge.go deleted file mode 100644 index ba8c755e954..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/auth/challenge/authchallenge.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package challenge - -import ( - "fmt" - "net/http" - "net/url" - "strings" - "sync" -) - -// Challenge carries information from a WWW-Authenticate response header. -// See RFC 2617. -type Challenge struct { - // Scheme is the auth-scheme according to RFC 2617 - Scheme string - - // Parameters are the auth-params according to RFC 2617 - Parameters map[string]string -} - -// Manager manages the challenges for endpoints. -// The challenges are pulled out of HTTP responses. Only -// responses which expect challenges should be added to -// the manager, since a non-unauthorized request will be -// viewed as not requiring challenges. -type Manager interface { - // GetChallenges returns the challenges for the given - // endpoint URL. - GetChallenges(endpoint url.URL) ([]Challenge, error) - - // AddResponse adds the response to the challenge - // manager. The challenges will be parsed out of - // the WWW-Authenicate headers and added to the - // URL which was produced the response. If the - // response was authorized, any challenges for the - // endpoint will be cleared. - AddResponse(resp *http.Response) error -} - -// NewSimpleManager returns an instance of -// Manager which only maps endpoints to challenges -// based on the responses which have been added the -// manager. The simple manager will make no attempt to -// perform requests on the endpoints or cache the responses -// to a backend. -func NewSimpleManager() Manager { - return &simpleManager{ - Challenges: make(map[string][]Challenge), - } -} - -type simpleManager struct { - sync.RWMutex - Challenges map[string][]Challenge -} - -func normalizeURL(endpoint *url.URL) { - endpoint.Host = strings.ToLower(endpoint.Host) - endpoint.Host = canonicalAddr(endpoint) -} - -func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) { - normalizeURL(&endpoint) - - m.RLock() - defer m.RUnlock() - challenges := m.Challenges[endpoint.String()] - return challenges, nil -} - -func (m *simpleManager) AddResponse(resp *http.Response) error { - challenges := ResponseChallenges(resp) - if resp.Request == nil { - return fmt.Errorf("missing request reference") - } - urlCopy := url.URL{ - Path: resp.Request.URL.Path, - Host: resp.Request.URL.Host, - Scheme: resp.Request.URL.Scheme, - } - normalizeURL(&urlCopy) - - m.Lock() - defer m.Unlock() - m.Challenges[urlCopy.String()] = challenges - return nil -} - -// Octet types from RFC 2616. -type octetType byte - -var octetTypes [256]octetType - -const ( - isToken octetType = 1 << iota - isSpace -) - -func init() { - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t octetType - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) - if strings.ContainsRune(" \t\r\n", rune(c)) { - t |= isSpace - } - if isChar && !isCtl && !isSeparator { - t |= isToken - } - octetTypes[c] = t - } -} - -// ResponseChallenges returns a list of authorization challenges -// for the given http Response. Challenges are only checked if -// the response status code was a 401. -func ResponseChallenges(resp *http.Response) []Challenge { - if resp.StatusCode == http.StatusUnauthorized { - // Parse the WWW-Authenticate Header and store the challenges - // on this endpoint object. - return parseAuthHeader(resp.Header) - } - - return nil -} - -func parseAuthHeader(header http.Header) []Challenge { - challenges := []Challenge{} - for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] { - v, p := parseValueAndParams(h) - if v != "" { - challenges = append(challenges, Challenge{Scheme: v, Parameters: p}) - } - } - return challenges -} - -func parseValueAndParams(header string) (value string, params map[string]string) { - params = make(map[string]string) - value, s := expectToken(header) - if value == "" { - return - } - value = strings.ToLower(value) - s = "," + skipSpace(s) - for strings.HasPrefix(s, ",") { - var pkey string - pkey, s = expectToken(skipSpace(s[1:])) - if pkey == "" { - return - } - if !strings.HasPrefix(s, "=") { - return - } - var pvalue string - pvalue, s = expectTokenOrQuoted(s[1:]) - if pvalue == "" { - return - } - pkey = strings.ToLower(pkey) - params[pkey] = pvalue - s = skipSpace(s) - } - return -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpace == 0 { - break - } - } - return s[i:] -} - -func expectToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isToken == 0 { - break - } - } - return s[:i], s[i:] -} - -func expectTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return expectToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} diff --git a/pkg/registry/save/lib/distributionpkg/client/auth/session.go b/pkg/registry/save/lib/distributionpkg/client/auth/session.go deleted file mode 100644 index 2ad283be0a9..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/auth/session.go +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package auth - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth/challenge" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/transport" -) - -var ( - // ErrNoBasicAuthCredentials is returned if a request can't be authorized with - // basic auth due to lack of credentials. - ErrNoBasicAuthCredentials = errors.New("no basic auth credentials") - - // ErrNoToken is returned if a request is successful but the body does not - // contain an authorization token. - ErrNoToken = errors.New("authorization server did not include a token in the response") -) - -const defaultClientID = "registry-client" - -// AuthenticationHandler is an interface for authorizing a request from -// params from a "WWW-Authenicate" header for a single scheme. -type AuthenticationHandler interface { - // Scheme returns the scheme as expected from the "WWW-Authenicate" header. - Scheme() string - - // AuthorizeRequest adds the authorization header to a request (if needed) - // using the parameters from "WWW-Authenticate" method. The parameters - // values depend on the scheme. - AuthorizeRequest(req *http.Request, params map[string]string) error -} - -// CredentialStore is an interface for getting credentials for -// a given URL -type CredentialStore interface { - // Basic returns basic auth for the given URL - Basic(*url.URL) (string, string) - - // RefreshToken returns a refresh token for the - // given URL and service - RefreshToken(*url.URL, string) string - - // SetRefreshToken sets the refresh token if none - // is provided for the given url and service - SetRefreshToken(realm *url.URL, service, token string) -} - -// NewAuthorizer creates an authorizer which can handle multiple authentication -// schemes. The handlers are tried in order, the higher priority authentication -// methods should be first. The challengeMap holds a list of challenges for -// a given root API endpoint (for example "https://registry-1.docker.io/v2/"). -func NewAuthorizer(manager challenge.Manager, handlers ...AuthenticationHandler) transport.RequestModifier { - return &endpointAuthorizer{ - challenges: manager, - handlers: handlers, - } -} - -type endpointAuthorizer struct { - challenges challenge.Manager - handlers []AuthenticationHandler -} - -func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error { - pingPath := req.URL.Path - if v2Root := strings.Index(req.URL.Path, "/v2/"); v2Root != -1 { - pingPath = pingPath[:v2Root+4] - } else if v1Root := strings.Index(req.URL.Path, "/v1/"); v1Root != -1 { - pingPath = pingPath[:v1Root] + "/v2/" - } else { - return nil - } - - ping := url.URL{ - Host: req.URL.Host, - Scheme: req.URL.Scheme, - Path: pingPath, - } - - challenges, err := ea.challenges.GetChallenges(ping) - if err != nil { - return err - } - - if len(challenges) > 0 { - for _, handler := range ea.handlers { - for _, c := range challenges { - if c.Scheme != handler.Scheme() { - continue - } - if err := handler.AuthorizeRequest(req, c.Parameters); err != nil { - return err - } - } - } - } - - return nil -} - -// This is the minimum duration a token can last (in seconds). -// A token must not live less than 60 seconds because older versions -// of the Docker client didn't read their expiration from the token -// response and assumed 60 seconds. So to remain compatible with -// those implementations, a token must live at least this long. -const minimumTokenLifetimeSeconds = 60 - -// Private interface for time used by this package to enable tests to provide their own implementation. -type clock interface { - Now() time.Time -} - -type tokenHandler struct { - creds CredentialStore - transport http.RoundTripper - clock clock - - offlineAccess bool - forceOAuth bool - clientID string - scopes []Scope - - tokenLock sync.Mutex - tokenCache string - tokenExpiration time.Time - - logger Logger -} - -// Scope is a type which is serializable to a string -// using the allow scope grammar. -type Scope interface { - String() string -} - -// RepositoryScope represents a token scope for access -// to a repository. -type RepositoryScope struct { - Repository string - Class string - Actions []string -} - -// String returns the string representation of the repository -// using the scope grammar -func (rs RepositoryScope) String() string { - repoType := "repository" - // Keep existing format for image class to maintain backwards compatibility - // with authorization servers which do not support the expanded grammar. - if rs.Class != "" && rs.Class != "image" { - repoType = fmt.Sprintf("%s(%s)", repoType, rs.Class) - } - return fmt.Sprintf("%s:%s:%s", repoType, rs.Repository, strings.Join(rs.Actions, ",")) -} - -// RegistryScope represents a token scope for access -// to resources in the registry. -type RegistryScope struct { - Name string - Actions []string -} - -// String returns the string representation of the user -// using the scope grammar -func (rs RegistryScope) String() string { - return fmt.Sprintf("registry:%s:%s", rs.Name, strings.Join(rs.Actions, ",")) -} - -// Logger defines the injectable logging interface, used on TokenHandlers. -type Logger interface { - Debugf(format string, args ...interface{}) -} - -func logDebugf(logger Logger, format string, args ...interface{}) { - if logger == nil { - return - } - logger.Debugf(format, args...) -} - -// TokenHandlerOptions is used to configure a new token handler -type TokenHandlerOptions struct { - Transport http.RoundTripper - Credentials CredentialStore - - OfflineAccess bool - ForceOAuth bool - ClientID string - Scopes []Scope - Logger Logger -} - -// An implementation of clock for providing real time data. -type realClock struct{} - -// Now implements clock -func (realClock) Now() time.Time { return time.Now() } - -// NewTokenHandler creates a new AuthenicationHandler which supports -// fetching tokens from a remote token server. -func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler { - // Create options... - return NewTokenHandlerWithOptions(TokenHandlerOptions{ - Transport: transport, - Credentials: creds, - Scopes: []Scope{ - RepositoryScope{ - Repository: scope, - Actions: actions, - }, - }, - }) -} - -// NewTokenHandlerWithOptions creates a new token handler using the provided -// options structure. -func NewTokenHandlerWithOptions(options TokenHandlerOptions) AuthenticationHandler { - handler := &tokenHandler{ - transport: options.Transport, - creds: options.Credentials, - offlineAccess: options.OfflineAccess, - forceOAuth: options.ForceOAuth, - clientID: options.ClientID, - scopes: options.Scopes, - clock: realClock{}, - logger: options.Logger, - } - - return handler -} - -func (th *tokenHandler) client() *http.Client { - return &http.Client{ - Transport: th.transport, - Timeout: 15 * time.Second, - } -} - -func (th *tokenHandler) Scheme() string { - return "bearer" -} - -func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error { - var additionalScopes []string - if fromParam := req.URL.Query().Get("from"); fromParam != "" { - additionalScopes = append(additionalScopes, RepositoryScope{ - Repository: fromParam, - Actions: []string{"pull"}, - }.String()) - } - - token, err := th.getToken(params, additionalScopes...) - if err != nil { - return err - } - - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - return nil -} - -func (th *tokenHandler) getToken(params map[string]string, additionalScopes ...string) (string, error) { - th.tokenLock.Lock() - defer th.tokenLock.Unlock() - scopes := make([]string, 0, len(th.scopes)+len(additionalScopes)) - for _, scope := range th.scopes { - scopes = append(scopes, scope.String()) - } - var addedScopes bool - for _, scope := range additionalScopes { - if hasScope(scopes, scope) { - continue - } - scopes = append(scopes, scope) - addedScopes = true - } - - now := th.clock.Now() - if now.After(th.tokenExpiration) || addedScopes { - token, expiration, err := th.fetchToken(params, scopes) - if err != nil { - return "", err - } - - // do not update cache for added scope tokens - if !addedScopes { - th.tokenCache = token - th.tokenExpiration = expiration - } - - return token, nil - } - - return th.tokenCache, nil -} - -func hasScope(scopes []string, scope string) bool { - for _, s := range scopes { - if s == scope { - return true - } - } - return false -} - -type postTokenResponse struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int `json:"expires_in"` - IssuedAt time.Time `json:"issued_at"` - Scope string `json:"scope"` -} - -func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) { - form := url.Values{} - form.Set("scope", strings.Join(scopes, " ")) - form.Set("service", service) - - clientID := th.clientID - if clientID == "" { - // Use default client, this is a required field - clientID = defaultClientID - } - form.Set("client_id", clientID) - - if refreshToken != "" { - form.Set("grant_type", "refresh_token") - form.Set("refresh_token", refreshToken) - } else if th.creds != nil { - form.Set("grant_type", "password") - username, password := th.creds.Basic(realm) - form.Set("username", username) - form.Set("password", password) - - // attempt to get a refresh token - form.Set("access_type", "offline") - } else { - // refuse to do oauth without a grant type - return "", time.Time{}, fmt.Errorf("no supported grant type") - } - - resp, err := th.client().PostForm(realm.String(), form) - if err != nil { - return "", time.Time{}, err - } - defer resp.Body.Close() - - if !client.SuccessStatus(resp.StatusCode) { - err := client.HandleErrorResponse(resp) - return "", time.Time{}, err - } - - decoder := json.NewDecoder(resp.Body) - - var tr postTokenResponse - if err = decoder.Decode(&tr); err != nil { - return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err) - } - - if tr.AccessToken == "" { - return "", time.Time{}, ErrNoToken - } - - if tr.RefreshToken != "" && tr.RefreshToken != refreshToken { - th.creds.SetRefreshToken(realm, service, tr.RefreshToken) - } - - if tr.ExpiresIn < minimumTokenLifetimeSeconds { - // The default/minimum lifetime. - tr.ExpiresIn = minimumTokenLifetimeSeconds - logDebugf(th.logger, "Increasing token expiration to: %d seconds", tr.ExpiresIn) - } - - if tr.IssuedAt.IsZero() { - // issued_at is optional in the token response. - tr.IssuedAt = th.clock.Now().UTC() - } - - return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil -} - -type getTokenResponse struct { - Token string `json:"token"` - AccessToken string `json:"access_token"` - ExpiresIn int `json:"expires_in"` - IssuedAt time.Time `json:"issued_at"` - RefreshToken string `json:"refresh_token"` -} - -func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) { - req, err := http.NewRequest("GET", realm.String(), nil) - if err != nil { - return "", time.Time{}, err - } - - reqParams := req.URL.Query() - - if service != "" { - reqParams.Add("service", service) - } - - for _, scope := range scopes { - reqParams.Add("scope", scope) - } - - if th.offlineAccess { - reqParams.Add("offline_token", "true") - clientID := th.clientID - if clientID == "" { - clientID = defaultClientID - } - reqParams.Add("client_id", clientID) - } - - if th.creds != nil { - username, password := th.creds.Basic(realm) - if username != "" && password != "" { - reqParams.Add("account", username) - req.SetBasicAuth(username, password) - } - } - - req.URL.RawQuery = reqParams.Encode() - - resp, err := th.client().Do(req) - if err != nil { - return "", time.Time{}, err - } - defer resp.Body.Close() - - if !client.SuccessStatus(resp.StatusCode) { - err := client.HandleErrorResponse(resp) - return "", time.Time{}, err - } - - decoder := json.NewDecoder(resp.Body) - - var tr getTokenResponse - if err = decoder.Decode(&tr); err != nil { - return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err) - } - - if tr.RefreshToken != "" && th.creds != nil { - th.creds.SetRefreshToken(realm, service, tr.RefreshToken) - } - - // `access_token` is equivalent to `token` and if both are specified - // the choice is undefined. Canonicalize `access_token` by sticking - // things in `token`. - if tr.AccessToken != "" { - tr.Token = tr.AccessToken - } - - if tr.Token == "" { - return "", time.Time{}, ErrNoToken - } - - if tr.ExpiresIn < minimumTokenLifetimeSeconds { - // The default/minimum lifetime. - tr.ExpiresIn = minimumTokenLifetimeSeconds - logDebugf(th.logger, "Increasing token expiration to: %d seconds", tr.ExpiresIn) - } - - if tr.IssuedAt.IsZero() { - // issued_at is optional in the token response. - tr.IssuedAt = th.clock.Now().UTC() - } - - return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil -} - -func (th *tokenHandler) fetchToken(params map[string]string, scopes []string) (token string, expiration time.Time, err error) { - realm, ok := params["realm"] - if !ok { - return "", time.Time{}, errors.New("no realm specified for token auth challenge") - } - - // TODO(dmcgowan): Handle empty scheme and relative realm - realmURL, err := url.Parse(realm) - if err != nil { - return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err) - } - - service := params["service"] - - var refreshToken string - - if th.creds != nil { - refreshToken = th.creds.RefreshToken(realmURL, service) - } - - if refreshToken != "" || th.forceOAuth { - return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes) - } - - return th.fetchTokenWithBasicAuth(realmURL, service, scopes) -} - -type basicHandler struct { - creds CredentialStore - remoteURL *url.URL -} - -// NewBasicHandler creaters a new authentiation handler which adds -// basic authentication credentials to a request. -func NewBasicHandler(creds CredentialStore, remoteURL *url.URL) AuthenticationHandler { - return &basicHandler{ - creds: creds, - remoteURL: remoteURL, - } -} - -func (*basicHandler) Scheme() string { - return "basic" -} - -func (bh *basicHandler) AuthorizeRequest(req *http.Request, params map[string]string) error { - if bh.creds != nil { - username, password := bh.creds.Basic(bh.remoteURL) - if username != "" && password != "" { - req.SetBasicAuth(username, password) - return nil - } - } - return ErrNoBasicAuthCredentials -} diff --git a/pkg/registry/save/lib/distributionpkg/client/blob_writer.go b/pkg/registry/save/lib/distributionpkg/client/blob_writer.go deleted file mode 100644 index be15cb77dd4..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/blob_writer.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "time" - - "github.com/distribution/distribution/v3" -) - -type httpBlobUpload struct { - statter distribution.BlobStatter - client *http.Client - - uuid string - startedAt time.Time - - location string // always the last value of the location header. - offset int64 - closed bool -} - -func (hbu *httpBlobUpload) Reader() (io.ReadCloser, error) { - panic("Not implemented") -} - -func (hbu *httpBlobUpload) handleErrorResponse(resp *http.Response) error { - if resp.StatusCode == http.StatusNotFound { - return distribution.ErrBlobUploadUnknown - } - return HandleErrorResponse(resp) -} - -func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) { - req, err := http.NewRequest("PATCH", hbu.location, io.NopCloser(r)) - if err != nil { - return 0, err - } - defer req.Body.Close() - - resp, err := hbu.client.Do(req) - if err != nil { - return 0, err - } - - if !SuccessStatus(resp.StatusCode) { - return 0, hbu.handleErrorResponse(resp) - } - - hbu.uuid = resp.Header.Get("Docker-Upload-UUID") - location, err := sanitizeLocation(resp.Header.Get("Location"), hbu.location) - if err != nil { - return 0, err - } - hbu.location = location - - rng := resp.Header.Get("Range") - var start, end int64 - if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil { - return 0, err - } else if n != 2 || end < start { - return 0, fmt.Errorf("bad range format: %s", rng) - } - - hbu.offset += end - start + 1 - return (end - start + 1), nil -} - -func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) { - req, err := http.NewRequest("PATCH", hbu.location, bytes.NewReader(p)) - if err != nil { - return 0, err - } - req.Header.Set("Content-Range", fmt.Sprintf("%d-%d", hbu.offset, hbu.offset+int64(len(p)-1))) - req.Header.Set("Content-Length", fmt.Sprintf("%d", len(p))) - req.Header.Set("Content-Type", "application/octet-stream") - - resp, err := hbu.client.Do(req) - if err != nil { - return 0, err - } - - if !SuccessStatus(resp.StatusCode) { - return 0, hbu.handleErrorResponse(resp) - } - - hbu.uuid = resp.Header.Get("Docker-Upload-UUID") - location, err := sanitizeLocation(resp.Header.Get("Location"), hbu.location) - if err != nil { - return 0, err - } - hbu.location = location - - rng := resp.Header.Get("Range") - var start, end int - if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil { - return 0, err - } else if n != 2 || end < start { - return 0, fmt.Errorf("bad range format: %s", rng) - } - - hbu.offset += int64(end - start + 1) - return (end - start + 1), nil -} - -func (hbu *httpBlobUpload) Size() int64 { - return hbu.offset -} - -func (hbu *httpBlobUpload) ID() string { - return hbu.uuid -} - -func (hbu *httpBlobUpload) StartedAt() time.Time { - return hbu.startedAt -} - -func (hbu *httpBlobUpload) Commit(ctx context.Context, desc distribution.Descriptor) (distribution.Descriptor, error) { - // TODO(dmcgowan): Check if already finished, if so just fetch - req, err := http.NewRequestWithContext(ctx, "PUT", hbu.location, nil) - if err != nil { - return distribution.Descriptor{}, err - } - - values := req.URL.Query() - values.Set("digest", desc.Digest.String()) - req.URL.RawQuery = values.Encode() - - resp, err := hbu.client.Do(req) - if err != nil { - return distribution.Descriptor{}, err - } - defer resp.Body.Close() - - if !SuccessStatus(resp.StatusCode) { - return distribution.Descriptor{}, hbu.handleErrorResponse(resp) - } - - return hbu.statter.Stat(ctx, desc.Digest) -} - -func (hbu *httpBlobUpload) Cancel(ctx context.Context) error { - req, err := http.NewRequestWithContext(ctx, "DELETE", hbu.location, nil) - if err != nil { - return err - } - resp, err := hbu.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound || SuccessStatus(resp.StatusCode) { - return nil - } - return hbu.handleErrorResponse(resp) -} - -func (hbu *httpBlobUpload) Close() error { - hbu.closed = true - return nil -} diff --git a/pkg/registry/save/lib/distributionpkg/client/errors.go b/pkg/registry/save/lib/distributionpkg/client/errors.go deleted file mode 100644 index 186030f5bcb..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/errors.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth/challenge" - - "github.com/distribution/distribution/v3/registry/api/errcode" -) - -// ErrNoErrorsInBody is returned when an HTTP response body parses to an empty -// errcode.Errors slice. -var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body") - -// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is -// returned when making a registry api call. -type UnexpectedHTTPStatusError struct { - Status string -} - -func (e *UnexpectedHTTPStatusError) Error() string { - return fmt.Sprintf("received unexpected HTTP status: %s", e.Status) -} - -// UnexpectedHTTPResponseError is returned when an expected HTTP status code -// is returned, but the content was unexpected and failed to be parsed. -type UnexpectedHTTPResponseError struct { - ParseErr error - StatusCode int - Response []byte -} - -func (e *UnexpectedHTTPResponseError) Error() string { - return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response)) -} - -func parseHTTPErrorResponse(statusCode int, r io.Reader) error { - var errors errcode.Errors - body, err := io.ReadAll(r) - if err != nil { - return err - } - - // For backward compatibility, handle irregularly formatted - // messages that contain a "details" field. - var detailsErr struct { - Details string `json:"details"` - } - err = json.Unmarshal(body, &detailsErr) - if err == nil && detailsErr.Details != "" { - switch statusCode { - case http.StatusUnauthorized: - return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details) - case http.StatusTooManyRequests: - return errcode.ErrorCodeTooManyRequests.WithMessage(detailsErr.Details) - default: - return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details) - } - } - - if err := json.Unmarshal(body, &errors); err != nil { - return &UnexpectedHTTPResponseError{ - ParseErr: err, - StatusCode: statusCode, - Response: body, - } - } - - if len(errors) == 0 { - // If there was no error specified in the body, return - // UnexpectedHTTPResponseError. - return &UnexpectedHTTPResponseError{ - ParseErr: ErrNoErrorsInBody, - StatusCode: statusCode, - Response: body, - } - } - - return errors -} - -func makeErrorList(err error) []error { - if errL, ok := err.(errcode.Errors); ok { - return []error(errL) - } - return []error{err} -} - -func mergeErrors(err1, err2 error) error { - return errcode.Errors(append(makeErrorList(err1), makeErrorList(err2)...)) -} - -// HandleErrorResponse returns error parsed from HTTP response for an -// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An -// UnexpectedHTTPStatusError returned for response code outside of expected -// range. -func HandleErrorResponse(resp *http.Response) error { - if resp.StatusCode >= 400 && resp.StatusCode < 500 { - // Check for OAuth errors within the `WWW-Authenticate` header first - // See https://tools.ietf.org/html/rfc6750#section-3 - for _, c := range challenge.ResponseChallenges(resp) { - if c.Scheme == "bearer" { - var err errcode.Error - // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 - switch c.Parameters["error"] { - case "invalid_token": - err.Code = errcode.ErrorCodeUnauthorized - case "insufficient_scope": - err.Code = errcode.ErrorCodeDenied - default: - continue - } - if description := c.Parameters["error_description"]; description != "" { - err.Message = description - } else { - err.Message = err.Code.Message() - } - - return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body)) - } - } - err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) - if uErr, ok := err.(*UnexpectedHTTPResponseError); ok && resp.StatusCode == 401 { - return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) - } - return err - } - return &UnexpectedHTTPStatusError{Status: resp.Status} -} - -// SuccessStatus returns true if the argument is a successful HTTP response -// code (in the range 200 - 399 inclusive). -func SuccessStatus(status int) bool { - return status >= 200 && status <= 399 -} diff --git a/pkg/registry/save/lib/distributionpkg/client/repository.go b/pkg/registry/save/lib/distributionpkg/client/repository.go deleted file mode 100644 index 510e06635bf..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/repository.go +++ /dev/null @@ -1,960 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/transport" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/reference" - v2 "github.com/distribution/distribution/v3/registry/api/v2" - "github.com/distribution/distribution/v3/registry/storage/cache" - "github.com/distribution/distribution/v3/registry/storage/cache/memory" - "github.com/opencontainers/go-digest" -) - -// Registry provides an interface for calling Repositories, which returns a catalog of repositories. -type Registry interface { - Repositories(ctx context.Context, repos []string, last string) (n int, err error) -} - -// checkHTTPRedirect is a callback that can manipulate redirected HTTP -// requests. It is used to preserve Accept and Range headers. -func checkHTTPRedirect(req *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return errors.New("stopped after 10 redirects") - } - - if len(via) > 0 { - for headerName, headerVals := range via[0].Header { - if headerName != "Accept" && headerName != "Range" { - continue - } - for _, val := range headerVals { - // Don't add to redirected request if redirected - // request already has a header with the same - // name and value. - hasValue := false - for _, existingVal := range req.Header[headerName] { - if existingVal == val { - hasValue = true - break - } - } - if !hasValue { - req.Header.Add(headerName, val) - } - } - } - } - - return nil -} - -// NewRegistry creates a registry namespace which can be used to get a listing of repositories -func NewRegistry(baseURL string, transport http.RoundTripper) (Registry, error) { - ub, err := v2.NewURLBuilderFromString(baseURL, false) - if err != nil { - return nil, err - } - - client := &http.Client{ - Transport: transport, - Timeout: 1 * time.Minute, - CheckRedirect: checkHTTPRedirect, - } - - return ®istry{ - client: client, - ub: ub, - }, nil -} - -type registry struct { - client *http.Client - ub *v2.URLBuilder -} - -// Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size -// of the slice, starting at the value provided in 'last'. The number of entries will be returned along with io.EOF if there -// are no more entries -func (r *registry) Repositories(ctx context.Context, entries []string, last string) (int, error) { - var numFilled int - var returnErr error - - values := buildCatalogValues(len(entries), last) - u, err := r.ub.BuildCatalogURL(values) - if err != nil { - return 0, err - } - - req, err := http.NewRequestWithContext(ctx, "GET", u, nil) - if err != nil { - return 0, err - } - resp, err := r.client.Do(req) - if err != nil { - return 0, err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - var ctlg struct { - Repositories []string `json:"repositories"` - } - decoder := json.NewDecoder(resp.Body) - - if err := decoder.Decode(&ctlg); err != nil { - return 0, err - } - - numFilled = copy(entries, ctlg.Repositories) - - link := resp.Header.Get("Link") - if link == "" { - returnErr = io.EOF - } - } else { - return 0, HandleErrorResponse(resp) - } - - return numFilled, returnErr -} - -// NewRepository creates a new Repository for the given repository name and base URL. -func NewRepository(name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) { - ub, err := v2.NewURLBuilderFromString(baseURL, false) - if err != nil { - return nil, err - } - - client := &http.Client{ - Transport: transport, - CheckRedirect: checkHTTPRedirect, - // TODO(dmcgowan): create cookie jar - } - - return &repository{ - client: client, - ub: ub, - name: name, - }, nil -} - -type repository struct { - client *http.Client - ub *v2.URLBuilder - name reference.Named -} - -func (r *repository) Named() reference.Named { - return r.name -} - -func (r *repository) Blobs(ctx context.Context) distribution.BlobStore { - statter := &blobStatter{ - name: r.name, - ub: r.ub, - client: r.client, - } - return &blobs{ - name: r.name, - ub: r.ub, - client: r.client, - statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(0), statter), - } -} - -func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { - // todo(richardscothern): options should be sent over the wire - return &manifests{ - name: r.name, - ub: r.ub, - client: r.client, - etags: make(map[string]string), - }, nil -} - -func (r *repository) Tags(ctx context.Context) distribution.TagService { - return &tags{ - client: r.client, - ub: r.ub, - name: r.Named(), - } -} - -// tags implements remote tagging operations. -type tags struct { - client *http.Client - ub *v2.URLBuilder - name reference.Named -} - -// All returns all tags -func (t *tags) All(ctx context.Context) ([]string, error) { - var tags []string - - listURLStr, err := t.ub.BuildTagsURL(t.name) - if err != nil { - return tags, err - } - - listURL, err := url.Parse(listURLStr) - if err != nil { - return tags, err - } - - for { - req, err := http.NewRequestWithContext(ctx, "GET", listURL.String(), nil) - if err != nil { - return nil, err - } - resp, err := t.client.Do(req) - if err != nil { - return tags, err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - b, err := io.ReadAll(resp.Body) - if err != nil { - return tags, err - } - - tagsResponse := struct { - Tags []string `json:"tags"` - }{} - if err := json.Unmarshal(b, &tagsResponse); err != nil { - return tags, err - } - tags = append(tags, tagsResponse.Tags...) - if link := resp.Header.Get("Link"); link != "" { - linkURLStr := strings.Trim(strings.Split(link, ";")[0], "<>") - linkURL, err := url.Parse(linkURLStr) - if err != nil { - return tags, err - } - - listURL = listURL.ResolveReference(linkURL) - } else { - return tags, nil - } - } else { - return tags, HandleErrorResponse(resp) - } - } -} - -func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) { - desc := distribution.Descriptor{} - headers := response.Header - - ctHeader := headers.Get("Content-Type") - if ctHeader == "" { - return distribution.Descriptor{}, errors.New("missing or empty Content-Type header") - } - desc.MediaType = ctHeader - - digestHeader := headers.Get("Docker-Content-Digest") - if digestHeader == "" { - bytes, err := io.ReadAll(response.Body) - if err != nil { - return distribution.Descriptor{}, err - } - _, desc, err := distribution.UnmarshalManifest(ctHeader, bytes) - if err != nil { - return distribution.Descriptor{}, err - } - return desc, nil - } - - dgst, err := digest.Parse(digestHeader) - if err != nil { - return distribution.Descriptor{}, err - } - desc.Digest = dgst - - lengthHeader := headers.Get("Content-Length") - if lengthHeader == "" { - return distribution.Descriptor{}, errors.New("missing or empty Content-Length header") - } - length, err := strconv.ParseInt(lengthHeader, 10, 64) - if err != nil { - return distribution.Descriptor{}, err - } - desc.Size = length - - return desc, nil -} - -// Get issues a HEAD request for a Manifest against its named endpoint in order -// to construct a descriptor for the tag. If the registry doesn't support HEADing -// a manifest, fallback to GET. -func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) { - ref, err := reference.WithTag(t.name, tag) - if err != nil { - return distribution.Descriptor{}, err - } - u, err := t.ub.BuildManifestURL(ref) - if err != nil { - return distribution.Descriptor{}, err - } - - newRequest := func(method string) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, method, u, nil) - if err != nil { - return nil, err - } - - for _, t := range distribution.ManifestMediaTypes() { - req.Header.Add("Accept", t) - } - resp, err := t.client.Do(req) - return resp, err - } - - resp, err := newRequest("HEAD") - if err != nil { - return distribution.Descriptor{}, err - } - defer resp.Body.Close() - - switch { - case resp.StatusCode >= 200 && resp.StatusCode < 400 && len(resp.Header.Get("Docker-Content-Digest")) > 0: - // if the response is a success AND a Docker-Content-Digest can be retrieved from the headers - return descriptorFromResponse(resp) - default: - // if the response is an error - there will be no body to decode. - // Issue a GET request: - // - for data from a server that does not handle HEAD - // - to get error details in case of a failure - resp, err = newRequest("GET") - if err != nil { - return distribution.Descriptor{}, err - } - defer resp.Body.Close() - - if resp.StatusCode >= 200 && resp.StatusCode < 400 { - return descriptorFromResponse(resp) - } - return distribution.Descriptor{}, HandleErrorResponse(resp) - } -} - -func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) { - panic("not implemented") -} - -func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error { - panic("not implemented") -} - -func (t *tags) Untag(ctx context.Context, tag string) error { - ref, err := reference.WithTag(t.name, tag) - if err != nil { - return err - } - u, err := t.ub.BuildManifestURL(ref) - if err != nil { - return err - } - - req, err := http.NewRequest("DELETE", u, nil) - if err != nil { - return err - } - - resp, err := t.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - return nil - } - return HandleErrorResponse(resp) -} - -type manifests struct { - name reference.Named - ub *v2.URLBuilder - client *http.Client - etags map[string]string -} - -func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) { - ref, err := reference.WithDigest(ms.name, dgst) - if err != nil { - return false, err - } - u, err := ms.ub.BuildManifestURL(ref) - if err != nil { - return false, err - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", u, nil) - if err != nil { - return false, err - } - resp, err := ms.client.Do(req) - if err != nil { - return false, err - } - - if SuccessStatus(resp.StatusCode) { - return true, nil - } else if resp.StatusCode == http.StatusNotFound { - return false, nil - } - return false, HandleErrorResponse(resp) -} - -// AddEtagToTag allows a client to supply an eTag to Get which will be -// used for a conditional HTTP request. If the eTag matches, a nil manifest -// and ErrManifestNotModified error will be returned. etag is automatically -// quoted when added to this map. -func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption { - return etagOption{tag, etag} -} - -type etagOption struct{ tag, etag string } - -func (o etagOption) Apply(ms distribution.ManifestService) error { - if ms, ok := ms.(*manifests); ok { - ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag) - return nil - } - return fmt.Errorf("etag options is a client-only option") -} - -// ReturnContentDigest allows a client to set a the content digest on -// a successful request from the 'Docker-Content-Digest' header. This -// returned digest is represents the digest which the registry uses -// to refer to the content and can be used to delete the content. -func ReturnContentDigest(dgst *digest.Digest) distribution.ManifestServiceOption { - return contentDigestOption{dgst} -} - -type contentDigestOption struct{ digest *digest.Digest } - -func (o contentDigestOption) Apply(ms distribution.ManifestService) error { - return nil -} - -func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { - var ( - digestOrTag string - ref reference.Named - err error - contentDgst *digest.Digest - mediaTypes []string - ) - - for _, option := range options { - switch opt := option.(type) { - case distribution.WithTagOption: - digestOrTag = opt.Tag - ref, err = reference.WithTag(ms.name, opt.Tag) - if err != nil { - return nil, err - } - case contentDigestOption: - contentDgst = opt.digest - case distribution.WithManifestMediaTypesOption: - mediaTypes = opt.MediaTypes - default: - err := option.Apply(ms) - if err != nil { - return nil, err - } - } - } - - if digestOrTag == "" { - digestOrTag = dgst.String() - ref, err = reference.WithDigest(ms.name, dgst) - if err != nil { - return nil, err - } - } - - if len(mediaTypes) == 0 { - mediaTypes = distribution.ManifestMediaTypes() - } - - u, err := ms.ub.BuildManifestURL(ref) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, "GET", u, nil) - if err != nil { - return nil, err - } - - for _, t := range mediaTypes { - req.Header.Add("Accept", t) - } - - if _, ok := ms.etags[digestOrTag]; ok { - req.Header.Set("If-None-Match", ms.etags[digestOrTag]) - } - - resp, err := ms.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusNotModified { - return nil, distribution.ErrManifestNotModified - } else if SuccessStatus(resp.StatusCode) { - if contentDgst != nil { - dgst, err := digest.Parse(resp.Header.Get("Docker-Content-Digest")) - if err == nil { - *contentDgst = dgst - } - } - mt := resp.Header.Get("Content-Type") - body, err := io.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - m, _, err := distribution.UnmarshalManifest(mt, body) - if err != nil { - return nil, err - } - return m, nil - } - return nil, HandleErrorResponse(resp) -} - -// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the -// tag name in order to build the correct upload URL. -func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) { - ref := ms.name - var tagged bool - - for _, option := range options { - if opt, ok := option.(distribution.WithTagOption); ok { - var err error - ref, err = reference.WithTag(ref, opt.Tag) - if err != nil { - return "", err - } - tagged = true - } else { - err := option.Apply(ms) - if err != nil { - return "", err - } - } - } - mediaType, p, err := m.Payload() - if err != nil { - return "", err - } - - if !tagged { - // generate a canonical digest and Put by digest - _, d, err := distribution.UnmarshalManifest(mediaType, p) - if err != nil { - return "", err - } - ref, err = reference.WithDigest(ref, d.Digest) - if err != nil { - return "", err - } - } - - manifestURL, err := ms.ub.BuildManifestURL(ref) - if err != nil { - return "", err - } - - putRequest, err := http.NewRequestWithContext(ctx, "PUT", manifestURL, bytes.NewReader(p)) - if err != nil { - return "", err - } - - putRequest.Header.Set("Content-Type", mediaType) - - resp, err := ms.client.Do(putRequest) - if err != nil { - return "", err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - dgstHeader := resp.Header.Get("Docker-Content-Digest") - dgst, err := digest.Parse(dgstHeader) - if err != nil { - return "", err - } - - return dgst, nil - } - - return "", HandleErrorResponse(resp) -} - -func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error { - ref, err := reference.WithDigest(ms.name, dgst) - if err != nil { - return err - } - u, err := ms.ub.BuildManifestURL(ref) - if err != nil { - return err - } - req, err := http.NewRequestWithContext(ctx, "DELETE", u, nil) - if err != nil { - return err - } - - resp, err := ms.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - return nil - } - return HandleErrorResponse(resp) -} - -// todo(richardscothern): Restore interface and implementation with merge of #1050 -/*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) { - panic("not supported") -}*/ - -type blobs struct { - name reference.Named - ub *v2.URLBuilder - client *http.Client - - statter distribution.BlobDescriptorService - distribution.BlobDeleter -} - -func sanitizeLocation(location, base string) (string, error) { - baseURL, err := url.Parse(base) - if err != nil { - return "", err - } - - locationURL, err := url.Parse(location) - if err != nil { - return "", err - } - - return baseURL.ResolveReference(locationURL).String(), nil -} - -func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { - return bs.statter.Stat(ctx, dgst) -} - -func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { - reader, err := bs.Open(ctx, dgst) - if err != nil { - return nil, err - } - defer reader.Close() - - return io.ReadAll(reader) -} - -func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { - ref, err := reference.WithDigest(bs.name, dgst) - if err != nil { - return nil, err - } - blobURL, err := bs.ub.BuildBlobURL(ref) - if err != nil { - return nil, err - } - - return transport.NewHTTPReadSeeker(ctx, bs.client, blobURL, - func(resp *http.Response) error { - if resp.StatusCode == http.StatusNotFound { - return distribution.ErrBlobUnknown - } - return HandleErrorResponse(resp) - }), nil -} - -func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { - desc, err := bs.statter.Stat(ctx, dgst) - if err != nil { - return err - } - - w.Header().Set("Content-Length", strconv.FormatInt(desc.Size, 10)) - w.Header().Set("Content-Type", desc.MediaType) - w.Header().Set("Docker-Content-Digest", dgst.String()) - w.Header().Set("Etag", dgst.String()) - - if r.Method == http.MethodHead { - return nil - } - - blob, err := bs.Open(ctx, dgst) - if err != nil { - return err - } - defer blob.Close() - - _, err = io.CopyN(w, blob, desc.Size) - return err -} - -func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { - writer, err := bs.Create(ctx) - if err != nil { - return distribution.Descriptor{}, err - } - dgstr := digest.Canonical.Digester() - n, err := io.Copy(writer, io.TeeReader(bytes.NewReader(p), dgstr.Hash())) - if err != nil { - return distribution.Descriptor{}, err - } - if n < int64(len(p)) { - return distribution.Descriptor{}, fmt.Errorf("short copy: wrote %d of %d", n, len(p)) - } - - desc := distribution.Descriptor{ - MediaType: mediaType, - Size: int64(len(p)), - Digest: dgstr.Digest(), - } - - return writer.Commit(ctx, desc) -} - -type optionFunc func(interface{}) error - -func (f optionFunc) Apply(v interface{}) error { - return f(v) -} - -// WithMountFrom returns a BlobCreateOption which designates that the blob should be -// mounted from the given canonical reference. -func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { - return optionFunc(func(v interface{}) error { - opts, ok := v.(*distribution.CreateOptions) - if !ok { - return fmt.Errorf("unexpected options type: %T", v) - } - - opts.Mount.ShouldMount = true - opts.Mount.From = ref - - return nil - }) -} - -func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { - var opts distribution.CreateOptions - - for _, option := range options { - err := option.Apply(&opts) - if err != nil { - return nil, err - } - } - - var values []url.Values - - if opts.Mount.ShouldMount { - values = append(values, url.Values{"from": {opts.Mount.From.Name()}, "mount": {opts.Mount.From.Digest().String()}}) - } - - u, err := bs.ub.BuildBlobUploadURL(bs.name, values...) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", u, nil) - if err != nil { - return nil, err - } - - resp, err := bs.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - switch resp.StatusCode { - case http.StatusCreated: - desc, err := bs.statter.Stat(ctx, opts.Mount.From.Digest()) - if err != nil { - return nil, err - } - return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc} - case http.StatusAccepted: - // TODO(dmcgowan): Check for invalid UUID - uuid := resp.Header.Get("Docker-Upload-UUID") - if uuid == "" { - parts := strings.Split(resp.Header.Get("Location"), "/") - uuid = parts[len(parts)-1] - } - if uuid == "" { - return nil, errors.New("cannot retrieve docker upload UUID") - } - - location, err := sanitizeLocation(resp.Header.Get("Location"), u) - if err != nil { - return nil, err - } - - return &httpBlobUpload{ - statter: bs.statter, - client: bs.client, - uuid: uuid, - startedAt: time.Now(), - location: location, - }, nil - default: - return nil, HandleErrorResponse(resp) - } -} - -func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { - location, err := bs.ub.BuildBlobUploadChunkURL(bs.name, id) - if err != nil { - return nil, err - } - - return &httpBlobUpload{ - statter: bs.statter, - client: bs.client, - uuid: id, - startedAt: time.Now(), - location: location, - }, nil -} - -func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error { - return bs.statter.Clear(ctx, dgst) -} - -type blobStatter struct { - name reference.Named - ub *v2.URLBuilder - client *http.Client -} - -func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { - ref, err := reference.WithDigest(bs.name, dgst) - if err != nil { - return distribution.Descriptor{}, err - } - u, err := bs.ub.BuildBlobURL(ref) - if err != nil { - return distribution.Descriptor{}, err - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", u, nil) - if err != nil { - return distribution.Descriptor{}, err - } - resp, err := bs.client.Do(req) - if err != nil { - return distribution.Descriptor{}, err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - lengthHeader := resp.Header.Get("Content-Length") - if lengthHeader == "" { - return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u) - } - - length, err := strconv.ParseInt(lengthHeader, 10, 64) - if err != nil { - return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err) - } - - return distribution.Descriptor{ - MediaType: resp.Header.Get("Content-Type"), - Size: length, - Digest: dgst, - }, nil - } else if resp.StatusCode == http.StatusNotFound { - return distribution.Descriptor{}, distribution.ErrBlobUnknown - } - return distribution.Descriptor{}, HandleErrorResponse(resp) -} - -func buildCatalogValues(maxEntries int, last string) url.Values { - values := url.Values{} - - if maxEntries > 0 { - values.Add("n", strconv.Itoa(maxEntries)) - } - - if last != "" { - values.Add("last", last) - } - - return values -} - -func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error { - ref, err := reference.WithDigest(bs.name, dgst) - if err != nil { - return err - } - blobURL, err := bs.ub.BuildBlobURL(ref) - if err != nil { - return err - } - - req, err := http.NewRequestWithContext(ctx, "DELETE", blobURL, nil) - if err != nil { - return err - } - - resp, err := bs.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - return nil - } - return HandleErrorResponse(resp) -} - -func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { - return nil -} diff --git a/pkg/registry/save/lib/distributionpkg/client/transport/http_reader.go b/pkg/registry/save/lib/distributionpkg/client/transport/http_reader.go deleted file mode 100644 index cd9b187f448..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/transport/http_reader.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transport - -import ( - "context" - "errors" - "fmt" - "io" - "net/http" - "regexp" - "strconv" -) - -var ( - contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`) - - // ErrWrongCodeForByteRange is returned if the client sends a request - // with a Range header but the server returns a 2xx or 3xx code other - // than 206 Partial Content. - ErrWrongCodeForByteRange = errors.New("expected HTTP 206 from byte range request") -) - -// ReadSeekCloser combines io.ReadSeeker with io.Closer. -type ReadSeekCloser interface { - io.ReadSeeker - io.Closer -} - -// NewHTTPReadSeeker handles reading from an HTTP endpoint using a GET -// request. When seeking and starting a read from a non-zero offset -// the a "Range" header will be added which sets the offset. -// TODO(dmcgowan): Move this into a separate utility package -func NewHTTPReadSeeker(ctx context.Context, client *http.Client, url string, errorHandler func(*http.Response) error) ReadSeekCloser { - return &httpReadSeeker{ - ctx: ctx, - client: client, - url: url, - errorHandler: errorHandler, - } -} - -type httpReadSeeker struct { - ctx context.Context - client *http.Client - url string - - // errorHandler creates an error from an unsuccessful HTTP response. - // This allows the error to be created with the HTTP response body - // without leaking the body through a returned error. - errorHandler func(*http.Response) error - - size int64 - - // rc is the remote read closer. - rc io.ReadCloser - // readerOffset tracks the offset as of the last read. - readerOffset int64 - // seekOffset allows Seek to override the offset. Seek changes - // seekOffset instead of changing readOffset directly so that - // connection resets can be delayed and possibly avoided if the - // seek is undone (i.e. seeking to the end and then back to the - // beginning). - seekOffset int64 - err error -} - -func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) { - if hrs.err != nil { - return 0, hrs.err - } - - // If we sought to a different position, we need to reset the - // connection. This logic is here instead of Seek so that if - // a seek is undone before the next read, the connection doesn't - // need to be closed and reopened. A common example of this is - // seeking to the end to determine the length, and then seeking - // back to the original position. - if hrs.readerOffset != hrs.seekOffset { - hrs.reset() - } - - hrs.readerOffset = hrs.seekOffset - - rd, err := hrs.reader() - if err != nil { - return 0, err - } - - n, err = rd.Read(p) - hrs.seekOffset += int64(n) - hrs.readerOffset += int64(n) - - return n, err -} - -func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) { - if hrs.err != nil { - return 0, hrs.err - } - - lastReaderOffset := hrs.readerOffset - - if whence == io.SeekStart && hrs.rc == nil { - // If no request has been made yet, and we are seeking to an - // absolute position, set the read offset as well to avoid an - // unnecessary request. - hrs.readerOffset = offset - } - - _, err := hrs.reader() - if err != nil { - hrs.readerOffset = lastReaderOffset - return 0, err - } - //nolint:all - newOffset := hrs.seekOffset - - switch whence { - case io.SeekCurrent: - newOffset += offset - case io.SeekEnd: - if hrs.size < 0 { - return 0, errors.New("content length not known") - } - newOffset = hrs.size + offset - case io.SeekStart: - newOffset = offset - } - - if newOffset < 0 { - err = errors.New("cannot seek to negative position") - } else { - hrs.seekOffset = newOffset - } - - return hrs.seekOffset, err -} - -func (hrs *httpReadSeeker) Close() error { - if hrs.err != nil { - return hrs.err - } - - // close and release reader chain - if hrs.rc != nil { - hrs.rc.Close() - } - - hrs.rc = nil - - hrs.err = errors.New("httpLayer: closed") - - return nil -} - -func (hrs *httpReadSeeker) reset() { - if hrs.err != nil { - return - } - if hrs.rc != nil { - hrs.rc.Close() - hrs.rc = nil - } -} - -func (hrs *httpReadSeeker) reader() (io.Reader, error) { - if hrs.err != nil { - return nil, hrs.err - } - - if hrs.rc != nil { - return hrs.rc, nil - } - - req, err := http.NewRequestWithContext(hrs.ctx, "GET", hrs.url, nil) - if err != nil { - return nil, err - } - - if hrs.readerOffset > 0 { - // If we are at different offset, issue a range request from there. - req.Header.Add("Range", fmt.Sprintf("bytes=%d-", hrs.readerOffset)) - // TODO: get context in here - // context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range")) - } - - req.Header.Add("Accept-Encoding", "identity") - resp, err := hrs.client.Do(req) - if err != nil { - return nil, err - } - - // Normally would use client.SuccessStatus, but that would be a cyclic - // import - if resp.StatusCode >= 200 && resp.StatusCode <= 399 { - if hrs.readerOffset > 0 { - if resp.StatusCode != http.StatusPartialContent { - return nil, ErrWrongCodeForByteRange - } - - contentRange := resp.Header.Get("Content-Range") - if contentRange == "" { - return nil, errors.New("no Content-Range header found in HTTP 206 response") - } - - submatches := contentRangeRegexp.FindStringSubmatch(contentRange) - if len(submatches) < 4 { - return nil, fmt.Errorf("could not parse Content-Range header: %s", contentRange) - } - - startByte, err := strconv.ParseUint(submatches[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("could not parse start of range in Content-Range header: %s", contentRange) - } - - if startByte != uint64(hrs.readerOffset) { - return nil, fmt.Errorf("received Content-Range starting at offset %d instead of requested %d", startByte, hrs.readerOffset) - } - // nosemgrep - endByte, err := strconv.ParseUint(submatches[2], 10, 64) - if err != nil { - return nil, fmt.Errorf("could not parse end of range in Content-Range header: %s", contentRange) - } - - if submatches[3] == "*" { - hrs.size = -1 - } else { - // nosemgrep - size, err := strconv.ParseUint(submatches[3], 10, 64) - if err != nil { - return nil, fmt.Errorf("could not parse total size in Content-Range header: %s", contentRange) - } - - if endByte+1 != size { - return nil, fmt.Errorf("range in Content-Range stops before the end of the content: %s", contentRange) - } - - hrs.size = int64(size) - } - } else if resp.StatusCode == http.StatusOK { - hrs.size = resp.ContentLength - } else { - hrs.size = -1 - } - hrs.rc = resp.Body - } else { - defer resp.Body.Close() - if hrs.errorHandler != nil { - return nil, hrs.errorHandler(resp) - } - return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status) - } - - return hrs.rc, nil -} diff --git a/pkg/registry/save/lib/distributionpkg/client/transport/transport.go b/pkg/registry/save/lib/distributionpkg/client/transport/transport.go deleted file mode 100644 index 533f461c3ae..00000000000 --- a/pkg/registry/save/lib/distributionpkg/client/transport/transport.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright © 2022 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transport - -import ( - "io" - "net/http" - "sync" -) - -func identityTransportWrapper(rt http.RoundTripper) http.RoundTripper { - return rt -} - -// DefaultTransportWrapper allows a user to wrap every generated transport -var DefaultTransportWrapper = identityTransportWrapper - -// RequestModifier represents an object which will do an inplace -// modification of an HTTP request. -type RequestModifier interface { - ModifyRequest(*http.Request) error -} - -type headerModifier http.Header - -// NewHeaderRequestModifier returns a new RequestModifier which will -// add the given headers to a request. -func NewHeaderRequestModifier(header http.Header) RequestModifier { - return headerModifier(header) -} - -func (h headerModifier) ModifyRequest(req *http.Request) error { - for k, s := range http.Header(h) { - req.Header[k] = append(req.Header[k], s...) - } - - return nil -} - -// NewTransport creates a new transport which will apply modifiers to -// the request on a RoundTrip call. -func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper { - return DefaultTransportWrapper( - &transport{ - Modifiers: modifiers, - Base: base, - }) -} - -// transport is an http.RoundTripper that makes HTTP requests after -// copying and modifying the request -type transport struct { - Modifiers []RequestModifier - Base http.RoundTripper - - mu sync.Mutex // guards modReq - modReq map[*http.Request]*http.Request // original -> modified -} - -// RoundTrip authorizes and authenticates the request with an -// access token. If no token exists or token is expired, -// tries to refresh/fetch a new token. -func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { - req2 := cloneRequest(req) - for _, modifier := range t.Modifiers { - if err := modifier.ModifyRequest(req2); err != nil { - return nil, err - } - } - - t.setModReq(req, req2) - res, err := t.base().RoundTrip(req2) - if err != nil { - t.setModReq(req, nil) - return nil, err - } - res.Body = &onEOFReader{ - rc: res.Body, - fn: func() { t.setModReq(req, nil) }, - } - return res, nil -} - -// CancelRequest cancels an in-flight request by closing its connection. -func (t *transport) CancelRequest(req *http.Request) { - type canceler interface { - CancelRequest(*http.Request) - } - if cr, ok := t.base().(canceler); ok { - t.mu.Lock() - modReq := t.modReq[req] - delete(t.modReq, req) - t.mu.Unlock() - cr.CancelRequest(modReq) - } -} - -func (t *transport) base() http.RoundTripper { - if t.Base != nil { - return t.Base - } - return http.DefaultTransport -} - -func (t *transport) setModReq(orig, mod *http.Request) { - t.mu.Lock() - defer t.mu.Unlock() - if t.modReq == nil { - t.modReq = make(map[*http.Request]*http.Request) - } - if mod == nil { - delete(t.modReq, orig) - } else { - t.modReq[orig] = mod - } -} - -// cloneRequest returns a clone of the provided *http.Request. -// The clone is a shallow copy of the struct and its Header map. -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - - return r2 -} - -type onEOFReader struct { - rc io.ReadCloser - fn func() -} - -func (r *onEOFReader) Read(p []byte) (n int, err error) { - n, err = r.rc.Read(p) - if err == io.EOF { - r.runFunc() - } - return -} - -func (r *onEOFReader) Close() error { - err := r.rc.Close() - r.runFunc() - return err -} - -func (r *onEOFReader) runFunc() { - if fn := r.fn; fn != nil { - fn() - r.fn = nil - } -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxyauth.go b/pkg/registry/save/lib/distributionpkg/proxy/proxyauth.go deleted file mode 100644 index 701fd17ae24..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxyauth.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "crypto/tls" - "net/http" - "net/url" - "strings" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth/challenge" - - "github.com/labring/sealos/pkg/utils/logger" -) - -// comment this const because not used -//const challengeHeader = "Docker-Distribution-Api-Version" - -const certUnknown = "x509: certificate signed by unknown authority" - -type userpass struct { - username string - password string -} - -type credentials struct { - creds map[string]userpass -} - -func (c credentials) Basic(u *url.URL) (string, string) { - up := c.creds[u.String()] - - return up.username, up.password -} - -func (c credentials) RefreshToken(u *url.URL, service string) string { - return "" -} - -func (c credentials) SetRefreshToken(u *url.URL, service, token string) { -} - -// configureAuth stores credentials for challenge responses -func configureAuth(username, password, remoteURL string, basicAuth bool) (auth.CredentialStore, error) { - creds := map[string]userpass{} - - authURLs, err := getAuthURLs(remoteURL) - if err != nil { - return nil, err - } - - for _, url := range authURLs { - // context.GetLogger(context.Background()).Infof("Discovered token authentication URL: %s", url) - creds[url] = userpass{ - username: username, - password: password, - } - } - if len(authURLs) == 0 && basicAuth { - creds[remoteURL] = userpass{ - username: username, - password: password, - } - } - return credentials{creds: creds}, nil -} - -func getAuthURLs(remoteURL string) ([]string, error) { - authURLs := []string{} - - resp, err := http.Get(remoteURL + "/v2/") - if err != nil { - if strings.Contains(err.Error(), certUnknown) { - logger.Warn("create connect with unauthenticated registry url: %s", remoteURL) - resp, err = newClientSkipVerify().Get(remoteURL + "/v2/") - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - defer resp.Body.Close() - - for _, c := range challenge.ResponseChallenges(resp) { - if strings.EqualFold(c.Scheme, "bearer") { - authURLs = append(authURLs, c.Parameters["realm"]) - } - } - - return authURLs, nil -} - -// #nosec -func ping(manager challenge.Manager, endpoint string) error { - resp, err := http.Get(endpoint) - if err != nil { - if strings.Contains(err.Error(), certUnknown) { - resp, err = newClientSkipVerify().Get(endpoint) - if err != nil { - return err - } - } else { - return err - } - } - defer resp.Body.Close() - - return manager.AddResponse(resp) -} - -// #nosec -func newClientSkipVerify() *http.Client { - return &http.Client{ - Transport: &http.Transport{ - // nosemgrep - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxyblobstore.go b/pkg/registry/save/lib/distributionpkg/proxy/proxyblobstore.go deleted file mode 100644 index 91512d67169..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxyblobstore.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "context" - "io" - "net/http" - "strconv" - "sync" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/proxy/scheduler" - - "github.com/distribution/distribution/v3" - dcontext "github.com/distribution/distribution/v3/context" - "github.com/distribution/distribution/v3/reference" - "github.com/opencontainers/go-digest" -) - -type proxyBlobStore struct { - localStore distribution.BlobStore - remoteStore distribution.BlobService - scheduler *scheduler.TTLExpirationScheduler - repositoryName reference.Named - authChallenger authChallenger -} - -var _ distribution.BlobStore = &proxyBlobStore{} - -// inflight tracks currently downloading blobs -var inflight = make(map[digest.Digest]struct{}) - -// mu protects inflight -var mu sync.Mutex - -func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) { - w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) - w.Header().Set("Content-Type", mediaType) - w.Header().Set("Docker-Content-Digest", digest.String()) - w.Header().Set("Etag", digest.String()) -} - -func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer) (distribution.Descriptor, error) { - desc, err := pbs.remoteStore.Stat(ctx, dgst) - if err != nil { - return distribution.Descriptor{}, err - } - - if w, ok := writer.(http.ResponseWriter); ok { - setResponseHeaders(w, desc.Size, desc.MediaType, dgst) - } - - remoteReader, err := pbs.remoteStore.Open(ctx, dgst) - if err != nil { - return distribution.Descriptor{}, err - } - - defer remoteReader.Close() - - _, err = io.CopyN(writer, remoteReader, desc.Size) - if err != nil { - return distribution.Descriptor{}, err - } - - proxyMetrics.BlobPush(uint64(desc.Size)) - - return desc, nil -} - -func (pbs *proxyBlobStore) serveLocal(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) (bool, error) { - localDesc, err := pbs.localStore.Stat(ctx, dgst) - if err != nil { - // Stat can report a zero sized file here if it's checked between creation - // and population. Return nil error, and continue - return false, err - } - - proxyMetrics.BlobPush(uint64(localDesc.Size)) - return true, pbs.localStore.ServeBlob(ctx, w, r, dgst) -} - -func (pbs *proxyBlobStore) storeLocal(ctx context.Context, dgst digest.Digest) error { - defer func() { - mu.Lock() - delete(inflight, dgst) - mu.Unlock() - }() - - var desc distribution.Descriptor - var err error - var bw distribution.BlobWriter - - bw, err = pbs.localStore.Create(ctx) - if err != nil { - return err - } - - desc, err = pbs.copyContent(ctx, dgst, bw) - if err != nil { - return err - } - - _, err = bw.Commit(ctx, desc) - if err != nil { - return err - } - - return nil -} - -func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { - served, err := pbs.serveLocal(ctx, w, r, dgst) - if err != nil { - dcontext.GetLogger(ctx).Errorf("Error serving blob from local storage: %s", err.Error()) - return err - } - - if served { - return nil - } - - if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { - return err - } - - mu.Lock() - if _, ok := inflight[dgst]; ok { - mu.Unlock() - _, err := pbs.copyContent(ctx, dgst, w) - return err - } - inflight[dgst] = struct{}{} - mu.Unlock() - - go func(dgst digest.Digest) { - if err := pbs.storeLocal(ctx, dgst); err != nil { - dcontext.GetLogger(ctx).Errorf("Error committing to storage: %s", err.Error()) - } - - blobRef, err := reference.WithDigest(pbs.repositoryName, dgst) - if err != nil { - dcontext.GetLogger(ctx).Errorf("Error creating reference: %s", err) - return - } - - _ = pbs.scheduler.AddBlob(blobRef, repositoryTTL) - }(dgst) - - _, err = pbs.copyContent(ctx, dgst, w) - if err != nil { - return err - } - return nil -} - -func (pbs *proxyBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { - desc, err := pbs.localStore.Stat(ctx, dgst) - if err == nil { - return desc, nil - } - - return distribution.Descriptor{}, err -} - -func (pbs *proxyBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { - blob, err := pbs.localStore.Get(ctx, dgst) - if err == nil { - return blob, nil - } - if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { - return []byte{}, err - } - - blob, err = pbs.remoteStore.Get(ctx, dgst) - if err != nil { - return []byte{}, err - } - - _, err = pbs.localStore.Put(ctx, "", blob) - if err != nil { - return []byte{}, err - } - return blob, nil -} - -func (pbs *proxyBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { - if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { - return nil, err - } - - reader, err := pbs.remoteStore.Open(ctx, dgst) - if err != nil { - return nil, err - } - return reader, nil -} - -func (pbs *proxyBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { - desc, err := pbs.localStore.Put(ctx, "", p) - if err != nil { - return distribution.Descriptor{}, err - } - return desc, nil -} - -func (pbs *proxyBlobStore) Local(_ context.Context) (distribution.BlobStore, error) { - return pbs.localStore, nil -} - -// Unsupported functions -func (pbs *proxyBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { - return nil, distribution.ErrUnsupported -} - -func (pbs *proxyBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { - return nil, distribution.ErrUnsupported -} - -func (pbs *proxyBlobStore) Mount(ctx context.Context, sourceRepo reference.Named, dgst digest.Digest) (distribution.Descriptor, error) { - return distribution.Descriptor{}, distribution.ErrUnsupported -} - -func (pbs *proxyBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { - return distribution.ErrUnsupported -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxymanifeststore.go b/pkg/registry/save/lib/distributionpkg/proxy/proxymanifeststore.go deleted file mode 100644 index bfed349060b..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxymanifeststore.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "context" - "time" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/proxy/scheduler" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/reference" - "github.com/opencontainers/go-digest" -) - -// todo(richardscothern): from cache control header or config -const repositoryTTL = 24 * 7 * time.Hour - -type proxyManifestStore struct { - ctx context.Context - localManifests distribution.ManifestService - remoteManifests distribution.ManifestService - repositoryName reference.Named - scheduler *scheduler.TTLExpirationScheduler - authChallenger authChallenger -} - -var _ distribution.ManifestService = &proxyManifestStore{} - -func (pms proxyManifestStore) Exists(ctx context.Context, dgst digest.Digest) (bool, error) { - exists, err := pms.localManifests.Exists(ctx, dgst) - if err != nil { - return false, err - } - if exists { - return true, nil - } - if err := pms.authChallenger.tryEstablishChallenges(ctx); err != nil { - return false, err - } - return pms.remoteManifests.Exists(ctx, dgst) -} - -func (pms proxyManifestStore) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { - // At this point `dgst` was either specified explicitly, or returned by the - // tagstore with the most recent association. - var fromRemote bool - manifest, err := pms.localManifests.Get(ctx, dgst, options...) - if err != nil { - if err := pms.authChallenger.tryEstablishChallenges(ctx); err != nil { - return nil, err - } - - manifest, err = pms.remoteManifests.Get(ctx, dgst, options...) - if err != nil { - return nil, err - } - fromRemote = true - } - - _, payload, err := manifest.Payload() - if err != nil { - return nil, err - } - - proxyMetrics.ManifestPush(uint64(len(payload))) - if fromRemote { - proxyMetrics.ManifestPull(uint64(len(payload))) - - _, err = pms.localManifests.Put(ctx, manifest) - if err != nil { - return nil, err - } - - // Schedule the manifest blob for removal - // repoBlob, err := reference.WithDigest(pms.repositoryName, dgst) - // if err != nil { - // dcontext.GetLogger(ctx).Errorf("Error creating reference: %s", err) - // return nil, err - // } - - // pms.scheduler.AddManifest(repoBlob, repositoryTTL) - // Ensure the manifest blob is cleaned up - //pms.scheduler.AddBlob(blobRef, repositoryTTL) - } - - return manifest, err -} - -func (pms proxyManifestStore) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) { - var d digest.Digest - return d, distribution.ErrUnsupported -} - -func (pms proxyManifestStore) Delete(ctx context.Context, dgst digest.Digest) error { - return distribution.ErrUnsupported -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxymetrics.go b/pkg/registry/save/lib/distributionpkg/proxy/proxymetrics.go deleted file mode 100644 index 88fb1b0c419..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxymetrics.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "expvar" - "sync/atomic" -) - -// Metrics is used to hold metric counters -// related to the proxy -type Metrics struct { - Requests uint64 - Hits uint64 - Misses uint64 - BytesPulled uint64 - BytesPushed uint64 -} - -type proxyMetricsCollector struct { - blobMetrics Metrics - manifestMetrics Metrics -} - -// BlobPull tracks metrics about blobs pulled into the cache -func (pmc *proxyMetricsCollector) BlobPull(bytesPulled uint64) { - atomic.AddUint64(&pmc.blobMetrics.Misses, 1) - atomic.AddUint64(&pmc.blobMetrics.BytesPulled, bytesPulled) -} - -// BlobPush tracks metrics about blobs pushed to clients -func (pmc *proxyMetricsCollector) BlobPush(bytesPushed uint64) { - atomic.AddUint64(&pmc.blobMetrics.Requests, 1) - atomic.AddUint64(&pmc.blobMetrics.Hits, 1) - atomic.AddUint64(&pmc.blobMetrics.BytesPushed, bytesPushed) -} - -// ManifestPull tracks metrics related to Manifests pulled into the cache -func (pmc *proxyMetricsCollector) ManifestPull(bytesPulled uint64) { - atomic.AddUint64(&pmc.manifestMetrics.Misses, 1) - atomic.AddUint64(&pmc.manifestMetrics.BytesPulled, bytesPulled) -} - -// ManifestPush tracks metrics about manifests pushed to clients -func (pmc *proxyMetricsCollector) ManifestPush(bytesPushed uint64) { - atomic.AddUint64(&pmc.manifestMetrics.Requests, 1) - atomic.AddUint64(&pmc.manifestMetrics.Hits, 1) - atomic.AddUint64(&pmc.manifestMetrics.BytesPushed, bytesPushed) -} - -// proxyMetrics tracks metrics about the proxy cache. This is -// kept globally and made available via expvar. -var proxyMetrics = &proxyMetricsCollector{} - -func init() { - registry := expvar.Get("registry") - if registry == nil { - registry = expvar.NewMap("registry") - } - - pm := registry.(*expvar.Map).Get("proxy") - if pm == nil { - pm = &expvar.Map{} - pm.(*expvar.Map).Init() - registry.(*expvar.Map).Set("proxy", pm) - } - - pm.(*expvar.Map).Set("blobs", expvar.Func(func() interface{} { - return proxyMetrics.blobMetrics - })) - - pm.(*expvar.Map).Set("manifests", expvar.Func(func() interface{} { - return proxyMetrics.manifestMetrics - })) -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxyregistry.go b/pkg/registry/save/lib/distributionpkg/proxy/proxyregistry.go deleted file mode 100644 index f376958536a..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxyregistry.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "context" - "crypto/tls" - "net/http" - "net/url" - "strings" - "sync" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/auth/challenge" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/client/transport" - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/proxy/scheduler" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/configuration" - dcontext "github.com/distribution/distribution/v3/context" - "github.com/distribution/distribution/v3/reference" - "github.com/distribution/distribution/v3/registry/storage" -) - -// proxyingRegistry fetches content from a remote registry and caches it locally -type proxyingRegistry struct { - embedded distribution.Namespace // provides local registry functionality - scheduler *scheduler.TTLExpirationScheduler - remoteURL url.URL - basicAuth bool - authChallenger authChallenger -} - -// NewRegistryPullThroughCache creates a registry acting as a pull through cache -func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, config configuration.Proxy) (distribution.Namespace, error) { - remoteURL, err := url.Parse(config.RemoteURL) - if err != nil { - return nil, err - } - var basicAuth bool - if remoteURL.Scheme == "http" { - basicAuth = true - } - cs, err := configureAuth(config.Username, config.Password, config.RemoteURL, basicAuth) - if err != nil { - return nil, err - } - - return &proxyingRegistry{ - embedded: registry, - scheduler: nil, - remoteURL: *remoteURL, - basicAuth: basicAuth, - authChallenger: &remoteAuthChallenger{ - remoteURL: *remoteURL, - cm: challenge.NewSimpleManager(), - cs: cs, - }, - }, nil -} - -func (pr *proxyingRegistry) Scope() distribution.Scope { - return distribution.GlobalScope -} - -func (pr *proxyingRegistry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) { - return pr.embedded.Repositories(ctx, repos, last) -} - -// #nosec -func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) { - c := pr.authChallenger - - tkopts := auth.TokenHandlerOptions{ - Transport: http.DefaultTransport, - Credentials: c.credentialStore(), - Scopes: []auth.Scope{ - auth.RepositoryScope{ - Repository: name.Name(), - Actions: []string{"pull"}, - }, - }, - Logger: dcontext.GetLogger(ctx), - } - authorizer := auth.NewAuthorizer(c.challengeManager(), - auth.NewTokenHandlerWithOptions(tkopts)) - if pr.basicAuth { - authorizer = auth.NewAuthorizer(c.challengeManager(), - auth.NewBasicHandler(tkopts.Credentials, &pr.remoteURL)) - } - - tr := transport.NewTransport(http.DefaultTransport, authorizer) - - tryClient := &http.Client{Transport: tr} - _, err := tryClient.Get(pr.remoteURL.String()) - if err != nil && strings.Contains(err.Error(), certUnknown) { - // nosemgrep - tr = transport.NewTransport(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, - authorizer) - } - - localRepo, err := pr.embedded.Repository(ctx, name) - if err != nil { - return nil, err - } - localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification()) - if err != nil { - return nil, err - } - - remoteRepo, err := client.NewRepository(name, pr.remoteURL.String(), tr) - if err != nil { - return nil, err - } - - remoteManifests, err := remoteRepo.Manifests(ctx) - if err != nil { - return nil, err - } - - return &proxiedRepository{ - blobStore: &proxyBlobStore{ - localStore: localRepo.Blobs(ctx), - remoteStore: remoteRepo.Blobs(ctx), - scheduler: pr.scheduler, - repositoryName: name, - authChallenger: pr.authChallenger, - }, - manifests: &proxyManifestStore{ - repositoryName: name, - localManifests: localManifests, // Options? - remoteManifests: remoteManifests, - ctx: ctx, - scheduler: pr.scheduler, - authChallenger: pr.authChallenger, - }, - name: name, - tags: &proxyTagService{ - localTags: localRepo.Tags(ctx), - remoteTags: remoteRepo.Tags(ctx), - authChallenger: pr.authChallenger, - }, - }, nil -} - -func (pr *proxyingRegistry) Blobs() distribution.BlobEnumerator { - return pr.embedded.Blobs() -} - -func (pr *proxyingRegistry) BlobStatter() distribution.BlobStatter { - return pr.embedded.BlobStatter() -} - -// authChallenger encapsulates a request to the upstream to establish credential challenges -type authChallenger interface { - tryEstablishChallenges(context.Context) error - challengeManager() challenge.Manager - credentialStore() auth.CredentialStore -} - -type remoteAuthChallenger struct { - remoteURL url.URL - sync.Mutex - cm challenge.Manager - cs auth.CredentialStore -} - -func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore { - return r.cs -} - -func (r *remoteAuthChallenger) challengeManager() challenge.Manager { - return r.cm -} - -// tryEstablishChallenges will attempt to get a challenge type for the upstream if none currently exist -func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error { - r.Lock() - defer r.Unlock() - - remoteURL := r.remoteURL - remoteURL.Path = "/v2/" - challenges, err := r.cm.GetChallenges(remoteURL) - if err != nil { - return err - } - - if len(challenges) > 0 { - return nil - } - - // establish challenge type with upstream - if err := ping(r.cm, remoteURL.String()); err != nil { - return err - } - - // dcontext.GetLogger(ctx).Infof("Challenge established with upstream : %s %s", remoteURL, r.cm) - return nil -} - -// proxiedRepository uses proxying blob and manifest services to serve content -// locally, or pulling it through from a remote and caching it locally if it doesn't -// already exist -type proxiedRepository struct { - blobStore distribution.BlobStore - manifests distribution.ManifestService - name reference.Named - tags distribution.TagService -} - -func (pr *proxiedRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { - return pr.manifests, nil -} - -func (pr *proxiedRepository) Blobs(ctx context.Context) distribution.BlobStore { - return pr.blobStore -} - -func (pr *proxiedRepository) Named() reference.Named { - return pr.name -} - -func (pr *proxiedRepository) Tags(ctx context.Context) distribution.TagService { - return pr.tags -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/proxytagservice.go b/pkg/registry/save/lib/distributionpkg/proxy/proxytagservice.go deleted file mode 100644 index a877ec0c54c..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/proxytagservice.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proxy - -import ( - "context" - - "github.com/distribution/distribution/v3" -) - -// proxyTagService supports local and remote lookup of tags. -type proxyTagService struct { - localTags distribution.TagService - remoteTags distribution.TagService - authChallenger authChallenger -} - -var _ distribution.TagService = proxyTagService{} - -// Get attempts to get the most recent digest for the tag by checking the remote -// tag service first and then caching it locally. If the remote is unavailable -// the local association is returned -func (pt proxyTagService) Get(ctx context.Context, tag string) (distribution.Descriptor, error) { - err := pt.authChallenger.tryEstablishChallenges(ctx) - if err == nil { - desc, err := pt.remoteTags.Get(ctx, tag) - if err == nil { - err := pt.localTags.Tag(ctx, tag, desc) - if err != nil { - return distribution.Descriptor{}, err - } - return desc, nil - } - } - - desc, err := pt.localTags.Get(ctx, tag) - if err != nil { - return distribution.Descriptor{}, err - } - return desc, nil -} - -func (pt proxyTagService) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error { - return distribution.ErrUnsupported -} - -func (pt proxyTagService) Untag(ctx context.Context, tag string) error { - err := pt.localTags.Untag(ctx, tag) - if err != nil { - return err - } - return nil -} - -func (pt proxyTagService) All(ctx context.Context) ([]string, error) { - err := pt.authChallenger.tryEstablishChallenges(ctx) - if err == nil { - tags, err := pt.remoteTags.All(ctx) - if err == nil { - return tags, nil - } - } - return pt.localTags.All(ctx) -} - -func (pt proxyTagService) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) { - return []string{}, distribution.ErrUnsupported -} diff --git a/pkg/registry/save/lib/distributionpkg/proxy/scheduler/scheduler.go b/pkg/registry/save/lib/distributionpkg/proxy/scheduler/scheduler.go deleted file mode 100644 index e9b31798e97..00000000000 --- a/pkg/registry/save/lib/distributionpkg/proxy/scheduler/scheduler.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright © 2021 https://github.com/distribution/distribution -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scheduler - -import ( - "context" - "encoding/json" - "fmt" - "sync" - "time" - - dcontext "github.com/distribution/distribution/v3/context" - "github.com/distribution/distribution/v3/reference" - "github.com/distribution/distribution/v3/registry/storage/driver" -) - -// onTTLExpiryFunc is called when a repository's TTL expires -type expiryFunc func(reference.Reference) error - -const ( - entryTypeBlob = iota - entryTypeManifest - indexSaveFrequency = 5 * time.Second -) - -// schedulerEntry represents an entry in the scheduler -// fields are exported for serialization -type schedulerEntry struct { - Key string `json:"Key"` - Expiry time.Time `json:"ExpiryData"` - EntryType int `json:"EntryType"` - - timer *time.Timer -} - -// New returns a new instance of the scheduler -func New(ctx context.Context, driver driver.StorageDriver, path string) *TTLExpirationScheduler { - return &TTLExpirationScheduler{ - entries: make(map[string]*schedulerEntry), - driver: driver, - pathToStateFile: path, - ctx: ctx, - stopped: true, - doneChan: make(chan struct{}), - saveTimer: time.NewTicker(indexSaveFrequency), - } -} - -// TTLExpirationScheduler is a scheduler used to perform actions -// when TTLs expire -type TTLExpirationScheduler struct { - sync.Mutex - - entries map[string]*schedulerEntry - - driver driver.StorageDriver - ctx context.Context - pathToStateFile string - - stopped bool - - onBlobExpire expiryFunc - onManifestExpire expiryFunc - - indexDirty bool - saveTimer *time.Ticker - doneChan chan struct{} -} - -// OnBlobExpire is called when a scheduled blob's TTL expires -func (ttles *TTLExpirationScheduler) OnBlobExpire(f expiryFunc) { - ttles.Lock() - defer ttles.Unlock() - - ttles.onBlobExpire = f -} - -// OnManifestExpire is called when a scheduled manifest's TTL expires -func (ttles *TTLExpirationScheduler) OnManifestExpire(f expiryFunc) { - ttles.Lock() - defer ttles.Unlock() - - ttles.onManifestExpire = f -} - -// AddBlob schedules a blob cleanup after ttl expires -func (ttles *TTLExpirationScheduler) AddBlob(blobRef reference.Canonical, ttl time.Duration) error { - ttles.Lock() - defer ttles.Unlock() - - if ttles.stopped { - return fmt.Errorf("scheduler not started") - } - - ttles.add(blobRef, ttl, entryTypeBlob) - return nil -} - -// AddManifest schedules a manifest cleanup after ttl expires -func (ttles *TTLExpirationScheduler) AddManifest(manifestRef reference.Canonical, ttl time.Duration) error { - ttles.Lock() - defer ttles.Unlock() - - if ttles.stopped { - return fmt.Errorf("scheduler not started") - } - - ttles.add(manifestRef, ttl, entryTypeManifest) - return nil -} - -// Start starts the scheduler -func (ttles *TTLExpirationScheduler) Start() error { - ttles.Lock() - defer ttles.Unlock() - - if err := ttles.readState(); err != nil { - return err - } - - if !ttles.stopped { - return fmt.Errorf("scheduler already started") - } - - // dcontext.GetLogger(ttles.ctx).Infof("Starting cached object TTL expiration scheduler...") - ttles.stopped = false - - // Start timer for each deserialized entry - for _, entry := range ttles.entries { - entry.timer = ttles.startTimer(entry, time.Until(entry.Expiry)) - } - - // Start a ticker to periodically save the entries index - - go func() { - for { - select { - case <-ttles.saveTimer.C: - ttles.Lock() - if !ttles.indexDirty { - ttles.Unlock() - continue - } - - err := ttles.writeState() - if err != nil { - dcontext.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) - } else { - ttles.indexDirty = false - } - ttles.Unlock() - - case <-ttles.doneChan: - return - } - } - }() - - return nil -} - -func (ttles *TTLExpirationScheduler) add(r reference.Reference, ttl time.Duration, eType int) { - entry := &schedulerEntry{ - Key: r.String(), - Expiry: time.Now().Add(ttl), - EntryType: eType, - } - // dcontext.GetLogger(ttles.ctx).Infof("Adding new scheduler entry for %s with ttl=%s", entry.Key, time.Until(entry.Expiry)) - if oldEntry, present := ttles.entries[entry.Key]; present && oldEntry.timer != nil { - oldEntry.timer.Stop() - } - ttles.entries[entry.Key] = entry - entry.timer = ttles.startTimer(entry, ttl) - ttles.indexDirty = true -} - -func (ttles *TTLExpirationScheduler) startTimer(entry *schedulerEntry, ttl time.Duration) *time.Timer { - return time.AfterFunc(ttl, func() { - ttles.Lock() - defer ttles.Unlock() - - var f expiryFunc - - switch entry.EntryType { - case entryTypeBlob: - f = ttles.onBlobExpire - case entryTypeManifest: - f = ttles.onManifestExpire - default: - f = func(reference.Reference) error { - return fmt.Errorf("scheduler entry type") - } - } - - ref, err := reference.Parse(entry.Key) - if err == nil { - if err := f(ref); err != nil { - dcontext.GetLogger(ttles.ctx).Errorf("Scheduler error returned from OnExpire(%s): %s", entry.Key, err) - } - } else { - dcontext.GetLogger(ttles.ctx).Errorf("Error unpacking reference: %s", err) - } - - delete(ttles.entries, entry.Key) - ttles.indexDirty = true - }) -} - -// Stop stops the scheduler. -func (ttles *TTLExpirationScheduler) Stop() { - ttles.Lock() - defer ttles.Unlock() - - if err := ttles.writeState(); err != nil { - dcontext.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) - } - - for _, entry := range ttles.entries { - entry.timer.Stop() - } - - close(ttles.doneChan) - ttles.saveTimer.Stop() - ttles.stopped = true -} - -func (ttles *TTLExpirationScheduler) writeState() error { - jsonBytes, err := json.Marshal(ttles.entries) - if err != nil { - return err - } - - err = ttles.driver.PutContent(ttles.ctx, ttles.pathToStateFile, jsonBytes) - if err != nil { - return err - } - - return nil -} - -func (ttles *TTLExpirationScheduler) readState() error { - if _, err := ttles.driver.Stat(ttles.ctx, ttles.pathToStateFile); err != nil { - switch err := err.(type) { - case driver.PathNotFoundError: - return nil - default: - return err - } - } - - bytes, err := ttles.driver.GetContent(ttles.ctx, ttles.pathToStateFile) - if err != nil { - return err - } - - err = json.Unmarshal(bytes, &ttles.entries) - if err != nil { - return err - } - return nil -} diff --git a/pkg/registry/save/lib/imagemanifest/blobmanifest.go b/pkg/registry/save/lib/imagemanifest/blobmanifest.go deleted file mode 100644 index 62751154f6f..00000000000 --- a/pkg/registry/save/lib/imagemanifest/blobmanifest.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package imagemanifest - -import ( - "encoding/json" - "fmt" - - distribution "github.com/distribution/distribution/v3" - "github.com/opencontainers/go-digest" -) - -// this package unmarshal blobs from json into a BlobList struct -// then return a slice of blob digest -type BlobList struct { - Layers []distribution.Descriptor `json:"layers"` - Config distribution.Descriptor `json:"config"` - MediaType string `json:"mediaType"` - Schema int `json:"schemaVersion"` -} - -func GetBlobList(blobListJSON distribution.Manifest) ([]digest.Digest, error) { - _, list, err := blobListJSON.Payload() - if err != nil { - return nil, fmt.Errorf("failed to get blob list: %v", err) - } - var blobList BlobList - err = json.Unmarshal(list, &blobList) - if err != nil { - return nil, fmt.Errorf("json unmarshal error: %v", err) - } - var blobDigests []digest.Digest - blobDigests = append(blobDigests, blobList.Config.Digest) - for _, layer := range blobList.Layers { - blobDigests = append(blobDigests, layer.Digest) - } - return blobDigests, nil -} diff --git a/pkg/registry/save/lib/imagemanifest/imagemanifest.go b/pkg/registry/save/lib/imagemanifest/imagemanifest.go deleted file mode 100644 index 475813e58b4..00000000000 --- a/pkg/registry/save/lib/imagemanifest/imagemanifest.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package imagemanifest - -import ( - "encoding/json" - "fmt" - - "github.com/opencontainers/go-digest" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -// this package unmarshal manifests from json into a ManifestList struct -// then choose corresponding manifest by platform -type ManifestList struct { - List []Manifest `json:"manifests"` - MediaType string `json:"mediaType"` - Schema int `json:"schemaVersion"` -} - -type Manifest struct { - Digest string `json:"digest"` - MediaType string `json:"mediaType"` - Platform v1.Platform - Size int -} - -func GetImageManifestDigest(payload []byte, platform v1.Platform) (digest.Digest, error) { - var manifestList ManifestList - err := json.Unmarshal(payload, &manifestList) - if err != nil { - return "", fmt.Errorf("json unmarshal error: %v", err) - } - - // look up manifest of the corresponding architecture - var maxWeight uint64 - var resDigest digest.Digest - for _, item := range manifestList.List { - tmpWeight := getWeightByPlatform(item.Platform, platform) - if tmpWeight > maxWeight { - resDigest = digest.Digest(item.Digest) - maxWeight = tmpWeight - } - } - if maxWeight != 0 { - return resDigest, nil - } - return "", fmt.Errorf("no manifest of the corresponding platform") -} - -func getWeightByPlatform(src, target v1.Platform) uint64 { - var weight uint64 - if target.OS != "" && src.OS == target.OS { - weight++ - } - - if target.Architecture != "" && src.Architecture == target.Architecture { - weight++ - } - - if target.Variant != "" && src.Variant == target.Variant { - weight++ - } - - if target.OSVersion != "" && src.OSVersion == target.OSVersion { - weight++ - } - return weight -} diff --git a/pkg/registry/save/registry_save.go b/pkg/registry/save/registry_save.go deleted file mode 100644 index 6708d6ef14a..00000000000 --- a/pkg/registry/save/registry_save.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package save - -import ( - "context" - "fmt" - "strings" - stdsync "sync" - "time" - - "github.com/containers/image/v5/copy" - itype "github.com/containers/image/v5/types" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "golang.org/x/sync/errgroup" - - "github.com/labring/sealos/pkg/registry/handler" - "github.com/labring/sealos/pkg/registry/sync" - httputils "github.com/labring/sealos/pkg/utils/http" - "github.com/labring/sealos/pkg/utils/logger" -) - -const localhost = "127.0.0.1" - -func (is *tmpRegistryImage) SaveImages(images []string, dir string, platform v1.Platform) ([]string, error) { - logger.Debug("trying to save images: %+v for platform: %s", images, - strings.Join([]string{platform.OS, platform.Architecture, platform.Variant}, ",")) - config, err := handler.NewConfig(dir, 0) - if err != nil { - return nil, err - } - config.Log.AccessLog.Disabled = true - errCh := handler.Run(is.ctx, config) - - probeCtx, cancel := context.WithTimeout(is.ctx, time.Second*3) - defer cancel() - ep := sync.ParseRegistryAddress(localhost, config.HTTP.Addr) - if err = httputils.WaitUntilEndpointAlive(probeCtx, "http://"+ep); err != nil { - return nil, err - } - - if platform.OS == "" { - platform.OS = "linux" - } - sys := &itype.SystemContext{ - ArchitectureChoice: platform.Architecture, - OSChoice: platform.OS, - VariantChoice: platform.Variant, - DockerInsecureSkipTLSVerify: itype.OptionalBoolTrue, - } - eg, _ := errgroup.WithContext(is.ctx) - numCh := make(chan struct{}, is.maxPullProcs) - var outImages []string - var mu stdsync.Mutex - for index := range images { - img := images[index] - numCh <- struct{}{} - eg.Go(func() error { - mu.Lock() - defer func() { - <-numCh - mu.Unlock() - }() - srcRef, err := sync.ImageNameToReference(sys, img, is.auths) - if err != nil { - return err - } - err = sync.ToImage(is.ctx, sys, srcRef, ep, copy.CopySystemImage) - if err != nil { - return fmt.Errorf("save image %s: %w", img, err) - } - outImages = append(outImages, img) - return nil - }) - } - err = eg.Wait() - errCh <- err - return outImages, err -} diff --git a/pkg/registry/save/save.go b/pkg/registry/save/save.go deleted file mode 100644 index 0be83550ad4..00000000000 --- a/pkg/registry/save/save.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package save - -import ( - "bufio" - "context" - "fmt" - "io" - "os" - "strings" - "sync" - - "github.com/labring/sealos/pkg/registry/save/lib/distributionpkg/proxy" - "github.com/labring/sealos/pkg/registry/save/lib/imagemanifest" - - "github.com/google/go-containerregistry/pkg/name" - - storagedriver "github.com/distribution/distribution/v3/registry/storage/driver" - - distribution "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/configuration" - "github.com/distribution/distribution/v3/reference" - "github.com/distribution/distribution/v3/registry/storage" - "github.com/distribution/distribution/v3/registry/storage/driver/factory" - dockerstreams "github.com/docker/cli/cli/streams" - "github.com/docker/docker/api/types" - dockerjsonmessage "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/progress" - "github.com/docker/docker/pkg/streamformatter" - "github.com/opencontainers/go-digest" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "golang.org/x/sync/errgroup" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/labring/sealos/pkg/passwd" - "github.com/labring/sealos/pkg/utils/http" - "github.com/labring/sealos/pkg/utils/logger" -) - -const ( - HTTPS = "https://" - HTTP = "http://" - defaultProxyURL = "https://registry-1.docker.io" - configRootDir = "rootdirectory" - defaultDomain = "docker.io" - manifestV2 = "application/vnd.docker.distribution.manifest.v2+json" - manifestOCI = "application/vnd.oci.image.manifest.v1+json" - manifestList = "application/vnd.docker.distribution.manifest.list.v2+json" - manifestOCIIndex = "application/vnd.oci.image.index.v1+json" -) - -func (is *defaultImage) SaveImages(images []string, dir string, platform v1.Platform) ([]string, error) { - logger.Debug("trying to save images: %+v for platform: %s", images, - strings.Join([]string{platform.OS, platform.Architecture, platform.Variant}, ",")) - // init a pipe for display pull message - reader, writer := io.Pipe() - defer func() { - _ = reader.Close() - _ = writer.Close() - }() - is.progressOut = streamformatter.NewJSONProgressOutput(writer, false) - - go func() { - err := dockerjsonmessage.DisplayJSONMessagesToStream(reader, dockerstreams.NewOut(os.Stdout), nil) - if err != nil && err != io.ErrClosedPipe { - logger.Warn("error occurs in display progressing, err: %s", err) - } - }() - - //handle image name - pullImages := sets.NewString() - for _, image := range images { - named, err := name.ParseReference(image) - if err != nil { - return nil, fmt.Errorf("parse image name error: %v", err) - } - if pullImages.Has(named.Name()) { - continue - } - is.domainToImages[named.Name()] = append(is.domainToImages[named.Name()], named) - progress.Message(is.progressOut, "", fmt.Sprintf("Pulling image: %s", named.Name())) - pullImages.Insert(named.Name()) - } - - //perform image save ability - eg, _ := errgroup.WithContext(context.Background()) - numCh := make(chan struct{}, is.maxPullProcs) - var outImages []string - var mu sync.Mutex - for _, nameds := range is.domainToImages { - tmpnameds := nameds - numCh <- struct{}{} - eg.Go(func() error { - auth := is.auths[tmpnameds[0].Context().RegistryStr()] - if auth.ServerAddress == "" { - auth.ServerAddress = tmpnameds[0].Context().RegistryStr() - } - registry, err := NewProxyRegistry(is.ctx, dir, auth, nil) - if err != nil { - return fmt.Errorf("init registry error: %v", err) - } - mu.Lock() - defer func() { - <-numCh - mu.Unlock() - }() - - err = is.save(tmpnameds, platform, registry) - if err != nil { - return fmt.Errorf("save image %s: %w", tmpnameds[0].Name(), err) - } - outImages = append(outImages, tmpnameds[0].Name()) - return nil - }) - } - if err := eg.Wait(); err != nil { - return nil, err - } - if len(images) != 0 { - progress.Message(is.progressOut, "", "Status: images save success") - } - return outImages, nil -} - -func authConfigToProxy(auth types.AuthConfig) configuration.Proxy { - // set the URL of registry - if auth.ServerAddress == defaultDomain || auth.ServerAddress == "" { - auth.ServerAddress = defaultProxyURL - } - if _, ok := http.IsURL(auth.ServerAddress); !ok { - auth.ServerAddress = HTTPS + auth.ServerAddress - } - if auth.Auth != "" && auth.Username == "" && auth.Password == "" { - uPassword, _ := passwd.LoginAuthDecode(auth.Auth) - if data := strings.Split(uPassword, ":"); len(data) > 1 { - auth.Username = data[0] - auth.Password = data[1] - } - } - // proxy not support IdentityToken - return configuration.Proxy{ - RemoteURL: auth.ServerAddress, - Username: auth.Username, - Password: auth.Password, - } -} - -func NewProxyRegistry(ctx context.Context, rootdir string, auth types.AuthConfig, driver storagedriver.StorageDriver) (distribution.Namespace, error) { - config := configuration.Configuration{ - Proxy: authConfigToProxy(auth), - Storage: configuration.Storage{ - "filesystem": configuration.Parameters{configRootDir: rootdir}, - }, - } - var err error - if driver == nil { - driver, err = factory.Create(config.Storage.Type(), config.Storage.Parameters()) - if err != nil { - return nil, fmt.Errorf("create storage driver error: %v", err) - } - } - - //create a local registry service - registry, err := storage.NewRegistry(ctx, driver, make([]storage.RegistryOption, 0)...) - if err != nil { - return nil, fmt.Errorf("create local registry error: %v", err) - } - - proxyRegistry, err := proxy.NewRegistryPullThroughCache(ctx, registry, config.Proxy) - if err != nil { // try http - logger.Warn("https error: %v, sealos try to use http", err) - config.Proxy.RemoteURL = strings.Replace(config.Proxy.RemoteURL, HTTPS, HTTP, 1) - proxyRegistry, err = proxy.NewRegistryPullThroughCache(ctx, registry, config.Proxy) - if err != nil { - return nil, fmt.Errorf("create proxy registry error: %v", err) - } - } - return proxyRegistry, nil -} - -func (is *defaultImage) save(nameds []name.Reference, platform v1.Platform, registry distribution.Namespace) error { - repo, err := is.getRepository(nameds[0], registry) - if err != nil { - return err - } - - imageDigests, err := is.saveManifestAndGetDigest(nameds, repo, platform) - if err != nil { - return err - } - - err = is.saveBlobs(imageDigests, repo) - if err != nil { - return err - } - - return nil -} - -func (is *defaultImage) getRepository(named name.Reference, registry distribution.Namespace) (distribution.Repository, error) { - repoName, err := reference.WithName(named.Context().RepositoryStr()) - if err != nil { - return nil, fmt.Errorf("get repository name error: %v", err) - } - repo, err := registry.Repository(is.ctx, repoName) - if err != nil { - return nil, fmt.Errorf("get repository error: %v", err) - } - return repo, nil -} - -func (is *defaultImage) saveManifestAndGetDigest(nameds []name.Reference, repo distribution.Repository, platform v1.Platform) ([]digest.Digest, error) { - manifest, err := repo.Manifests(is.ctx, make([]distribution.ManifestServiceOption, 0)...) - if err != nil { - return nil, fmt.Errorf("get manifest service error: %v", err) - } - eg, _ := errgroup.WithContext(context.Background()) - numCh := make(chan struct{}, is.maxPullProcs) - imageDigests := make([]digest.Digest, 0) - for _, named := range nameds { - tmpnamed := named - numCh <- struct{}{} - eg.Go(func() error { - defer func() { - <-numCh - }() - var imageDigestFromImageName digest.Digest - if _, ok := tmpnamed.(name.Tag); ok { - desc, err := repo.Tags(is.ctx).Get(is.ctx, tmpnamed.Identifier()) - if err != nil { - return fmt.Errorf("get %s tag descriptor error: %v, try \"sealos login\" if you are using a private registry", tmpnamed.Context().RepositoryStr(), err) - } - imageDigestFromImageName = desc.Digest - } else { - imageDigestFromImageName, err = digest.Parse(tmpnamed.Identifier()) - if err != nil { - return fmt.Errorf("parse image digest error: %v", err) - } - } - - imageDigest, err := is.handleManifest(manifest, imageDigestFromImageName, platform) - if err != nil { - return fmt.Errorf("get digest error: %v", err) - } - imageDigests = append(imageDigests, imageDigest) - return nil - }) - } - if err := eg.Wait(); err != nil { - return nil, err - } - - return imageDigests, nil -} - -func (is *defaultImage) handleManifest(manifest distribution.ManifestService, imagedigest digest.Digest, platform v1.Platform) (digest.Digest, error) { - mani, err := manifest.Get(is.ctx, imagedigest, make([]distribution.ManifestServiceOption, 0)...) - if err != nil { - return digest.Digest(""), fmt.Errorf("get image manifest error: %v", err) - } - ct, p, err := mani.Payload() - if err != nil { - return digest.Digest(""), fmt.Errorf("failed to get image manifest payload: %v", err) - } - - switch ct { - case manifestV2, manifestOCI: - return imagedigest, nil - case manifestList, manifestOCIIndex: - imageDigest, err := imagemanifest.GetImageManifestDigest(p, platform) - if err != nil { - return digest.Digest(""), fmt.Errorf("get digest from manifest list error: %v", err) - } - return imageDigest, nil - case "": - //OCI image or image index - no media type in the content - - //First see if it is a list - imageDigest, _ := imagemanifest.GetImageManifestDigest(p, platform) - if imageDigest != "" { - return imageDigest, nil - } - //If not list, then assume it must be an image manifest - return imagedigest, nil - default: - return digest.Digest(""), fmt.Errorf("unrecognized manifest content type") - } -} - -func (is *defaultImage) saveBlobs(imageDigests []digest.Digest, repo distribution.Repository) error { - manifest, err := repo.Manifests(is.ctx, make([]distribution.ManifestServiceOption, 0)...) - if err != nil { - return fmt.Errorf("failed to get blob service: %v", err) - } - eg, _ := errgroup.WithContext(context.Background()) - numCh := make(chan struct{}, is.maxPullProcs) - blobLists := make([]digest.Digest, 0) - - //get blob list - //each blob identified by a digest - for _, imageDigest := range imageDigests { - tmpImageDigest := imageDigest - numCh <- struct{}{} - eg.Go(func() error { - defer func() { - <-numCh - }() - - blobListJSON, err := manifest.Get(is.ctx, tmpImageDigest, make([]distribution.ManifestServiceOption, 0)...) - if err != nil { - return err - } - - blobList, err := imagemanifest.GetBlobList(blobListJSON) - if err != nil { - return fmt.Errorf("failed to get blob list: %v", err) - } - blobLists = append(blobLists, blobList...) - return nil - }) - } - if err = eg.Wait(); err != nil { - return err - } - - //pull and save each blob - blobStore := repo.Blobs(is.ctx) - type localStore interface { - Local(context.Context) (distribution.BlobStore, error) - } - ls, _ := blobStore.(localStore).Local(is.ctx) - for _, blob := range blobLists { - tmpBlob := blob - if len(blob) == 0 { - continue - } - numCh <- struct{}{} - eg.Go(func() error { - defer func() { - <-numCh - }() - - simpleDgst := string(tmpBlob)[7:19] - - _, err = blobStore.Stat(is.ctx, tmpBlob) - if err == nil { //blob already exist - progress.Update(is.progressOut, simpleDgst, "already exists") - return nil - } - reader, err := blobStore.Open(is.ctx, tmpBlob) - if err != nil { - return fmt.Errorf("failed to get blob %s: %v", tmpBlob, err) - } - - size, err := reader.Seek(0, io.SeekEnd) - if err != nil { - return fmt.Errorf("seek end error when save blob %s: %v", tmpBlob, err) - } - _, err = reader.Seek(0, io.SeekStart) - if err != nil { - return fmt.Errorf("failed to seek start when save blob %s: %v", tmpBlob, err) - } - preader := progress.NewProgressReader(reader, is.progressOut, size, simpleDgst, "Downloading") - - defer func() { - _ = reader.Close() - _ = preader.Close() - progress.Update(is.progressOut, simpleDgst, "Download complete") - }() - - // store to local filesystem - bf := bufio.NewReader(preader) - bw, err := ls.Create(is.ctx) - if err != nil { - return fmt.Errorf("failed to create local blob writer: %v", err) - } - if _, err = bf.WriteTo(bw); err != nil { - return fmt.Errorf("failed to write blob to service: %v", err) - } - _, err = bw.Commit(is.ctx, distribution.Descriptor{ - MediaType: "", - Size: bw.Size(), - Digest: tmpBlob, - }) - if err != nil { - return fmt.Errorf("failed to commit blob %s to local: %v", tmpBlob, err) - } - - return nil - }) - } - - return eg.Wait() -} diff --git a/pkg/registry/save/save_test.go b/pkg/registry/save/save_test.go deleted file mode 100644 index 77c56b2b655..00000000000 --- a/pkg/registry/save/save_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright © 2021 sealos. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package save - -import ( - "context" - "os" - "testing" - - v1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/labring/sealos/pkg/registry/crane" -) - -func TestSaveImages(t *testing.T) { - defer func() { - _ = os.RemoveAll("testdata/registry") - }() - t.Setenv("DOCKER_CONFIG", "/Users/cuisongliu/.docker") - authConfigs, _ := crane.GetAuthInfo(nil) - save := newDefaultRegistrySaver(context.TODO(), 5, authConfigs) - t.Run("no credentials found", func(t *testing.T) { - _ = os.Mkdir("testdata/registry", 0755) - imgs, err := save.SaveImages([]string{"nginx"}, "testdata/registry", v1.Platform{ - Architecture: "amd64", - }) - if err != nil { - t.Error(err) - return - } - t.Logf("images: %+v", imgs) - }) -} - -func TestSaveTmpImages(t *testing.T) { - defer func() { - _ = os.RemoveAll("testdata/registry") - }() - t.Setenv("DOCKER_CONFIG", "/Users/cuisongliu/.docker") - authConfigs, _ := crane.GetAuthInfo(nil) - save := newTmpRegistrySaver(context.TODO(), 5, authConfigs) - t.Run("no credentials found", func(t *testing.T) { - _ = os.Mkdir("testdata/registry", 0755) - imgs, err := save.SaveImages([]string{"nginx"}, "testdata/registry", v1.Platform{ - Architecture: "amd64", - OS: "linux", - }) - if err != nil { - t.Error(err) - return - } - t.Logf("images: %+v", imgs) - }) -} diff --git a/pkg/registry/save/testdata/config.json b/pkg/registry/save/testdata/config.json deleted file mode 100644 index 5ec2c48dfa5..00000000000 --- a/pkg/registry/save/testdata/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "auths": { - "192.168.64.27:5000": { - "auth": "YWRtaW46cGFzc3cwcmQ=" - } - } -} diff --git a/pkg/registry/sync/sync.go b/pkg/registry/sync/sync.go deleted file mode 100644 index f323fd070bb..00000000000 --- a/pkg/registry/sync/sync.go +++ /dev/null @@ -1,263 +0,0 @@ -/* -Copyright 2023 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package sync - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "os" - "path/filepath" - "strings" - - "github.com/google/go-containerregistry/pkg/name" - - "github.com/containers/common/pkg/retry" - "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/docker/daemon" - "github.com/containers/image/v5/docker/reference" - "github.com/containers/image/v5/signature" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - dtype "github.com/docker/docker/api/types" - - "github.com/labring/sealos/pkg/utils/logger" -) - -const ( - defaultPort = "5000" -) - -type Options struct { - Source string - Target string - SelectionOptions []copy.ImageListSelection - OmitError bool - SystemContext *types.SystemContext - ReportWriter io.Writer -} - -func ToRegistry(ctx context.Context, opts *Options) error { - src := opts.Source - dst := opts.Target - sys := opts.SystemContext - reportWriter := opts.ReportWriter - - policyContext, err := getPolicyContext() - if err != nil { - return err - } - repos, err := docker.SearchRegistry(ctx, sys, src, "", 1<<10) - if err != nil { - return err - } - if len(repos) == 0 { - return nil - } - if reportWriter == nil { - reportWriter = io.Discard - } - logger.Debug("syncing repos %v from %s to %s", repos, src, dst) - for i := range repos { - named, err := parseRepositoryReference(fmt.Sprintf("%s/%s", src, repos[i].Name)) - if err != nil { - return err - } - - refs, err := imagesToCopyFromRepo(ctx, sys, named) - if err != nil { - return err - } - for j := range refs { - destSuffix := strings.TrimPrefix(refs[j].DockerReference().String(), src) - destRef, err := docker.ParseReference(fmt.Sprintf("//%s", filepath.Join(dst, destSuffix))) - if err != nil { - return err - } - ref := refs[j] - for s := range opts.SelectionOptions { - selection := opts.SelectionOptions[s] - logger.Debug("syncing %s with selection %v", destRef.DockerReference().String(), selection) - if err = retry.RetryIfNecessary(ctx, func() error { - _, copyErr := copy.Image(ctx, policyContext, destRef, ref, ©.Options{ - SourceCtx: sys, - DestinationCtx: sys, - ImageListSelection: selection, - ReportWriter: reportWriter, - }) - return copyErr - }, getRetryOptions()); err != nil { - if strings.Contains(err.Error(), "manifest unknown") && selection == copy.CopyAllImages { - continue - } - if !opts.OmitError { - return err - } - logger.Warn("failed to copy image %s: %v", refs[j].DockerReference().String(), err) - } - } - } - } - return nil -} - -func getRetryOptions() *retry.RetryOptions { - return &retry.RetryOptions{ - MaxRetry: 3, - IsErrorRetryable: func(err error) bool { - if strings.Contains(err.Error(), "500 Internal Server Error") { - return true - } - return retry.IsErrorRetryable(err) - }, - } -} - -func ImageNameToReference(sys *types.SystemContext, img string, auth map[string]dtype.AuthConfig) (types.ImageReference, error) { - src, err := name.ParseReference(img) - if err != nil { - return nil, fmt.Errorf("ref invalid source name %s: %v", img, err) - } - reg := src.Context().RegistryStr() - info, ok := auth[reg] - if sys != nil && ok { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: info.Username, - Password: info.Password, - IdentityToken: info.IdentityToken, - } - } - image := src.Name() - srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image)) - if err != nil { - return nil, fmt.Errorf("invalid source name %s: %v", src, err) - } - return srcRef, nil -} - -func ToImage(ctx context.Context, sys *types.SystemContext, src types.ImageReference, dst string, selection copy.ImageListSelection) error { - allSrcImage := src.DockerReference().String() - var repo string - parts := strings.SplitN(allSrcImage, "/", 2) - if len(parts) == 2 && (strings.ContainsRune(parts[0], '.') || strings.ContainsRune(parts[0], ':')) { - // The first part of the repository is treated as the registry domain - // iff it contains a '.' or ':' character, otherwise it is all repository - // and the domain defaults to Docker Hub. - _ = parts[0] - repo = parts[1] - } - logger.Debug("syncing image from %s to %s", allSrcImage, dst) - //named. - dstImage := strings.Join([]string{dst, repo}, "/") - destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", dstImage)) - if err != nil { - return fmt.Errorf("invalid destination name %s: %v", dst, err) - } - policyContext, err := getPolicyContext() - if err != nil { - return err - } - return retry.RetryIfNecessary(ctx, func() error { - _, err = copy.Image(ctx, policyContext, destRef, src, ©.Options{ - SourceCtx: sys, - DestinationCtx: sys, - ImageListSelection: selection, - ReportWriter: os.Stdout, - }) - return err - }, getRetryOptions()) -} - -func getPolicyContext() (*signature.PolicyContext, error) { - policy := &signature.Policy{ - Default: []signature.PolicyRequirement{ - signature.NewPRInsecureAcceptAnything(), - }, - Transports: map[string]signature.PolicyTransportScopes{ - daemon.Transport.Name(): { - "": signature.PolicyRequirements{signature.NewPRInsecureAcceptAnything()}, - }, - }, - } - return signature.NewPolicyContext(policy) -} - -func parseRepositoryReference(input string) (reference.Named, error) { - ref, err := reference.ParseNormalizedNamed(input) - if err != nil { - return nil, err - } - if !reference.IsNameOnly(ref) { - return nil, errors.New("input names a reference, not a repository") - } - return ref, nil -} - -func imagesToCopyFromRepo(ctx context.Context, sys *types.SystemContext, repoRef reference.Named) ([]types.ImageReference, error) { - tags, err := getImageTags(ctx, sys, repoRef) - if err != nil { - return nil, err - } - - var sourceReferences []types.ImageReference - for _, tag := range tags { - taggedRef, err := reference.WithTag(repoRef, tag) - if err != nil { - logger.Error("Error creating a tagged reference from registry tag %s:%s list: %v", repoRef.Name(), tag, err) - continue - } - ref, err := docker.NewReference(taggedRef) - if err != nil { - return nil, fmt.Errorf("cannot obtain a valid image reference for transport %q and reference %s: %w", docker.Transport.Name(), taggedRef.String(), err) - } - sourceReferences = append(sourceReferences, ref) - } - return sourceReferences, nil -} - -func getImageTags(ctx context.Context, sysCtx *types.SystemContext, repoRef reference.Named) ([]string, error) { - name := repoRef.Name() - dockerRef, err := docker.NewReference(reference.TagNameOnly(repoRef)) - if err != nil { - return nil, err - } - tags, err := docker.GetRepositoryTags(ctx, sysCtx, dockerRef) - if err != nil { - return nil, fmt.Errorf("error determining repository tag for %s: %v", name, err) - } - return tags, nil -} - -func ParseRegistryAddress(s string, args ...string) string { - if strings.Contains(s, ":") { - return s - } - - var portStr string - if len(args) > 0 { - portStr = args[0] - } else { - portStr = defaultPort - } - if idx := strings.Index(portStr, ":"); idx >= 0 { - portStr = portStr[idx+1:] - } - return net.JoinHostPort(s, portStr) -} diff --git a/pkg/passwd/passwd.go b/pkg/utils/passwd/passwd.go similarity index 100% rename from pkg/passwd/passwd.go rename to pkg/utils/passwd/passwd.go diff --git a/pkg/utils/tmpl/tmpl.go b/pkg/utils/tmpl/tmpl.go deleted file mode 100644 index db6848b4f98..00000000000 --- a/pkg/utils/tmpl/tmpl.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2022 cuisongliu@qq.com. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package tmpl - -import ( - "path/filepath" - "strings" -) - -func Matcher(path string) bool { - ext := strings.ToLower(filepath.Ext(path)) - return ext == ".tmpl" -} diff --git a/staging/src/github.com/labring/image-cri-shim/pkg/server/utils.go b/staging/src/github.com/labring/image-cri-shim/pkg/server/utils.go index fdc7a0761e2..a84a979a2f7 100644 --- a/staging/src/github.com/labring/image-cri-shim/pkg/server/utils.go +++ b/staging/src/github.com/labring/image-cri-shim/pkg/server/utils.go @@ -17,7 +17,7 @@ limitations under the License. package server import ( - "github.com/labring/sealos/pkg/registry/crane" + "github.com/labring/sreg/pkg/registry/crane" "github.com/docker/docker/api/types" diff --git a/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go b/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go index ab7638a4242..72caac9ad37 100644 --- a/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go +++ b/staging/src/github.com/labring/image-cri-shim/pkg/types/config.go @@ -22,7 +22,7 @@ import ( "strings" "time" - registry2 "github.com/labring/sealos/pkg/registry/crane" + registry2 "github.com/labring/sreg/pkg/registry/crane" types2 "github.com/docker/docker/api/types"