2022-03-30 11:42:47 +03:00
// Copyright 2022 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-03-30 11:42:47 +03:00
package container
import (
"context"
"strings"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
2022-07-28 06:59:39 +03:00
user_model "code.gitea.io/gitea/models/user"
2022-03-30 11:42:47 +03:00
container_module "code.gitea.io/gitea/modules/packages/container"
2022-12-31 14:49:37 +03:00
"code.gitea.io/gitea/modules/util"
2022-03-30 11:42:47 +03:00
"xorm.io/builder"
)
2022-12-31 14:49:37 +03:00
var ErrContainerBlobNotExist = util . NewNotExistErrorf ( "container blob does not exist" )
2022-03-30 11:42:47 +03:00
type BlobSearchOptions struct {
OwnerID int64
Image string
Digest string
Tag string
IsManifest bool
2023-01-17 01:35:48 +03:00
Repository string
2022-03-30 11:42:47 +03:00
}
func ( opts * BlobSearchOptions ) toConds ( ) builder . Cond {
var cond builder . Cond = builder . Eq {
"package.type" : packages . TypeContainer ,
}
if opts . OwnerID != 0 {
cond = cond . And ( builder . Eq { "package.owner_id" : opts . OwnerID } )
}
if opts . Image != "" {
cond = cond . And ( builder . Eq { "package.lower_name" : strings . ToLower ( opts . Image ) } )
}
if opts . Tag != "" {
cond = cond . And ( builder . Eq { "package_version.lower_version" : strings . ToLower ( opts . Tag ) } )
}
if opts . IsManifest {
cond = cond . And ( builder . Eq { "package_file.lower_name" : ManifestFilename } )
}
if opts . Digest != "" {
var propsCond builder . Cond = builder . Eq {
"package_property.ref_type" : packages . PropertyTypeFile ,
"package_property.name" : container_module . PropertyDigest ,
"package_property.value" : opts . Digest ,
}
cond = cond . And ( builder . In ( "package_file.id" , builder . Select ( "package_property.ref_id" ) . Where ( propsCond ) . From ( "package_property" ) ) )
}
2023-01-17 01:35:48 +03:00
if opts . Repository != "" {
var propsCond builder . Cond = builder . Eq {
"package_property.ref_type" : packages . PropertyTypePackage ,
"package_property.name" : container_module . PropertyRepository ,
"package_property.value" : opts . Repository ,
}
cond = cond . And ( builder . In ( "package.id" , builder . Select ( "package_property.ref_id" ) . Where ( propsCond ) . From ( "package_property" ) ) )
}
2022-03-30 11:42:47 +03:00
return cond
}
// GetContainerBlob gets the container blob matching the blob search options
// If multiple matching blobs are found (manifests with the same digest) the first (according to the database) is selected.
func GetContainerBlob ( ctx context . Context , opts * BlobSearchOptions ) ( * packages . PackageFileDescriptor , error ) {
pfds , err := getContainerBlobsLimit ( ctx , opts , 1 )
if err != nil {
return nil , err
}
if len ( pfds ) != 1 {
return nil , ErrContainerBlobNotExist
}
return pfds [ 0 ] , nil
}
// GetContainerBlobs gets the container blobs matching the blob search options
func GetContainerBlobs ( ctx context . Context , opts * BlobSearchOptions ) ( [ ] * packages . PackageFileDescriptor , error ) {
return getContainerBlobsLimit ( ctx , opts , 0 )
}
func getContainerBlobsLimit ( ctx context . Context , opts * BlobSearchOptions , limit int ) ( [ ] * packages . PackageFileDescriptor , error ) {
pfs := make ( [ ] * packages . PackageFile , 0 , limit )
sess := db . GetEngine ( ctx ) .
Join ( "INNER" , "package_version" , "package_version.id = package_file.version_id" ) .
Join ( "INNER" , "package" , "package.id = package_version.package_id" ) .
Where ( opts . toConds ( ) )
if limit > 0 {
sess = sess . Limit ( limit )
}
if err := sess . Find ( & pfs ) ; err != nil {
return nil , err
}
2023-05-02 19:31:35 +03:00
return packages . GetPackageFileDescriptors ( ctx , pfs )
2022-03-30 11:42:47 +03:00
}
// GetManifestVersions gets all package versions representing the matching manifest
func GetManifestVersions ( ctx context . Context , opts * BlobSearchOptions ) ( [ ] * packages . PackageVersion , error ) {
cond := opts . toConds ( ) . And ( builder . Eq { "package_version.is_internal" : false } )
pvs := make ( [ ] * packages . PackageVersion , 0 , 10 )
return pvs , db . GetEngine ( ctx ) .
Join ( "INNER" , "package" , "package.id = package_version.package_id" ) .
Join ( "INNER" , "package_file" , "package_file.version_id = package_version.id" ) .
Where ( cond ) .
Find ( & pvs )
}
// GetImageTags gets a sorted list of the tags of an image
// The result is suitable for the api call.
func GetImageTags ( ctx context . Context , ownerID int64 , image string , n int , last string ) ( [ ] string , error ) {
// Short circuit: n == 0 should return an empty list
if n == 0 {
return [ ] string { } , nil
}
var cond builder . Cond = builder . Eq {
"package.type" : packages . TypeContainer ,
"package.owner_id" : ownerID ,
"package.lower_name" : strings . ToLower ( image ) ,
"package_version.is_internal" : false ,
}
var propsCond builder . Cond = builder . Eq {
"package_property.ref_type" : packages . PropertyTypeVersion ,
"package_property.name" : container_module . PropertyManifestTagged ,
}
cond = cond . And ( builder . In ( "package_version.id" , builder . Select ( "package_property.ref_id" ) . Where ( propsCond ) . From ( "package_property" ) ) )
if last != "" {
cond = cond . And ( builder . Gt { "package_version.lower_version" : strings . ToLower ( last ) } )
}
sess := db . GetEngine ( ctx ) .
Table ( "package_version" ) .
Select ( "package_version.lower_version" ) .
Join ( "INNER" , "package" , "package.id = package_version.package_id" ) .
Where ( cond ) .
Asc ( "package_version.lower_version" )
var tags [ ] string
if n > 0 {
sess = sess . Limit ( n )
tags = make ( [ ] string , 0 , n )
} else {
tags = make ( [ ] string , 0 , 10 )
}
return tags , sess . Find ( & tags )
}
type ImageTagsSearchOptions struct {
PackageID int64
Query string
IsTagged bool
2022-10-23 04:18:15 +03:00
Sort packages . VersionSort
2022-03-30 11:42:47 +03:00
db . Paginator
}
func ( opts * ImageTagsSearchOptions ) toConds ( ) builder . Cond {
var cond builder . Cond = builder . Eq {
"package.type" : packages . TypeContainer ,
"package.id" : opts . PackageID ,
"package_version.is_internal" : false ,
}
if opts . Query != "" {
cond = cond . And ( builder . Like { "package_version.lower_version" , strings . ToLower ( opts . Query ) } )
}
var propsCond builder . Cond = builder . Eq {
"package_property.ref_type" : packages . PropertyTypeVersion ,
"package_property.name" : container_module . PropertyManifestTagged ,
}
in := builder . In ( "package_version.id" , builder . Select ( "package_property.ref_id" ) . Where ( propsCond ) . From ( "package_property" ) )
if opts . IsTagged {
cond = cond . And ( in )
} else {
cond = cond . And ( builder . Not { in } )
}
return cond
}
2022-10-23 04:18:15 +03:00
func ( opts * ImageTagsSearchOptions ) configureOrderBy ( e db . Engine ) {
switch opts . Sort {
case packages . SortVersionDesc :
e . Desc ( "package_version.version" )
case packages . SortVersionAsc :
e . Asc ( "package_version.version" )
case packages . SortCreatedAsc :
e . Asc ( "package_version.created_unix" )
default :
e . Desc ( "package_version.created_unix" )
}
2023-10-29 17:14:47 +03:00
// Sort by id for stable order with duplicates in the other field
e . Asc ( "package_version.id" )
2022-10-23 04:18:15 +03:00
}
2022-03-30 11:42:47 +03:00
// SearchImageTags gets a sorted list of the tags of an image
func SearchImageTags ( ctx context . Context , opts * ImageTagsSearchOptions ) ( [ ] * packages . PackageVersion , int64 , error ) {
sess := db . GetEngine ( ctx ) .
Join ( "INNER" , "package" , "package.id = package_version.package_id" ) .
2022-10-23 04:18:15 +03:00
Where ( opts . toConds ( ) )
opts . configureOrderBy ( sess )
2022-03-30 11:42:47 +03:00
if opts . Paginator != nil {
sess = db . SetSessionPagination ( sess , opts )
}
pvs := make ( [ ] * packages . PackageVersion , 0 , 10 )
count , err := sess . FindAndCount ( & pvs )
return pvs , count , err
}
2022-07-28 06:59:39 +03:00
// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
2022-03-30 11:42:47 +03:00
func SearchExpiredUploadedBlobs ( ctx context . Context , olderThan time . Duration ) ( [ ] * packages . PackageFile , error ) {
var cond builder . Cond = builder . Eq {
"package_version.is_internal" : true ,
"package_version.lower_version" : UploadVersion ,
"package.type" : packages . TypeContainer ,
}
cond = cond . And ( builder . Lt { "package_file.created_unix" : time . Now ( ) . Add ( - olderThan ) . Unix ( ) } )
var pfs [ ] * packages . PackageFile
return pfs , db . GetEngine ( ctx ) .
Join ( "INNER" , "package_version" , "package_version.id = package_file.version_id" ) .
Join ( "INNER" , "package" , "package.id = package_version.package_id" ) .
Where ( cond ) .
Find ( & pfs )
}
2022-07-28 06:59:39 +03:00
// GetRepositories gets a sorted list of all repositories
func GetRepositories ( ctx context . Context , actor * user_model . User , n int , last string ) ( [ ] string , error ) {
var cond builder . Cond = builder . Eq {
"package.type" : packages . TypeContainer ,
"package_property.ref_type" : packages . PropertyTypePackage ,
"package_property.name" : container_module . PropertyRepository ,
}
cond = cond . And ( builder . Exists (
builder .
Select ( "package_version.id" ) .
Where ( builder . Eq { "package_version.is_internal" : false } . And ( builder . Expr ( "package.id = package_version.package_id" ) ) ) .
From ( "package_version" ) ,
) )
if last != "" {
cond = cond . And ( builder . Gt { "package_property.value" : strings . ToLower ( last ) } )
}
2023-06-13 06:21:11 +03:00
if actor . IsGhost ( ) {
actor = nil
}
2022-07-28 06:59:39 +03:00
cond = cond . And ( user_model . BuildCanSeeUserCondition ( actor ) )
sess := db . GetEngine ( ctx ) .
Table ( "package" ) .
Select ( "package_property.value" ) .
Join ( "INNER" , "user" , "`user`.id = package.owner_id" ) .
Join ( "INNER" , "package_property" , "package_property.ref_id = package.id" ) .
Where ( cond ) .
Asc ( "package_property.value" ) .
Limit ( n )
repositories := make ( [ ] string , 0 , n )
return repositories , sess . Find ( & repositories )
}