2024-04-10 22:10:09 +08:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-12-23 00:07:44 -08:00
|
|
|
"encoding/json"
|
2024-04-10 22:10:09 +08:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2024-12-23 00:07:44 -08:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"nf-repo/internal/controller"
|
2024-04-10 22:10:09 +08:00
|
|
|
"nf-repo/internal/interfaces"
|
|
|
|
"nf-repo/internal/model"
|
2024-12-23 00:07:44 -08:00
|
|
|
"nf-repo/internal/tool/rerr"
|
|
|
|
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
|
|
"github.com/loveuer/nf"
|
|
|
|
"github.com/loveuer/nf/nft/log"
|
2024-04-10 22:10:09 +08:00
|
|
|
)
|
|
|
|
|
2024-12-23 00:07:44 -08:00
|
|
|
func handleManifest(ctx *nf.Ctx, m interfaces.ManifestHandler, blobHandler interfaces.BlobHandler) error {
|
2024-04-10 22:10:09 +08:00
|
|
|
elem := strings.Split(ctx.Path(), "/")
|
|
|
|
elem = elem[1:]
|
|
|
|
target := elem[len(elem)-1]
|
|
|
|
repo := strings.Join(elem[1:len(elem)-2], "/")
|
|
|
|
|
2024-12-23 00:07:44 -08:00
|
|
|
log.Debug("handleManifest: path = %s, method = %s, repo = %s, target = %s",
|
|
|
|
ctx.Path(), ctx.Method(), repo, target)
|
2024-04-10 22:10:09 +08:00
|
|
|
|
|
|
|
switch ctx.Method() {
|
|
|
|
case http.MethodGet:
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
reader io.ReadCloser
|
|
|
|
contentType string
|
|
|
|
re *rerr.RepositoryError
|
|
|
|
bs []byte
|
|
|
|
)
|
|
|
|
|
|
|
|
if reader, contentType, re = m.Get(ctx.Request.Context(), repo, target); re != nil {
|
2024-12-23 00:07:44 -08:00
|
|
|
if re.Status == 404 {
|
|
|
|
log.Debug("handleManifest: repo not found, start pull, repo = %s, tag = %s", repo, target)
|
|
|
|
var manifest *v1.Manifest
|
|
|
|
if manifest, err = controller.PullRepo(ctx.Context(), repo, target, "", m, blobHandler); err != nil {
|
|
|
|
return rerr.Error(ctx, &rerr.RepositoryError{
|
|
|
|
Status: http.StatusInternalServerError,
|
|
|
|
Code: "INTERNAL_SERVER_ERROR",
|
|
|
|
Message: err.Error(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
contentType = string(manifest.MediaType)
|
|
|
|
bs, _ = json.Marshal(manifest)
|
|
|
|
goto RETURN_GET
|
|
|
|
}
|
|
|
|
|
2024-04-10 22:10:09 +08:00
|
|
|
return rerr.Error(ctx, re)
|
|
|
|
}
|
|
|
|
|
|
|
|
if bs, err = io.ReadAll(reader); err != nil {
|
|
|
|
return rerr.Error(ctx, &rerr.RepositoryError{
|
|
|
|
Status: http.StatusInternalServerError,
|
|
|
|
Code: "INTERNAL_SERVER_ERROR",
|
|
|
|
Message: err.Error(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-12-23 00:07:44 -08:00
|
|
|
RETURN_GET:
|
|
|
|
|
2024-04-10 22:10:09 +08:00
|
|
|
h, _, _ := model.SHA256(bytes.NewReader(bs))
|
|
|
|
ctx.Set("Docker-Content-Digest", h.String())
|
|
|
|
ctx.Set("Content-Type", contentType)
|
|
|
|
ctx.Set("Content-Length", fmt.Sprint(len(bs)))
|
|
|
|
ctx.Status(http.StatusOK)
|
|
|
|
_, err = ctx.Write(bs)
|
|
|
|
return err
|
|
|
|
|
|
|
|
case http.MethodHead:
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
reader io.ReadCloser
|
|
|
|
contentType string
|
|
|
|
re *rerr.RepositoryError
|
|
|
|
bs []byte
|
|
|
|
)
|
|
|
|
|
|
|
|
if reader, contentType, re = m.Get(ctx.Request.Context(), repo, target); re != nil {
|
|
|
|
return rerr.Error(ctx, re)
|
|
|
|
}
|
|
|
|
|
|
|
|
if bs, err = io.ReadAll(reader); err != nil {
|
|
|
|
return rerr.Error(ctx, &rerr.RepositoryError{
|
|
|
|
Status: http.StatusInternalServerError,
|
|
|
|
Code: "INTERNAL_SERVER_ERROR",
|
|
|
|
Message: err.Error(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
h, _, _ := model.SHA256(bytes.NewReader(bs))
|
|
|
|
ctx.Set("Docker-Content-Digest", h.String())
|
|
|
|
ctx.Set("Content-Type", contentType)
|
|
|
|
ctx.Set("Content-Length", fmt.Sprint(len(bs)))
|
|
|
|
|
|
|
|
return ctx.SendStatus(http.StatusOK)
|
|
|
|
|
|
|
|
case http.MethodPut:
|
2024-04-15 18:02:54 +08:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
buf = &bytes.Buffer{}
|
|
|
|
hash model.Hash
|
|
|
|
)
|
|
|
|
|
|
|
|
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()
|
2024-04-10 22:10:09 +08:00
|
|
|
|
2024-04-15 18:02:54 +08:00
|
|
|
mf := model.RepoSimpleManifest{
|
|
|
|
Blob: buf.Bytes(),
|
2024-04-10 22:10:09 +08:00
|
|
|
ContentType: ctx.Get("Content-Type"),
|
|
|
|
}
|
|
|
|
|
2024-12-23 00:07:44 -08:00
|
|
|
log.Debug("handleManifest: path = %s, method = %s, repo = %s, target = %s, digest = %s, content-type = %s, content = %s",
|
|
|
|
ctx.Path(), ctx.Method(), repo, target, digest, ctx.Get("Content-Type"), buf.String())
|
2024-04-10 22:10:09 +08:00
|
|
|
|
|
|
|
if err := m.Put(ctx.Request.Context(), repo, target, digest, &mf); err != nil {
|
|
|
|
return rerr.Error(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Set("Docker-Content-Digest", digest)
|
|
|
|
return ctx.SendStatus(http.StatusCreated)
|
|
|
|
|
|
|
|
case http.MethodDelete:
|
|
|
|
return ctx.SendStatus(http.StatusAccepted)
|
|
|
|
|
|
|
|
default:
|
|
|
|
return rerr.Error(ctx, &rerr.RepositoryError{
|
|
|
|
Status: http.StatusBadRequest,
|
|
|
|
Code: "METHOD_UNKNOWN",
|
|
|
|
Message: "We don't understand your method + url",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|