feat: mem, local uploader
This commit is contained in:
278
internal/interfaces/manifests/mem.go
Normal file
278
internal/interfaces/manifests/mem.go
Normal file
@ -0,0 +1,278 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"nf-repo/internal/interfaces"
|
||||
"nf-repo/internal/model"
|
||||
"nf-repo/internal/model/types"
|
||||
"nf-repo/internal/util/rerr"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type memManifest struct {
|
||||
sync.RWMutex
|
||||
m map[string]map[string]*model.Manifest
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (m *memManifest) Tags(ctx context.Context, repo string, limit int, last int) (*model.Tag, *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",
|
||||
}
|
||||
}
|
||||
|
||||
var tags []string
|
||||
for tag := range c {
|
||||
if !strings.Contains(tag, "sha256:") {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (m *memManifest) Catelog(ctx context.Context, limit, last int) (*model.Catalog, *rerr.RepositoryError) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
var repos []string
|
||||
countRepos := 0
|
||||
|
||||
// TODO: implement pagination
|
||||
for key := range m.m {
|
||||
if countRepos >= limit {
|
||||
break
|
||||
}
|
||||
countRepos++
|
||||
|
||||
repos = append(repos, key)
|
||||
}
|
||||
|
||||
repositoriesToList := &model.Catalog{
|
||||
Repos: repos,
|
||||
}
|
||||
|
||||
return repositoriesToList, nil
|
||||
}
|
||||
|
||||
func (m *memManifest) Put(ctx context.Context, repo string, target string, digest string, mf *model.Manifest) *rerr.RepositoryError {
|
||||
// If the manifest
|
||||
// list's constituent manifests are already uploaded.
|
||||
// This isn't strictly required by the registry API, but some
|
||||
// registries require this.
|
||||
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.
|
||||
logrus.Warnf("TODO: Check blobs for %q", desc.Digest)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if _, ok := m.m[repo]; !ok {
|
||||
m.m[repo] = make(map[string]*model.Manifest, 2)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &memManifest{m: make(map[string]map[string]*model.Manifest)}
|
||||
}
|
Reference in New Issue
Block a user