333 lines
8.3 KiB
Go
Raw Normal View History

2024-04-10 22:10:09 +08:00
package manifests
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
2024-04-10 22:10:09 +08:00
"nf-repo/internal/interfaces"
"nf-repo/internal/model"
"nf-repo/internal/tool/rerr"
"nf-repo/internal/tool/tools"
"github.com/loveuer/nf/nft/log"
"github.com/samber/lo"
"gorm.io/gorm"
"gorm.io/gorm/clause"
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
type pm struct {
model.PackageManifest
Digest string `json:"digest" gorm:"column:digest"`
Size int `json:"size" gorm:"column:size"`
2024-04-10 22:10:09 +08:00
}
type dbManifests struct {
db interfaces.Database
}
func (m *dbManifests) Get(ctx context.Context, repo string, target string) (io.ReadCloser, string, *rerr.RepositoryError) {
var (
2024-04-15 18:02:54 +08:00
err error
hash model.Hash
pd = new(model.PackageDigest)
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
if hash, err = model.NewHash(target); err == nil {
if err = m.db.TX(tools.Timeout(5)).
Model(&model.PackageDigest{}).
Where("digest", hash.String()).
Take(pd).
Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: fmt.Sprintf("Unknown name: %s@%s", repo, target),
}
}
return nil, "", rerr.ErrInternal(err)
}
return io.NopCloser(bytes.NewReader(pd.Content)), pd.ContentType, nil
2024-04-10 22:10:09 +08:00
}
pm := new(model.PackageManifest)
2024-04-15 18:02:54 +08:00
if err = m.db.TX(tools.Timeout(5)).
Model(&model.PackageManifest{}).
Where("repo", repo).
Where("tag", target).
2024-04-10 22:10:09 +08:00
Take(pm).
Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: fmt.Sprintf("Unknown name: %s@%s", repo, target),
}
}
return nil, "", rerr.ErrInternal(err)
}
2024-04-15 18:02:54 +08:00
if err = m.db.TX(tools.Timeout(5)).
Model(&model.PackageDigest{}).
Where("id", pm.DigestId).
Take(pd).
Error; err != nil {
log.Error("dbManifests: get err, digest = %s, err = %s", pm.Digest, err.Error())
2024-04-15 18:02:54 +08:00
return nil, "", rerr.ErrInternal(err)
}
return io.NopCloser(bytes.NewReader(pd.Content)), pd.ContentType, nil
2024-04-10 22:10:09 +08:00
}
2024-04-15 18:02:54 +08:00
func (m *dbManifests) Put(ctx context.Context, repo string, tag string, digest string, mf *model.RepoSimpleManifest) *rerr.RepositoryError {
2024-04-10 22:10:09 +08:00
var (
err error
2024-04-15 18:02:54 +08:00
pm = &model.PackageManifest{
Repo: repo,
Tag: tag,
}
pd = &model.PackageDigest{
2024-04-10 22:10:09 +08:00
Digest: digest,
ContentType: mf.ContentType,
Content: mf.Blob,
}
2024-04-15 18:02:54 +08:00
blob = new(model.RepoSimpleManifestBlob)
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
if err = json.Unmarshal(mf.Blob, blob); err != nil {
return rerr.ErrInternal(err)
}
pd.Size = blob.CountSize()
if err = m.db.TX(tools.Timeout(5)).
Clauses(clause.Returning{}).
Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "digest"}},
DoUpdates: clause.Set{
{
Column: clause.Column{Name: "content_type"},
Value: mf.ContentType,
},
{
Column: clause.Column{Name: "content"},
Value: mf.Blob,
},
{
Column: clause.Column{Name: "size"},
Value: pd.Size,
},
},
}).
2024-04-15 18:02:54 +08:00
Create(pd).
Error; err != nil {
log.Error("dbManifests: put create err, err = %s", err.Error())
2024-04-15 18:02:54 +08:00
return rerr.ErrInternal(err)
}
pm.DigestId = pd.Id
2024-04-10 22:10:09 +08:00
if err = m.db.TX(tools.Timeout(5)).Create(pm).Error; err == nil {
return nil
}
2024-04-15 18:02:54 +08:00
if err = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}).
Where("(repo = ? AND tag = ?)", repo, tag).
2024-04-10 22:10:09 +08:00
Updates(map[string]any{
2024-04-15 18:02:54 +08:00
"repo": repo,
"tag": tag,
"digest_id": pm.DigestId,
2024-04-10 22:10:09 +08:00
}).
Error; err != nil {
log.Debug("dbManifests: put updates err, err = %s", err.Error())
2024-04-10 22:10:09 +08:00
return rerr.ErrInternal(err)
}
return nil
}
func (m *dbManifests) Delete(ctx context.Context, repo string, target string) *rerr.RepositoryError {
var err error
2024-04-10 22:10:09 +08:00
if err = m.db.TX(tools.Timeout(5)).
Where("repo", repo).
Where("target", target).
2024-04-15 18:02:54 +08:00
Delete(&model.PackageManifest{}).
2024-04-10 22:10:09 +08:00
Error; err != nil {
return rerr.ErrInternal(err)
}
return nil
}
2024-04-15 18:02:54 +08:00
func (m *dbManifests) Catalog(ctx context.Context, limit int, last int, keyword string) (*model.Catalog, *rerr.RepositoryError) {
2024-04-10 22:10:09 +08:00
var (
err error
2024-04-15 18:02:54 +08:00
list = make([]*pm, 0)
tx = m.db.TX(tools.Timeout(5))
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
tx = tx.Model(&model.PackageManifest{}).
Select("\"package_manifests\".*", "\"pd\".\"digest\" as digest", "\"pd\".\"size\" as size")
if keyword != "" {
k := fmt.Sprintf("%%%s%%", keyword)
tx = tx.Where("package_manifests.repo like ?", k)
}
if err = tx.Group("\"package_manifests\".\"repo\"").
Joins("LEFT JOIN package_digests pd on \"pd\".\"id\" = \"package_manifests\".\"digest_id\"").
Order("updated_at DESC, tag = 'latest' DESC").
2024-04-10 22:10:09 +08:00
Offset(last).
Limit(limit).
Find(&list).
Error; err != nil {
return nil, rerr.ErrInternal(err)
}
return &model.Catalog{
2024-04-15 18:02:54 +08:00
Repositories: lo.Map(list, func(item *pm, index int) string {
2024-04-10 22:10:09 +08:00
return item.Repo
}),
2024-04-15 18:02:54 +08:00
Repos: lo.Map(list, func(item *pm, index int) *model.PackageManifest {
item.PackageManifest.Digest = item.Digest
item.PackageManifest.Size = item.Size
return &item.PackageManifest
}),
2024-04-10 22:10:09 +08:00
}, nil
}
2024-04-15 18:02:54 +08:00
func (m *dbManifests) Tags(ctx context.Context, repo string, limit, last int, keyword string) (*model.Tag, *rerr.RepositoryError) {
2024-04-10 22:10:09 +08:00
var (
2024-04-15 18:02:54 +08:00
err error
list = make([]*pm, 0)
tx = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}).Select("\"package_manifests\".*", "\"pd\".\"digest\" as digest")
txc = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}).Select("COUNT(id)")
total int
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
if keyword != "" {
k := fmt.Sprintf("%%%s%%", keyword)
tx = tx.Where("\"package_manifests\".\"tag\" like ?", k)
txc = txc.Where("tag like ?", k)
}
if err = txc.Find(&total).Error; err != nil {
return nil, rerr.ErrInternal(err)
}
if err = tx.
Where("package_manifests.repo", repo).
Joins("LEFT JOIN package_digests pd on \"pd\".\"id\" = \"package_manifests\".\"digest_id\"").
2024-04-10 22:10:09 +08:00
Order("updated_at").
Offset(last).
Limit(limit).
Find(&list).
Error; err != nil {
return nil, rerr.ErrInternal(err)
}
return &model.Tag{
Name: repo,
2024-04-15 18:02:54 +08:00
Tags: lo.Map(list, func(item *pm, index int) string {
return item.Tag
2024-04-10 22:10:09 +08:00
}),
2024-04-15 18:02:54 +08:00
RepoTags: lo.Map(list, func(item *pm, index int) *model.PackageManifest {
item.PackageManifest.Digest = item.Digest
return &item.PackageManifest
}),
Total: total,
2024-04-10 22:10:09 +08:00
}, nil
}
func (m *dbManifests) Referrers(ctx context.Context, repo string, target string) (*model.IndexManifest, *rerr.RepositoryError) {
var (
err error
2024-04-15 18:02:54 +08:00
pm = new(model.PackageManifest)
pd = new(model.PackageDigest)
2024-04-10 22:10:09 +08:00
manifest = &model.IndexManifest{}
2024-04-15 18:02:54 +08:00
tx = m.db.TX(tools.Timeout(5))
hash model.Hash
2024-04-10 22:10:09 +08:00
)
2024-04-15 18:02:54 +08:00
if hash, err = model.NewHash(target); err == nil {
if err = tx.Model(&model.PackageDigest{}).
Where("repo", repo).
Where("digest", hash.String()).
Take(pd).
Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: fmt.Sprintf("Unknown name: %s@%s", repo, target),
}
}
2024-04-10 22:10:09 +08:00
log.Debug("dbManifests: referrers take err, repo = %s, target = %s, err = %s", repo, target, err.Error())
2024-04-15 18:02:54 +08:00
return nil, rerr.ErrInternal(err)
}
} else {
if err = tx.Model(&model.PackageManifest{}).
Where("repo", repo).
Where("tag", target).
Take(pd).
Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, &rerr.RepositoryError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: fmt.Sprintf("Unknown name: %s@%s", repo, target),
}
2024-04-10 22:10:09 +08:00
}
2024-04-15 18:02:54 +08:00
log.Error("dbManifests: referrers take err, repo = %s, target = %s, err = %s", repo, target, err.Error())
2024-04-15 18:02:54 +08:00
return nil, rerr.ErrInternal(err)
2024-04-10 22:10:09 +08:00
}
2024-04-15 18:02:54 +08:00
if err = tx.Model(&model.PackageDigest{}).
Where("digest_id", pm.DigestId).
Take(pd).
Error; err != nil {
2024-04-10 22:10:09 +08:00
log.Error("dbManifests: referrers take err, repo = %s, target = %s, err = %s", repo, target, err.Error())
2024-04-15 18:02:54 +08:00
return nil, rerr.ErrInternal(err)
}
2024-04-10 22:10:09 +08:00
}
2024-04-15 18:02:54 +08:00
if err = json.Unmarshal(pd.Content, manifest); err != nil {
log.Debug("dbManifests: referrers unmarshal err, repo %s, target = %s, err = %s", repo, target, err.Error())
2024-04-10 22:10:09 +08:00
return nil, rerr.ErrInternal(err)
}
return manifest, nil
}
func NewManifestDBHandler(tx interfaces.Database) interfaces.ManifestHandler {
var err error
2024-04-10 22:10:09 +08:00
2024-04-15 18:02:54 +08:00
if err = tx.TX(tools.Timeout(5)).AutoMigrate(
&model.PackageManifest{},
&model.PackageDigest{},
); err != nil {
log.Panic("NewManifestDBHandler: db auto_migrate failed, err = %s", err.Error())
2024-04-10 22:10:09 +08:00
}
return &dbManifests{db: tx}
}