code.gitea.io/gitea@v1.21.7/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  
   212  // SearchImageTags gets a sorted list of the tags of an image
   213  func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) {
   214  	sess := db.GetEngine(ctx).
   215  		Join("INNER", "package", "package.id = package_version.package_id").
   216  		Where(opts.toConds())
   217  
   218  	opts.configureOrderBy(sess)
   219  
   220  	if opts.Paginator != nil {
   221  		sess = db.SetSessionPagination(sess, opts)
   222  	}
   223  
   224  	pvs := make([]*packages.PackageVersion, 0, 10)
   225  	count, err := sess.FindAndCount(&pvs)
   226  	return pvs, count, err
   227  }
   228  
   229  // SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
   230  func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
   231  	var cond builder.Cond = builder.Eq{
   232  		"package_version.is_internal":   true,
   233  		"package_version.lower_version": UploadVersion,
   234  		"package.type":                  packages.TypeContainer,
   235  	}
   236  	cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()})
   237  
   238  	var pfs []*packages.PackageFile
   239  	return pfs, db.GetEngine(ctx).
   240  		Join("INNER", "package_version", "package_version.id = package_file.version_id").
   241  		Join("INNER", "package", "package.id = package_version.package_id").
   242  		Where(cond).
   243  		Find(&pfs)
   244  }
   245  
   246  // GetRepositories gets a sorted list of all repositories
   247  func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
   248  	var cond builder.Cond = builder.Eq{
   249  		"package.type":              packages.TypeContainer,
   250  		"package_property.ref_type": packages.PropertyTypePackage,
   251  		"package_property.name":     container_module.PropertyRepository,
   252  	}
   253  
   254  	cond = cond.And(builder.Exists(
   255  		builder.
   256  			Select("package_version.id").
   257  			Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
   258  			From("package_version"),
   259  	))
   260  
   261  	if last != "" {
   262  		cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
   263  	}
   264  
   265  	if actor.IsGhost() {
   266  		actor = nil
   267  	}
   268  
   269  	cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
   270  
   271  	sess := db.GetEngine(ctx).
   272  		Table("package").
   273  		Select("package_property.value").
   274  		Join("INNER", "user", "`user`.id = package.owner_id").
   275  		Join("INNER", "package_property", "package_property.ref_id = package.id").
   276  		Where(cond).
   277  		Asc("package_property.value").
   278  		Limit(n)
   279  
   280  	repositories := make([]string, 0, n)
   281  	return repositories, sess.Find(&repositories)
   282  }