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 }