code.gitea.io/gitea@v1.22.3/models/packages/container/search.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package container 5 6 import ( 7 "context" 8 "strings" 9 "time" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/models/packages" 13 user_model "code.gitea.io/gitea/models/user" 14 container_module "code.gitea.io/gitea/modules/packages/container" 15 "code.gitea.io/gitea/modules/util" 16 17 "xorm.io/builder" 18 ) 19 20 var ErrContainerBlobNotExist = util.NewNotExistErrorf("container blob does not exist") 21 22 type BlobSearchOptions struct { 23 OwnerID int64 24 Image string 25 Digest string 26 Tag string 27 IsManifest bool 28 Repository string 29 } 30 31 func (opts *BlobSearchOptions) toConds() builder.Cond { 32 var cond builder.Cond = builder.Eq{ 33 "package.type": packages.TypeContainer, 34 } 35 36 if opts.OwnerID != 0 { 37 cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID}) 38 } 39 if opts.Image != "" { 40 cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Image)}) 41 } 42 if opts.Tag != "" { 43 cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)}) 44 } 45 if opts.IsManifest { 46 cond = cond.And(builder.Eq{"package_file.lower_name": ManifestFilename}) 47 } 48 if opts.Digest != "" { 49 var propsCond builder.Cond = builder.Eq{ 50 "package_property.ref_type": packages.PropertyTypeFile, 51 "package_property.name": container_module.PropertyDigest, 52 "package_property.value": opts.Digest, 53 } 54 55 cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property"))) 56 } 57 if opts.Repository != "" { 58 var propsCond builder.Cond = builder.Eq{ 59 "package_property.ref_type": packages.PropertyTypePackage, 60 "package_property.name": container_module.PropertyRepository, 61 "package_property.value": opts.Repository, 62 } 63 64 cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property"))) 65 } 66 67 return cond 68 } 69 70 // GetContainerBlob gets the container blob matching the blob search options 71 // If multiple matching blobs are found (manifests with the same digest) the first (according to the database) is selected. 72 func GetContainerBlob(ctx context.Context, opts *BlobSearchOptions) (*packages.PackageFileDescriptor, error) { 73 pfds, err := getContainerBlobsLimit(ctx, opts, 1) 74 if err != nil { 75 return nil, err 76 } 77 if len(pfds) != 1 { 78 return nil, ErrContainerBlobNotExist 79 } 80 81 return pfds[0], nil 82 } 83 84 // GetContainerBlobs gets the container blobs matching the blob search options 85 func GetContainerBlobs(ctx context.Context, opts *BlobSearchOptions) ([]*packages.PackageFileDescriptor, error) { 86 return getContainerBlobsLimit(ctx, opts, 0) 87 } 88 89 func getContainerBlobsLimit(ctx context.Context, opts *BlobSearchOptions, limit int) ([]*packages.PackageFileDescriptor, error) { 90 pfs := make([]*packages.PackageFile, 0, limit) 91 sess := db.GetEngine(ctx). 92 Join("INNER", "package_version", "package_version.id = package_file.version_id"). 93 Join("INNER", "package", "package.id = package_version.package_id"). 94 Where(opts.toConds()) 95 96 if limit > 0 { 97 sess = sess.Limit(limit) 98 } 99 100 if err := sess.Find(&pfs); err != nil { 101 return nil, err 102 } 103 104 return packages.GetPackageFileDescriptors(ctx, pfs) 105 } 106 107 // GetManifestVersions gets all package versions representing the matching manifest 108 func GetManifestVersions(ctx context.Context, opts *BlobSearchOptions) ([]*packages.PackageVersion, error) { 109 cond := opts.toConds().And(builder.Eq{"package_version.is_internal": false}) 110 111 pvs := make([]*packages.PackageVersion, 0, 10) 112 return pvs, db.GetEngine(ctx). 113 Join("INNER", "package", "package.id = package_version.package_id"). 114 Join("INNER", "package_file", "package_file.version_id = package_version.id"). 115 Where(cond). 116 Find(&pvs) 117 } 118 119 // GetImageTags gets a sorted list of the tags of an image 120 // The result is suitable for the api call. 121 func GetImageTags(ctx context.Context, ownerID int64, image string, n int, last string) ([]string, error) { 122 // Short circuit: n == 0 should return an empty list 123 if n == 0 { 124 return []string{}, nil 125 } 126 127 var cond builder.Cond = builder.Eq{ 128 "package.type": packages.TypeContainer, 129 "package.owner_id": ownerID, 130 "package.lower_name": strings.ToLower(image), 131 "package_version.is_internal": false, 132 } 133 134 var propsCond builder.Cond = builder.Eq{ 135 "package_property.ref_type": packages.PropertyTypeVersion, 136 "package_property.name": container_module.PropertyManifestTagged, 137 } 138 139 cond = cond.And(builder.In("package_version.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property"))) 140 141 if last != "" { 142 cond = cond.And(builder.Gt{"package_version.lower_version": strings.ToLower(last)}) 143 } 144 145 sess := db.GetEngine(ctx). 146 Table("package_version"). 147 Select("package_version.lower_version"). 148 Join("INNER", "package", "package.id = package_version.package_id"). 149 Where(cond). 150 Asc("package_version.lower_version") 151 152 var tags []string 153 if n > 0 { 154 sess = sess.Limit(n) 155 156 tags = make([]string, 0, n) 157 } else { 158 tags = make([]string, 0, 10) 159 } 160 161 return tags, sess.Find(&tags) 162 } 163 164 type ImageTagsSearchOptions struct { 165 PackageID int64 166 Query string 167 IsTagged bool 168 Sort packages.VersionSort 169 db.Paginator 170 } 171 172 func (opts *ImageTagsSearchOptions) toConds() builder.Cond { 173 var cond builder.Cond = builder.Eq{ 174 "package.type": packages.TypeContainer, 175 "package.id": opts.PackageID, 176 "package_version.is_internal": false, 177 } 178 179 if opts.Query != "" { 180 cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Query)}) 181 } 182 183 var propsCond builder.Cond = builder.Eq{ 184 "package_property.ref_type": packages.PropertyTypeVersion, 185 "package_property.name": container_module.PropertyManifestTagged, 186 } 187 188 in := builder.In("package_version.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")) 189 190 if opts.IsTagged { 191 cond = cond.And(in) 192 } else { 193 cond = cond.And(builder.Not{in}) 194 } 195 196 return cond 197 } 198 199 func (opts *ImageTagsSearchOptions) configureOrderBy(e db.Engine) { 200 switch opts.Sort { 201 case packages.SortVersionDesc: 202 e.Desc("package_version.version") 203 case packages.SortVersionAsc: 204 e.Asc("package_version.version") 205 case packages.SortCreatedAsc: 206 e.Asc("package_version.created_unix") 207 default: 208 e.Desc("package_version.created_unix") 209 } 210 211 // Sort by id for stable order with duplicates in the other field 212 e.Asc("package_version.id") 213 } 214 215 // SearchImageTags gets a sorted list of the tags of an image 216 func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) { 217 sess := db.GetEngine(ctx). 218 Join("INNER", "package", "package.id = package_version.package_id"). 219 Where(opts.toConds()) 220 221 opts.configureOrderBy(sess) 222 223 if opts.Paginator != nil { 224 sess = db.SetSessionPagination(sess, opts) 225 } 226 227 pvs := make([]*packages.PackageVersion, 0, 10) 228 count, err := sess.FindAndCount(&pvs) 229 return pvs, count, err 230 } 231 232 // SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified 233 func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) { 234 var cond builder.Cond = builder.Eq{ 235 "package_version.is_internal": true, 236 "package_version.lower_version": UploadVersion, 237 "package.type": packages.TypeContainer, 238 } 239 cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()}) 240 241 var pfs []*packages.PackageFile 242 return pfs, db.GetEngine(ctx). 243 Join("INNER", "package_version", "package_version.id = package_file.version_id"). 244 Join("INNER", "package", "package.id = package_version.package_id"). 245 Where(cond). 246 Find(&pfs) 247 } 248 249 // GetRepositories gets a sorted list of all repositories 250 func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) { 251 var cond builder.Cond = builder.Eq{ 252 "package.type": packages.TypeContainer, 253 "package_property.ref_type": packages.PropertyTypePackage, 254 "package_property.name": container_module.PropertyRepository, 255 } 256 257 cond = cond.And(builder.Exists( 258 builder. 259 Select("package_version.id"). 260 Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))). 261 From("package_version"), 262 )) 263 264 if last != "" { 265 cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)}) 266 } 267 268 if actor.IsGhost() { 269 actor = nil 270 } 271 272 cond = cond.And(user_model.BuildCanSeeUserCondition(actor)) 273 274 sess := db.GetEngine(ctx). 275 Table("package"). 276 Select("package_property.value"). 277 Join("INNER", "user", "`user`.id = package.owner_id"). 278 Join("INNER", "package_property", "package_property.ref_id = package.id"). 279 Where(cond). 280 Asc("package_property.value"). 281 Limit(n) 282 283 repositories := make([]string, 0, n) 284 return repositories, sess.Find(&repositories) 285 }