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 }