You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

search.go 8.6KB

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