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