package handler import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "nf-repo/internal/controller" "nf-repo/internal/interfaces" "nf-repo/internal/model" "nf-repo/internal/tool/rerr" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/loveuer/nf" "github.com/loveuer/nf/nft/log" ) func handleManifest(ctx *nf.Ctx, m interfaces.ManifestHandler, blobHandler interfaces.BlobHandler) error { elem := strings.Split(ctx.Path(), "/") elem = elem[1:] target := elem[len(elem)-1] repo := strings.Join(elem[1:len(elem)-2], "/") log.Debug("handleManifest: path = %s, method = %s, repo = %s, target = %s", ctx.Path(), ctx.Method(), repo, target) 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 { 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 } 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(), }) } RETURN_GET: 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: 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() mf := model.RepoSimpleManifest{ Blob: buf.Bytes(), ContentType: ctx.Get("Content-Type"), } 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()) 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", }) } }