292 lines
6.2 KiB
Go
Raw Permalink Normal View History

2024-04-10 22:10:09 +08:00
package manifests
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sort"
"strings"
"sync"
"nf-repo/internal/interfaces"
"nf-repo/internal/model"
"nf-repo/internal/model/types"
"nf-repo/internal/tool/rerr"
"github.com/loveuer/nf/nft/log"
2024-04-10 22:10:09 +08:00
)
type memManifest struct {
sync.RWMutex
2024-04-15 18:02:54 +08:00
m map[string]map[string]*model.RepoSimpleManifest
2024-04-10 22:10:09 +08:00
}
func (m *memManifest) Referrers(ctx context.Context, repo string, target string) (*model.IndexManifest, *rerr.RepositoryError) {
m.RLock()
defer m.RUnlock()
digestToManifestMap, repoExists := m.m[repo]
if !repoExists {
return nil, &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: "Unknown name",
}
}
im := &model.IndexManifest{
SchemaVersion: 2,
MediaType: types.OCIImageIndex,
Manifests: []model.Descriptor{},
}
for digest, manifest := range digestToManifestMap {
h, err := model.NewHash(digest)
if err != nil {
continue
}
var refPointer struct {
Subject *model.Descriptor `json:"subject"`
}
json.Unmarshal(manifest.Blob, &refPointer)
if refPointer.Subject == nil {
continue
}
referenceDigest := refPointer.Subject.Digest
if referenceDigest.String() != target {
continue
}
// At this point, we know the current digest references the target
var imageAsArtifact struct {
Config struct {
MediaType string `json:"mediaType"`
} `json:"config"`
}
json.Unmarshal(manifest.Blob, &imageAsArtifact)
im.Manifests = append(im.Manifests, model.Descriptor{
MediaType: types.MediaType(manifest.ContentType),
Size: int64(len(manifest.Blob)),
Digest: h,
ArtifactType: imageAsArtifact.Config.MediaType,
})
}
return im, nil
}
2024-04-15 18:02:54 +08:00
func (m *memManifest) Tags(ctx context.Context, repo string, limit int, last int, keyword string) (*model.Tag, *rerr.RepositoryError) {
2024-04-10 22:10:09 +08:00
m.RLock()
defer m.RUnlock()
c, ok := m.m[repo]
if !ok {
return nil, &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: "Unknown name",
}
}
var tags []string
for tag := range c {
2024-04-15 18:02:54 +08:00
if strings.Contains(tag, "sha256:") {
continue
}
if keyword == "" {
tags = append(tags, tag)
continue
}
if strings.Contains(tag, keyword) {
2024-04-10 22:10:09 +08:00
tags = append(tags, tag)
}
}
2024-04-15 18:02:54 +08:00
2024-04-10 22:10:09 +08:00
sort.Strings(tags)
// https://github.com/opencontainers/distribution-spec/blob/b505e9cc53ec499edbd9c1be32298388921bb705/detail.md#tags-paginated
// Offset using last query parameter.
//if last := ctx.Query("last"); last != "" {
// for i, t := range tags {
// if t > last {
// tags = tags[i:]
// break
// }
// }
//}
//
//// Limit using n query parameter.
//if ns := ctx.Query("n"); ns != "" {
// if n, err := strconv.Atoi(ns); err != nil {
// return rerr.Error(ctx, &rerr.RepositoryError{
// Status: http.StatusBadRequest,
// Code: "BAD_REQUEST",
// Message: fmt.Sprintf("parsing n: %v", err),
// })
// } else if n < len(tags) {
// tags = tags[:n]
// }
//}
tagsToList := &model.Tag{
Name: repo,
Tags: tags,
}
return tagsToList, nil
}
2024-04-15 18:02:54 +08:00
func (m *memManifest) Catalog(ctx context.Context, limit, last int, keyword string) (*model.Catalog, *rerr.RepositoryError) {
2024-04-10 22:10:09 +08:00
m.RLock()
defer m.RUnlock()
var repos []string
countRepos := 0
// TODO: implement pagination
for key := range m.m {
if countRepos >= limit {
break
}
2024-04-15 18:02:54 +08:00
if keyword != "" && !strings.Contains(key, keyword) {
continue
}
2024-04-10 22:10:09 +08:00
countRepos++
repos = append(repos, key)
}
repositoriesToList := &model.Catalog{
2024-04-15 18:02:54 +08:00
Repositories: repos,
2024-04-10 22:10:09 +08:00
}
return repositoriesToList, nil
}
2024-04-15 18:02:54 +08:00
func (m *memManifest) Put(ctx context.Context, repo string, target string, digest string, mf *model.RepoSimpleManifest) *rerr.RepositoryError {
2024-04-10 22:10:09 +08:00
if types.MediaType(mf.ContentType).IsIndex() {
if err := func() *rerr.RepositoryError {
m.RLock()
defer m.RUnlock()
im, err := model.ParseIndexManifest(bytes.NewReader(mf.Blob))
if err != nil {
return &rerr.RepositoryError{
Status: http.StatusBadRequest,
Code: "MANIFEST_INVALID",
Message: err.Error(),
}
}
for _, desc := range im.Manifests {
if !desc.MediaType.IsDistributable() {
continue
}
if desc.MediaType.IsIndex() || desc.MediaType.IsImage() {
if _, found := m.m[repo][desc.Digest.String()]; !found {
return &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: fmt.Sprintf("Sub-manifest %q not found", desc.Digest),
}
}
} else {
// TODO: Probably want to do an existence check for blobs.
log.Warn("TODO: Check blobs for %q", desc.Digest)
2024-04-10 22:10:09 +08:00
}
}
return nil
}(); err != nil {
return err
}
}
m.Lock()
defer m.Unlock()
if _, ok := m.m[repo]; !ok {
2024-04-15 18:02:54 +08:00
m.m[repo] = make(map[string]*model.RepoSimpleManifest, 2)
2024-04-10 22:10:09 +08:00
}
// Allow future references by target (tag) and immutable digest.
// See https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier.
m.m[repo][digest] = mf
m.m[repo][target] = mf
return nil
}
func (m *memManifest) Delete(ctx context.Context, repo string, target string) *rerr.RepositoryError {
m.Lock()
defer m.Unlock()
if _, ok := m.m[repo]; !ok {
return &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: "Unknown name",
}
}
_, ok := m.m[repo][target]
if !ok {
return &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: "Unknown manifest",
}
}
delete(m.m[repo], target)
if len(m.m[repo]) == 0 {
delete(m.m, repo)
}
return nil
}
func (m *memManifest) Get(ctx context.Context, repo string, target string) (io.ReadCloser, string, *rerr.RepositoryError) {
m.RLock()
defer m.RUnlock()
c, ok := m.m[repo]
if !ok {
return nil, "", &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: "Unknown name",
}
}
f, ok := c[target]
if !ok {
return nil, "", &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: "Unknown manifest",
}
}
reader := io.NopCloser(bytes.NewReader(f.Blob))
return reader, f.ContentType, nil
}
func NewManifestMemHandler() interfaces.ManifestHandler {
2024-04-15 18:02:54 +08:00
return &memManifest{m: make(map[string]map[string]*model.RepoSimpleManifest)}
2024-04-10 22:10:09 +08:00
}