code.gitea.io/gitea@v1.22.3/routers/api/packages/container/blob.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  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	packages_model "code.gitea.io/gitea/models/packages"
    16  	container_model "code.gitea.io/gitea/models/packages/container"
    17  	user_model "code.gitea.io/gitea/models/user"
    18  	"code.gitea.io/gitea/modules/log"
    19  	packages_module "code.gitea.io/gitea/modules/packages"
    20  	container_module "code.gitea.io/gitea/modules/packages/container"
    21  	"code.gitea.io/gitea/modules/sync"
    22  	"code.gitea.io/gitea/modules/util"
    23  	packages_service "code.gitea.io/gitea/services/packages"
    24  )
    25  
    26  // TODO: use clustered lock
    27  var uploadVersionMutex = sync.NewExclusivePool()
    28  
    29  // saveAsPackageBlob creates a package blob from an upload
    30  // The uploaded blob gets stored in a special upload version to link them to the package/image
    31  func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
    32  	pkgPath := pci.PackageInfo.Owner.LowerName + "/" + pci.PackageInfo.Name
    33  	uploadVersionMutex.CheckIn(pkgPath)
    34  	defer uploadVersionMutex.CheckOut(pkgPath)
    35  
    36  	pb := packages_service.NewPackageBlob(hsr)
    37  
    38  	exists := false
    39  
    40  	contentStore := packages_module.NewContentStore()
    41  
    42  	uploadVersion, err := getOrCreateUploadVersion(ctx, &pci.PackageInfo)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	err = db.WithTx(ctx, func(ctx context.Context) error {
    48  		if err := packages_service.CheckSizeQuotaExceeded(ctx, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil {
    49  			return err
    50  		}
    51  
    52  		pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
    53  		if err != nil {
    54  			log.Error("Error inserting package blob: %v", err)
    55  			return err
    56  		}
    57  		// FIXME: Workaround to be removed in v1.20
    58  		// https://github.com/go-gitea/gitea/issues/19586
    59  		if exists {
    60  			err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
    61  			if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
    62  				log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
    63  				exists = false
    64  			}
    65  		}
    66  		if !exists {
    67  			if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
    68  				log.Error("Error saving package blob in content store: %v", err)
    69  				return err
    70  			}
    71  		}
    72  
    73  		return createFileForBlob(ctx, uploadVersion, pb)
    74  	})
    75  	if err != nil {
    76  		if !exists {
    77  			if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
    78  				log.Error("Error deleting package blob from content store: %v", err)
    79  			}
    80  		}
    81  		return nil, err
    82  	}
    83  
    84  	return pb, nil
    85  }
    86  
    87  // mountBlob mounts the specific blob to a different package
    88  func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
    89  	pkgPath := pi.Owner.LowerName + "/" + pi.Name
    90  	uploadVersionMutex.CheckIn(pkgPath)
    91  	defer uploadVersionMutex.CheckOut(pkgPath)
    92  
    93  	uploadVersion, err := getOrCreateUploadVersion(ctx, pi)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	return db.WithTx(ctx, func(ctx context.Context) error {
    99  		return createFileForBlob(ctx, uploadVersion, pb)
   100  	})
   101  }
   102  
   103  func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
   104  	var uploadVersion *packages_model.PackageVersion
   105  
   106  	err := db.WithTx(ctx, func(ctx context.Context) error {
   107  		created := true
   108  		p := &packages_model.Package{
   109  			OwnerID:   pi.Owner.ID,
   110  			Type:      packages_model.TypeContainer,
   111  			Name:      strings.ToLower(pi.Name),
   112  			LowerName: strings.ToLower(pi.Name),
   113  		}
   114  		var err error
   115  		if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
   116  			if err == packages_model.ErrDuplicatePackage {
   117  				created = false
   118  			} else {
   119  				log.Error("Error inserting package: %v", err)
   120  				return err
   121  			}
   122  		}
   123  
   124  		if created {
   125  			if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
   126  				log.Error("Error setting package property: %v", err)
   127  				return err
   128  			}
   129  		}
   130  
   131  		pv := &packages_model.PackageVersion{
   132  			PackageID:    p.ID,
   133  			CreatorID:    pi.Owner.ID,
   134  			Version:      container_model.UploadVersion,
   135  			LowerVersion: container_model.UploadVersion,
   136  			IsInternal:   true,
   137  			MetadataJSON: "null",
   138  		}
   139  		if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
   140  			if err != packages_model.ErrDuplicatePackageVersion {
   141  				log.Error("Error inserting package: %v", err)
   142  				return err
   143  			}
   144  		}
   145  
   146  		uploadVersion = pv
   147  
   148  		return nil
   149  	})
   150  
   151  	return uploadVersion, err
   152  }
   153  
   154  func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
   155  	filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
   156  
   157  	pf := &packages_model.PackageFile{
   158  		VersionID:    pv.ID,
   159  		BlobID:       pb.ID,
   160  		Name:         filename,
   161  		LowerName:    filename,
   162  		CompositeKey: packages_model.EmptyFileKey,
   163  	}
   164  	var err error
   165  	if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
   166  		if err == packages_model.ErrDuplicatePackageFile {
   167  			return nil
   168  		}
   169  		log.Error("Error inserting package file: %v", err)
   170  		return err
   171  	}
   172  
   173  	if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
   174  		log.Error("Error setting package file property: %v", err)
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func deleteBlob(ctx context.Context, owner *user_model.User, image, digest string) error {
   182  	pkgPath := owner.LowerName + "/" + image
   183  	uploadVersionMutex.CheckIn(pkgPath)
   184  	defer uploadVersionMutex.CheckOut(pkgPath)
   185  
   186  	return db.WithTx(ctx, func(ctx context.Context) error {
   187  		pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
   188  			OwnerID: owner.ID,
   189  			Image:   image,
   190  			Digest:  digest,
   191  		})
   192  		if err != nil {
   193  			return err
   194  		}
   195  
   196  		for _, file := range pfds {
   197  			if err := packages_service.DeletePackageFile(ctx, file.File); err != nil {
   198  				return err
   199  			}
   200  		}
   201  		return nil
   202  	})
   203  }
   204  
   205  func digestFromHashSummer(h packages_module.HashSummer) string {
   206  	_, _, hashSHA256, _ := h.Sums()
   207  	return "sha256:" + hex.EncodeToString(hashSHA256)
   208  }
   209  
   210  func digestFromPackageBlob(pb *packages_model.PackageBlob) string {
   211  	return "sha256:" + pb.HashSHA256
   212  }