2022-03-30 10:42:47 +02:00
// Copyright 2022 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2022-03-30 10:42:47 +02:00
package container
import (
"context"
"encoding/hex"
2022-11-25 06:47:46 +01:00
"errors"
2022-03-30 10:42:47 +02:00
"fmt"
2022-11-25 06:47:46 +01:00
"os"
2022-03-30 10:42:47 +02:00
"strings"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
2024-10-11 15:37:49 +08:00
"code.gitea.io/gitea/modules/globallock"
2022-03-30 10:42:47 +02:00
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
container_module "code.gitea.io/gitea/modules/packages/container"
2022-11-25 06:47:46 +01:00
"code.gitea.io/gitea/modules/util"
2022-03-30 10:42:47 +02:00
packages_service "code.gitea.io/gitea/services/packages"
2025-06-16 16:27:01 +08:00
"github.com/opencontainers/go-digest"
2022-03-30 10:42:47 +02:00
)
// 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
2026-02-03 17:08:20 +01:00
// There will be concurrent uploading for the same blob, so it needs a global lock per blob hash
func saveAsPackageBlob ( ctx context . Context , hsr packages_module . HashedSizeReader , pci * packages_service . PackageCreationInfo ) ( * packages_model . PackageBlob , error ) { //nolint:unparam //returned PackageBlob is never used
2022-03-30 10:42:47 +02:00
pb := packages_service . NewPackageBlob ( hsr )
2026-02-03 17:08:20 +01:00
err := globallock . LockAndDo ( ctx , "container-blob:" + pb . HashSHA256 , func ( ctx context . Context ) error {
var err error
pb , err = saveAsPackageBlobInternal ( ctx , hsr , pci , pb )
return err
} )
return pb , err
}
2022-03-30 10:42:47 +02:00
2026-02-03 17:08:20 +01:00
func saveAsPackageBlobInternal ( ctx context . Context , hsr packages_module . HashedSizeReader , pci * packages_service . PackageCreationInfo , pb * packages_model . PackageBlob ) ( * packages_model . PackageBlob , error ) {
2022-03-30 10:42:47 +02:00
exists := false
contentStore := packages_module . NewContentStore ( )
2023-07-24 05:47:27 +02:00
uploadVersion , err := getOrCreateUploadVersion ( ctx , & pci . PackageInfo )
2023-01-16 23:35:48 +01:00
if err != nil {
return nil , err
}
2023-07-24 05:47:27 +02:00
err = db . WithTx ( ctx , func ( ctx context . Context ) error {
2023-05-02 18:31:35 +02:00
if err := packages_service . CheckSizeQuotaExceeded ( ctx , pci . Creator , pci . Owner , packages_model . TypeContainer , hsr . Size ( ) ) ; err != nil {
return err
}
2023-01-16 23:35:48 +01:00
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 {
2026-02-03 17:08:20 +01:00
if ! exists && pb != nil { // pb can be nil if GetOrInsertBlob failed
2023-01-16 23:35:48 +01:00
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
2023-07-24 05:47:27 +02:00
func mountBlob ( ctx context . Context , pi * packages_service . PackageInfo , pb * packages_model . PackageBlob ) error {
uploadVersion , err := getOrCreateUploadVersion ( ctx , pi )
2023-01-16 23:35:48 +01:00
if err != nil {
return err
}
2023-07-24 05:47:27 +02:00
return db . WithTx ( ctx , func ( ctx context . Context ) error {
2023-01-16 23:35:48 +01:00
return createFileForBlob ( ctx , uploadVersion , pb )
} )
}
2025-06-22 19:22:51 +08:00
func containerGlobalLockKey ( piOwnerID int64 , piName , usage string ) string {
return fmt . Sprintf ( "pkg_%d_container_%s_%s" , piOwnerID , strings . ToLower ( piName ) , usage )
2024-10-11 15:37:49 +08:00
}
2023-07-24 05:47:27 +02:00
func getOrCreateUploadVersion ( ctx context . Context , pi * packages_service . PackageInfo ) ( * packages_model . PackageVersion , error ) {
2025-06-22 19:22:51 +08:00
releaser , err := globallock . Lock ( ctx , containerGlobalLockKey ( pi . Owner . ID , pi . Name , "package" ) )
2024-10-11 15:37:49 +08:00
if err != nil {
return nil , err
}
defer releaser ( )
2025-06-29 09:20:38 +08:00
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * packages_model . PackageVersion , error ) {
2022-07-28 05:59:39 +02:00
created := true
2022-03-30 10:42:47 +02:00
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 {
2025-01-06 22:45:20 +08:00
if ! errors . Is ( err , packages_model . ErrDuplicatePackage ) {
2022-03-30 10:42:47 +02:00
log . Error ( "Error inserting package: %v" , err )
2025-06-29 09:20:38 +08:00
return nil , err
2022-03-30 10:42:47 +02:00
}
2025-01-06 22:45:20 +08:00
created = false
2022-03-30 10:42:47 +02:00
}
2022-07-28 05:59:39 +02:00
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 )
2025-06-29 09:20:38 +08:00
return nil , err
2022-07-28 05:59:39 +02:00
}
}
2022-03-30 10:42:47 +02:00
pv := & packages_model . PackageVersion {
PackageID : p . ID ,
CreatorID : pi . Owner . ID ,
2025-06-18 01:42:00 +08:00
Version : container_module . UploadVersion ,
LowerVersion : container_module . UploadVersion ,
2022-03-30 10:42:47 +02:00
IsInternal : true ,
MetadataJSON : "null" ,
}
if pv , err = packages_model . GetOrInsertVersion ( ctx , pv ) ; err != nil {
2025-01-06 22:45:20 +08:00
if ! errors . Is ( err , packages_model . ErrDuplicatePackageVersion ) {
2022-03-30 10:42:47 +02:00
log . Error ( "Error inserting package: %v" , err )
2025-06-29 09:20:38 +08:00
return nil , err
2022-03-30 10:42:47 +02:00
}
}
2025-06-29 09:20:38 +08:00
return pv , nil
2022-11-25 06:47:46 +01:00
} )
2023-01-16 23:35:48 +01:00
}
2022-03-30 10:42:47 +02:00
2023-01-16 23:35:48 +01:00
func createFileForBlob ( ctx context . Context , pv * packages_model . PackageVersion , pb * packages_model . PackageBlob ) error {
2025-04-01 12:14:01 +02:00
filename := strings . ToLower ( "sha256_" + pb . HashSHA256 )
2022-03-30 10:42:47 +02:00
2023-01-16 23:35:48 +01:00
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 {
2025-01-06 22:45:20 +08:00
if errors . Is ( err , packages_model . ErrDuplicatePackageFile ) {
2023-01-16 23:35:48 +01:00
return nil
2022-03-30 10:42:47 +02:00
}
2023-01-16 23:35:48 +01:00
log . Error ( "Error inserting package file: %v" , err )
return err
}
2022-03-30 10:42:47 +02:00
2023-01-16 23:35:48 +01:00
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
2022-03-30 10:42:47 +02:00
}
2023-01-16 23:35:48 +01:00
return nil
2022-03-30 10:42:47 +02:00
}
2025-06-16 16:27:01 +08:00
func deleteBlob ( ctx context . Context , ownerID int64 , image string , digest digest . Digest ) error {
2025-06-22 19:22:51 +08:00
releaser , err := globallock . Lock ( ctx , containerGlobalLockKey ( ownerID , image , "blob" ) )
2024-10-11 15:37:49 +08:00
if err != nil {
return err
}
defer releaser ( )
2023-07-24 05:47:27 +02:00
return db . WithTx ( ctx , func ( ctx context . Context ) error {
2022-03-30 10:42:47 +02:00
pfds , err := container_model . GetContainerBlobs ( ctx , & container_model . BlobSearchOptions {
OwnerID : ownerID ,
Image : image ,
2025-06-16 16:27:01 +08:00
Digest : string ( digest ) ,
2022-03-30 10:42:47 +02:00
} )
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
}