310 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handler
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"github.com/google/go-containerregistry/pkg/name"
 | |
| 	"github.com/google/go-containerregistry/pkg/v1"
 | |
| 	"github.com/google/go-containerregistry/pkg/v1/remote"
 | |
| 	"github.com/loveuer/nf"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"nf-repo/internal/interfaces"
 | |
| 	"nf-repo/internal/model"
 | |
| 	"nf-repo/internal/opt"
 | |
| 	"nf-repo/internal/util/r"
 | |
| 	"nf-repo/internal/util/rerr"
 | |
| 	"nf-repo/internal/util/tools"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| func RepoSettings(c *nf.Ctx) error {
 | |
| 	return r.Resp200(c, nf.Map{
 | |
| 		"base_address": opt.BaseAddress,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func RepoList(mh interfaces.ManifestHandler) nf.HandlerFunc {
 | |
| 	return func(c *nf.Ctx) error {
 | |
| 		type Req struct {
 | |
| 			Keyword string `json:"keyword" query:"keyword"`
 | |
| 			N       int    `json:"n" query:"n"`
 | |
| 			Last    int    `json:"last" query:"last"`
 | |
| 		}
 | |
| 
 | |
| 		var (
 | |
| 			err     error
 | |
| 			re      *rerr.RepositoryError
 | |
| 			req     = new(Req)
 | |
| 			catalog *model.Catalog
 | |
| 		)
 | |
| 
 | |
| 		if err = c.QueryParser(req); err != nil {
 | |
| 			return r.Resp400(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if req.N == 0 {
 | |
| 			req.N = 20
 | |
| 		}
 | |
| 
 | |
| 		if req.N > 1000 {
 | |
| 			return r.Resp400(c, "limit invalid: too big")
 | |
| 		}
 | |
| 
 | |
| 		if catalog, re = mh.Catalog(c.Request.Context(), req.N, req.Last, req.Keyword); re != nil {
 | |
| 			return r.Resp(c, uint32(re.Status), "", re.Code, re.Message)
 | |
| 		}
 | |
| 
 | |
| 		return r.Resp200(c, nf.Map{"list": catalog.Repos, "total": 0})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TagList(mh interfaces.ManifestHandler) nf.HandlerFunc {
 | |
| 	return func(c *nf.Ctx) error {
 | |
| 		type Req struct {
 | |
| 			Repo    string `json:"repo" query:"repo"`
 | |
| 			Keyword string `json:"keyword" query:"keyword"`
 | |
| 			N       int    `json:"n" query:"n"`
 | |
| 			Last    int    `json:"last" query:"last"`
 | |
| 		}
 | |
| 
 | |
| 		var (
 | |
| 			err error
 | |
| 			req = new(Req)
 | |
| 			tag *model.Tag
 | |
| 			re  *rerr.RepositoryError
 | |
| 		)
 | |
| 
 | |
| 		if err = c.QueryParser(req); err != nil {
 | |
| 			return r.Resp400(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if req.Repo == "" {
 | |
| 			return r.Resp400(c, "repo invalid")
 | |
| 		}
 | |
| 
 | |
| 		if req.N <= 0 {
 | |
| 			req.N = 20
 | |
| 		}
 | |
| 
 | |
| 		if req.N > 1000 {
 | |
| 			return r.Resp400(c, "limit invalid: too big")
 | |
| 		}
 | |
| 
 | |
| 		if tag, re = mh.Tags(c.Request.Context(), req.Repo, req.N, req.Last, req.Keyword); re != nil {
 | |
| 			return r.Resp(c, uint32(re.Status), "", re.Code, re.Message)
 | |
| 		}
 | |
| 
 | |
| 		return r.Resp200(c, nf.Map{"list": tag.RepoTags, "total": tag.Tags})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func ProxyDownloadImage(mh interfaces.ManifestHandler, bh interfaces.BlobHandler) nf.HandlerFunc {
 | |
| 	return func(c *nf.Ctx) error {
 | |
| 		type Req struct {
 | |
| 			Source string `json:"source"`
 | |
| 			Target string `json:"target"`
 | |
| 			Proxy  string `json:"proxy"`
 | |
| 		}
 | |
| 
 | |
| 		type Msg struct {
 | |
| 			Target string `json:"target"`
 | |
| 			Action string `json:"action"`
 | |
| 			Msg    string `json:"msg"`
 | |
| 			Data   any    `json:"data"`
 | |
| 		}
 | |
| 
 | |
| 		var (
 | |
| 			err       error
 | |
| 			req       = new(Req)
 | |
| 			puller    *remote.Puller
 | |
| 			tn        name.Tag
 | |
| 			des       *remote.Descriptor
 | |
| 			transport = &http.Transport{}
 | |
| 			img       v1.Image
 | |
| 			manifest  *v1.Manifest
 | |
| 			bs        []byte
 | |
| 			mhash     model.Hash
 | |
| 			//progressCh = make(chan v1.Update, 16)
 | |
| 		)
 | |
| 
 | |
| 		if err = c.BodyParser(req); err != nil {
 | |
| 			return r.Resp400(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if req.Target == "" {
 | |
| 			return r.Resp400(c, "target invalid")
 | |
| 		}
 | |
| 
 | |
| 		repoTags := strings.Split(req.Target, ":")
 | |
| 		if len(repoTags) != 2 {
 | |
| 			return r.Resp400(c, "target invalid: should only contain one ':'")
 | |
| 		}
 | |
| 
 | |
| 		repo, tag := repoTags[0], repoTags[1]
 | |
| 		repo = strings.TrimPrefix(repo, opt.BaseAddress)
 | |
| 		repo = strings.Trim(repo, "/")
 | |
| 
 | |
| 		if req.Source == "" {
 | |
| 			return r.Resp400(c, "source invalid")
 | |
| 		}
 | |
| 
 | |
| 		imageName := fmt.Sprintf("%s/%s:%s", opt.BaseAddress, repo, tag)
 | |
| 		_ = imageName
 | |
| 
 | |
| 		logrus.
 | |
| 			WithField("path", "handler.ProxyDownloadImage").
 | |
| 			WithField("req", *req).
 | |
| 			WithField("repo", repo).
 | |
| 			Debug()
 | |
| 
 | |
| 		if req.Proxy != "" {
 | |
| 			var pu *url.URL
 | |
| 			if pu, err = url.Parse(req.Proxy); err != nil {
 | |
| 				return r.Resp400(c, err.Error())
 | |
| 			}
 | |
| 
 | |
| 			transport.Proxy = http.ProxyURL(pu)
 | |
| 		}
 | |
| 
 | |
| 		if puller, err = remote.NewPuller(
 | |
| 			remote.WithTransport(transport),
 | |
| 		); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if tn, err = name.NewTag(req.Source); err != nil {
 | |
| 			return r.Resp400(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		_ = c.SSEvent("pull", Msg{
 | |
| 			Target: req.Target,
 | |
| 			Action: "get",
 | |
| 			Msg:    "开始获取镜像信息",
 | |
| 		})
 | |
| 		_ = c.Flush()
 | |
| 
 | |
| 		ctx := tools.TimeoutCtx(c.Request.Context())
 | |
| 		if des, err = puller.Get(ctx, tn); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if img, err = des.Image(); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if manifest, err = img.Manifest(); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		total := model.ManifestCountSize(manifest)
 | |
| 		size := 0
 | |
| 
 | |
| 		_ = c.SSEvent("pull", Msg{
 | |
| 			Target: req.Target,
 | |
| 			Action: "layer",
 | |
| 			Msg:    "正在获取镜像数据: 0.00%",
 | |
| 			Data:   map[string]any{"total": total, "size": size, "index_total": len(manifest.Layers) + 1, "index_size": 0},
 | |
| 		})
 | |
| 		_ = c.Flush()
 | |
| 
 | |
| 		var (
 | |
| 			tly     v1.Layer
 | |
| 			tdigest v1.Hash
 | |
| 			treader io.ReadCloser
 | |
| 		)
 | |
| 
 | |
| 		if tly, err = img.LayerByDigest(manifest.Config.Digest); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if tdigest, err = tly.Digest(); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if treader, err = tly.Uncompressed(); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 		defer treader.Close()
 | |
| 
 | |
| 		if err = bh.Put(
 | |
| 			c.Request.Context(),
 | |
| 			repo,
 | |
| 			model.Hash{Algorithm: tdigest.Algorithm, Hex: tdigest.Hex},
 | |
| 			treader,
 | |
| 		); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		size = size + int(manifest.Config.Size)
 | |
| 		_ = c.SSEvent("pull", Msg{
 | |
| 			Target: req.Target,
 | |
| 			Action: "layer",
 | |
| 			Msg:    fmt.Sprintf("正在获取镜像数据: %.2f%%", float64(size)/float64(total)*100),
 | |
| 			Data:   map[string]any{"total": model.ManifestCountSize(manifest), "size": size, "index_total": len(manifest.Layers) + 1, "index_size": 1},
 | |
| 		})
 | |
| 		_ = c.Flush()
 | |
| 
 | |
| 		if bs, err = json.Marshal(manifest); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if mhash, _, err = model.SHA256(bytes.NewReader(bs)); err != nil {
 | |
| 			return r.Resp500(c, err.Error())
 | |
| 		}
 | |
| 
 | |
| 		for idx := range manifest.Layers {
 | |
| 			var (
 | |
| 				reader io.ReadCloser
 | |
| 				lyHash v1.Hash
 | |
| 				ly     v1.Layer
 | |
| 			)
 | |
| 
 | |
| 			lyHash = manifest.Layers[idx].Digest
 | |
| 
 | |
| 			if ly, err = img.LayerByDigest(manifest.Layers[idx].Digest); err != nil {
 | |
| 				return r.Resp500(c, err.Error())
 | |
| 			}
 | |
| 
 | |
| 			if reader, err = ly.Compressed(); err != nil {
 | |
| 				return r.Resp500(c, err.Error())
 | |
| 			}
 | |
| 			defer reader.Close()
 | |
| 
 | |
| 			if err = bh.Put(c.Request.Context(), repo, model.Hash{Algorithm: lyHash.Algorithm, Hex: lyHash.Hex}, reader); err != nil {
 | |
| 				return r.Resp500(c, err.Error())
 | |
| 			}
 | |
| 
 | |
| 			size = size + int(manifest.Layers[idx].Size)
 | |
| 			_ = c.SSEvent("pull", Msg{
 | |
| 				Target: req.Target,
 | |
| 				Action: "layer",
 | |
| 				Msg:    fmt.Sprintf("正在获取镜像数据: %.2f%%", float64(size)/float64(total)*100),
 | |
| 				Data:   map[string]any{"total": model.ManifestCountSize(manifest), "size": size, "index_total": len(manifest.Layers) + 1, "index_size": 2 + idx},
 | |
| 			})
 | |
| 			_ = c.Flush()
 | |
| 		}
 | |
| 
 | |
| 		var re *rerr.RepositoryError
 | |
| 
 | |
| 		if re = mh.Put(c.Request.Context(), repo, tag, mhash.String(), &model.RepoSimpleManifest{
 | |
| 			ContentType: string(manifest.MediaType),
 | |
| 			Blob:        bs,
 | |
| 		}); re != nil {
 | |
| 			return r.Resp(c, uint32(re.Status), re.Message, re.Code, nil)
 | |
| 		}
 | |
| 
 | |
| 		_ = c.SSEvent("pull", Msg{
 | |
| 			Target: req.Target,
 | |
| 			Action: "done",
 | |
| 			Msg:    "获取成功",
 | |
| 			Data:   manifest,
 | |
| 		})
 | |
| 
 | |
| 		return c.Flush()
 | |
| 	}
 | |
| }
 |