You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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