loveuer 6e866b83e4 feat: as docker mirror registry
feat: add global proxy config
upgrade: upgrade front(angular) to 19
chore: deployment staff
  1. Dockerfile: build frontend, backend, and run in nginx base image
2024-12-23 22:46:34 -08:00

308 lines
7.2 KiB
Go

package handler
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"nf-repo/internal/interfaces"
"nf-repo/internal/model"
"nf-repo/internal/opt"
"nf-repo/internal/tool/r"
"nf-repo/internal/tool/rerr"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/resp"
)
func RepoSettings(c *nf.Ctx) error {
return resp.Resp200(c, nf.Map{
"repo_name": opt.Cfg.RepoName,
})
}
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 resp.Resp400(c, err.Error())
}
if req.N == 0 {
req.N = 20
}
if req.N > 1000 {
return resp.Resp400(c, "limit invalid: too big")
}
if catalog, re = mh.Catalog(c.Request.Context(), req.N, req.Last, req.Keyword); re != nil {
return resp.Resp(c, uint32(re.Status), "", re.Code, re.Message)
}
return resp.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.Cfg.RepoName)
repo = strings.Trim(repo, "/")
if req.Source == "" {
return r.Resp400(c, "source invalid")
}
log.Debug("ProxyDownloadImage: req = %#v, repo = %s", *req, repo)
if opt.Proxy != nil {
transport.Proxy = http.ProxyURL(opt.Proxy)
}
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()
}
}