forked from kevadesu/forgejo
Co-authored-by: Aleksandr Gamzin alexgamz1119@gmail.com Adds support for the Apt-Rpm registry of the Alt Lunux distribution. Alt Linux uses RPM packages to store and distribute software to its users. But the logic of the Alt Linux package registry is different from the Red Hat package registry. I have added support for the Alt Linux package registry. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [x] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Co-authored-by: Aleksandr Gamzin <gamzin@altlinux.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6351 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Alex619829 <alex619829@noreply.codeberg.org> Co-committed-by: Alex619829 <alex619829@noreply.codeberg.org>
202 lines
6 KiB
Go
202 lines
6 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package container
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
packages_model "code.gitea.io/gitea/models/packages"
|
|
container_model "code.gitea.io/gitea/models/packages/container"
|
|
"code.gitea.io/gitea/modules/log"
|
|
packages_module "code.gitea.io/gitea/modules/packages"
|
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
|
"code.gitea.io/gitea/modules/util"
|
|
packages_service "code.gitea.io/gitea/services/packages"
|
|
)
|
|
|
|
var uploadVersionMutex sync.Mutex
|
|
|
|
// saveAsPackageBlob creates a package blob from an upload
|
|
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
|
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
|
|
pb := packages_service.NewPackageBlob(hsr)
|
|
|
|
exists := false
|
|
|
|
contentStore := packages_module.NewContentStore()
|
|
|
|
uploadVersion, err := getOrCreateUploadVersion(ctx, &pci.PackageInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
|
if err := packages_service.CheckSizeQuotaExceeded(ctx, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil {
|
|
return err
|
|
}
|
|
|
|
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
|
if err != nil {
|
|
log.Error("Error inserting package blob: %v", err)
|
|
return err
|
|
}
|
|
// FIXME: Workaround to be removed in v1.20
|
|
// https://github.com/go-gitea/gitea/issues/19586
|
|
if exists {
|
|
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
|
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
|
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
|
exists = false
|
|
}
|
|
}
|
|
if !exists {
|
|
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
|
log.Error("Error saving package blob in content store: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return createFileForBlob(ctx, uploadVersion, pb)
|
|
})
|
|
if err != nil {
|
|
if !exists {
|
|
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
|
log.Error("Error deleting package blob from content store: %v", err)
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return pb, nil
|
|
}
|
|
|
|
// mountBlob mounts the specific blob to a different package
|
|
func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
|
|
uploadVersion, err := getOrCreateUploadVersion(ctx, pi)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
return createFileForBlob(ctx, uploadVersion, pb)
|
|
})
|
|
}
|
|
|
|
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
|
var uploadVersion *packages_model.PackageVersion
|
|
|
|
// FIXME: Replace usage of mutex with database transaction
|
|
// https://github.com/go-gitea/gitea/pull/21862
|
|
uploadVersionMutex.Lock()
|
|
err := db.WithTx(ctx, func(ctx context.Context) error {
|
|
created := true
|
|
p := &packages_model.Package{
|
|
OwnerID: pi.Owner.ID,
|
|
Type: packages_model.TypeContainer,
|
|
Name: strings.ToLower(pi.Name),
|
|
LowerName: strings.ToLower(pi.Name),
|
|
}
|
|
var err error
|
|
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
|
if err == packages_model.ErrDuplicatePackage {
|
|
created = false
|
|
} else {
|
|
log.Error("Error inserting package: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if created {
|
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
|
|
log.Error("Error setting package property: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
pv := &packages_model.PackageVersion{
|
|
PackageID: p.ID,
|
|
CreatorID: pi.Owner.ID,
|
|
Version: container_model.UploadVersion,
|
|
LowerVersion: container_model.UploadVersion,
|
|
IsInternal: true,
|
|
MetadataJSON: "null",
|
|
}
|
|
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
|
|
if err != packages_model.ErrDuplicatePackageVersion {
|
|
log.Error("Error inserting package: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
uploadVersion = pv
|
|
|
|
return nil
|
|
})
|
|
uploadVersionMutex.Unlock()
|
|
|
|
return uploadVersion, err
|
|
}
|
|
|
|
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
|
|
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
|
|
|
pf := &packages_model.PackageFile{
|
|
VersionID: pv.ID,
|
|
BlobID: pb.ID,
|
|
Name: filename,
|
|
LowerName: filename,
|
|
CompositeKey: packages_model.EmptyFileKey,
|
|
}
|
|
var err error
|
|
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
|
if err == packages_model.ErrDuplicatePackageFile {
|
|
return nil
|
|
}
|
|
log.Error("Error inserting package file: %v", err)
|
|
return err
|
|
}
|
|
|
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
|
|
log.Error("Error setting package file property: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error {
|
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
|
|
OwnerID: ownerID,
|
|
Image: image,
|
|
Digest: digest,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range pfds {
|
|
if err := packages_service.DeletePackageFile(ctx, file.File); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func digestFromHashSummer(h packages_module.HashSummer) string {
|
|
_, _, hashSHA256, _, _ := h.Sums()
|
|
return "sha256:" + hex.EncodeToString(hashSHA256)
|
|
}
|
|
|
|
func digestFromPackageBlob(pb *packages_model.PackageBlob) string {
|
|
return "sha256:" + pb.HashSHA256
|
|
}
|