feat: proxy download image
This commit is contained in:
@ -29,7 +29,7 @@ func handleCatalog(ctx *nf.Ctx, m interfaces.ManifestHandler) error {
|
||||
list *model.Catalog
|
||||
)
|
||||
|
||||
if list, re = m.Catelog(ctx.Request.Context(), n, 0); re != nil {
|
||||
if list, re = m.Catalog(ctx.Request.Context(), n, 0, ""); re != nil {
|
||||
return rerr.Error(ctx, re)
|
||||
}
|
||||
|
||||
|
@ -86,13 +86,24 @@ func handleManifest(ctx *nf.Ctx, m interfaces.ManifestHandler) error {
|
||||
return ctx.SendStatus(http.StatusOK)
|
||||
|
||||
case http.MethodPut:
|
||||
b := &bytes.Buffer{}
|
||||
io.Copy(b, ctx.Request.Body)
|
||||
h, _, _ := model.SHA256(bytes.NewReader(b.Bytes()))
|
||||
digest := h.String()
|
||||
var (
|
||||
err error
|
||||
buf = &bytes.Buffer{}
|
||||
hash model.Hash
|
||||
)
|
||||
|
||||
mf := model.Manifest{
|
||||
Blob: b.Bytes(),
|
||||
if _, err = io.Copy(buf, ctx.Request.Body); err != nil {
|
||||
return rerr.Error(ctx, rerr.ErrInternal(err))
|
||||
}
|
||||
|
||||
if hash, _, err = model.SHA256(bytes.NewReader(buf.Bytes())); err != nil {
|
||||
return rerr.Error(ctx, rerr.ErrInternal(err))
|
||||
}
|
||||
|
||||
digest := hash.String()
|
||||
|
||||
mf := model.RepoSimpleManifest{
|
||||
Blob: buf.Bytes(),
|
||||
ContentType: ctx.Get("Content-Type"),
|
||||
}
|
||||
|
||||
@ -104,7 +115,7 @@ func handleManifest(ctx *nf.Ctx, m interfaces.ManifestHandler) error {
|
||||
WithField("target", target).
|
||||
WithField("digest", digest).
|
||||
WithField("content-type", ctx.Get("Content-Type")).
|
||||
WithField("content", b.String()).
|
||||
WithField("content", buf.String()).
|
||||
Debug()
|
||||
|
||||
if err := m.Put(ctx.Request.Context(), repo, target, digest, &mf); err != nil {
|
||||
|
307
internal/handler/repo.go
Normal file
307
internal/handler/repo.go
Normal file
@ -0,0 +1,307 @@
|
||||
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"
|
||||
"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()
|
||||
|
||||
if des, err = puller.Get(c.Request.Context(), 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()
|
||||
}
|
||||
}
|
@ -42,7 +42,11 @@ func handleTags(ctx *nf.Ctx, m interfaces.ManifestHandler) error {
|
||||
})
|
||||
}
|
||||
|
||||
if list, re = m.Tags(ctx.Request.Context(), repo, req.N, req.Last); err != nil {
|
||||
if req.N <= 0 {
|
||||
req.N = 100
|
||||
}
|
||||
|
||||
if list, re = m.Tags(ctx.Request.Context(), repo, req.N, req.Last, ""); err != nil {
|
||||
return rerr.Error(ctx, re)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user