gitea源码

manifest.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package container
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "strings"
  11. "time"
  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. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/globallock"
  17. "code.gitea.io/gitea/modules/json"
  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. notify_service "code.gitea.io/gitea/services/notify"
  23. packages_service "code.gitea.io/gitea/services/packages"
  24. container_service "code.gitea.io/gitea/services/packages/container"
  25. "github.com/opencontainers/go-digest"
  26. oci "github.com/opencontainers/image-spec/specs-go/v1"
  27. )
  28. // manifestCreationInfo describes a manifest to create
  29. type manifestCreationInfo struct {
  30. MediaType string
  31. Owner *user_model.User
  32. Creator *user_model.User
  33. Image string
  34. Reference string
  35. IsTagged bool
  36. Properties map[string]string
  37. }
  38. func processManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (string, error) {
  39. var index oci.Index
  40. if err := json.NewDecoder(buf).Decode(&index); err != nil {
  41. return "", err
  42. }
  43. if index.SchemaVersion != 2 {
  44. return "", errUnsupported.WithMessage("Schema version is not supported")
  45. }
  46. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  47. return "", err
  48. }
  49. if !container_module.IsMediaTypeValid(mci.MediaType) {
  50. mci.MediaType = index.MediaType
  51. if !container_module.IsMediaTypeValid(mci.MediaType) {
  52. return "", errManifestInvalid.WithMessage("MediaType not recognized")
  53. }
  54. }
  55. // .../container/manifest.go:453:createManifestBlob() [E] Error inserting package blob: Error 1062 (23000): Duplicate entry '..........' for key 'package_blob.UQE_package_blob_md5'
  56. releaser, err := globallock.Lock(ctx, containerGlobalLockKey(mci.Owner.ID, mci.Image, "manifest"))
  57. if err != nil {
  58. return "", err
  59. }
  60. defer releaser()
  61. if container_module.IsMediaTypeImageManifest(mci.MediaType) {
  62. return processOciImageManifest(ctx, mci, buf)
  63. } else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
  64. return processOciImageIndex(ctx, mci, buf)
  65. }
  66. return "", errManifestInvalid
  67. }
  68. type processManifestTxRet struct {
  69. pv *packages_model.PackageVersion
  70. pb *packages_model.PackageBlob
  71. created bool
  72. digest string
  73. }
  74. func handleCreateManifestResult(ctx context.Context, err error, mci *manifestCreationInfo, contentStore *packages_module.ContentStore, txRet *processManifestTxRet) (string, error) {
  75. if err != nil && txRet.created && txRet.pb != nil {
  76. if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil {
  77. log.Error("Error deleting package blob from content store: %v", err)
  78. }
  79. return "", err
  80. }
  81. pd, err := packages_model.GetPackageDescriptor(ctx, txRet.pv)
  82. if err != nil {
  83. log.Error("Error getting package descriptor: %v", err) // ignore this error
  84. } else {
  85. notify_service.PackageCreate(ctx, mci.Creator, pd)
  86. }
  87. return txRet.digest, nil
  88. }
  89. func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
  90. manifest, configDescriptor, metadata, err := container_service.ParseManifestMetadata(ctx, buf, mci.Owner.ID, mci.Image)
  91. if err != nil {
  92. return "", err
  93. }
  94. if _, err = buf.Seek(0, io.SeekStart); err != nil {
  95. return "", err
  96. }
  97. contentStore := packages_module.NewContentStore()
  98. var txRet processManifestTxRet
  99. err = db.WithTx(ctx, func(ctx context.Context) (err error) {
  100. blobReferences := make([]*blobReference, 0, 1+len(manifest.Layers))
  101. blobReferences = append(blobReferences, &blobReference{
  102. Digest: manifest.Config.Digest,
  103. MediaType: manifest.Config.MediaType,
  104. File: configDescriptor,
  105. ExpectedSize: manifest.Config.Size,
  106. })
  107. for _, layer := range manifest.Layers {
  108. pfd, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
  109. OwnerID: mci.Owner.ID,
  110. Image: mci.Image,
  111. Digest: string(layer.Digest),
  112. })
  113. if err != nil {
  114. return err
  115. }
  116. blobReferences = append(blobReferences, &blobReference{
  117. Digest: layer.Digest,
  118. MediaType: layer.MediaType,
  119. File: pfd,
  120. ExpectedSize: layer.Size,
  121. })
  122. }
  123. pv, err := createPackageAndVersion(ctx, mci, metadata)
  124. if err != nil {
  125. return err
  126. }
  127. uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion)
  128. if err != nil && !errors.Is(err, packages_model.ErrPackageNotExist) {
  129. return err
  130. }
  131. for _, ref := range blobReferences {
  132. if _, err = createFileFromBlobReference(ctx, pv, uploadVersion, ref); err != nil {
  133. return err
  134. }
  135. }
  136. txRet.pv = pv
  137. txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
  138. return err
  139. })
  140. return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
  141. }
  142. func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
  143. var index oci.Index
  144. if err := json.NewDecoder(buf).Decode(&index); err != nil {
  145. return "", err
  146. }
  147. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  148. return "", err
  149. }
  150. contentStore := packages_module.NewContentStore()
  151. var txRet processManifestTxRet
  152. err := db.WithTx(ctx, func(ctx context.Context) (err error) {
  153. metadata := &container_module.Metadata{
  154. Type: container_module.TypeOCI,
  155. Manifests: make([]*container_module.Manifest, 0, len(index.Manifests)),
  156. }
  157. for _, manifest := range index.Manifests {
  158. if !container_module.IsMediaTypeImageManifest(manifest.MediaType) {
  159. return errManifestInvalid
  160. }
  161. platform := container_module.DefaultPlatform
  162. if manifest.Platform != nil {
  163. platform = fmt.Sprintf("%s/%s", manifest.Platform.OS, manifest.Platform.Architecture)
  164. if manifest.Platform.Variant != "" {
  165. platform = fmt.Sprintf("%s/%s", platform, manifest.Platform.Variant)
  166. }
  167. }
  168. pfd, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
  169. OwnerID: mci.Owner.ID,
  170. Image: mci.Image,
  171. Digest: string(manifest.Digest),
  172. IsManifest: true,
  173. })
  174. if err != nil {
  175. if errors.Is(err, container_model.ErrContainerBlobNotExist) {
  176. return errManifestBlobUnknown
  177. }
  178. return err
  179. }
  180. size, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{
  181. VersionID: pfd.File.VersionID,
  182. })
  183. if err != nil {
  184. return err
  185. }
  186. metadata.Manifests = append(metadata.Manifests, &container_module.Manifest{
  187. Platform: platform,
  188. Digest: string(manifest.Digest),
  189. Size: size,
  190. })
  191. }
  192. pv, err := createPackageAndVersion(ctx, mci, metadata)
  193. if err != nil {
  194. return err
  195. }
  196. txRet.pv = pv
  197. txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
  198. return err
  199. })
  200. return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
  201. }
  202. func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
  203. created := true
  204. p := &packages_model.Package{
  205. OwnerID: mci.Owner.ID,
  206. Type: packages_model.TypeContainer,
  207. Name: strings.ToLower(mci.Image),
  208. LowerName: strings.ToLower(mci.Image),
  209. }
  210. var err error
  211. if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
  212. if !errors.Is(err, packages_model.ErrDuplicatePackage) {
  213. log.Error("Error inserting package: %v", err)
  214. return nil, err
  215. }
  216. created = false
  217. }
  218. if created {
  219. if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil {
  220. log.Error("Error setting package property: %v", err)
  221. return nil, err
  222. }
  223. }
  224. metadata.IsTagged = mci.IsTagged
  225. metadataJSON, err := json.Marshal(metadata)
  226. if err != nil {
  227. return nil, err
  228. }
  229. _pv := &packages_model.PackageVersion{
  230. PackageID: p.ID,
  231. CreatorID: mci.Creator.ID,
  232. Version: strings.ToLower(mci.Reference),
  233. LowerVersion: strings.ToLower(mci.Reference),
  234. MetadataJSON: string(metadataJSON),
  235. }
  236. pv, err := packages_model.GetOrInsertVersion(ctx, _pv)
  237. if err != nil {
  238. if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
  239. log.Error("Error inserting package: %v", err)
  240. return nil, err
  241. }
  242. if container_module.IsMediaTypeImageIndex(mci.MediaType) {
  243. if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
  244. if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
  245. return nil, err
  246. }
  247. // keep download count on overwriting
  248. _pv.DownloadCount = pv.DownloadCount
  249. if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
  250. if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
  251. log.Error("Error inserting package: %v", err)
  252. return nil, err
  253. }
  254. }
  255. } else {
  256. err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON})
  257. if err != nil {
  258. return nil, err
  259. }
  260. }
  261. }
  262. }
  263. if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil {
  264. return nil, err
  265. }
  266. if mci.IsTagged {
  267. if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
  268. return nil, err
  269. }
  270. } else {
  271. if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil {
  272. return nil, err
  273. }
  274. }
  275. if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil {
  276. return nil, err
  277. }
  278. for _, manifest := range metadata.Manifests {
  279. if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
  280. return nil, err
  281. }
  282. }
  283. return pv, nil
  284. }
  285. type blobReference struct {
  286. Digest digest.Digest
  287. MediaType string
  288. Name string
  289. File *packages_model.PackageFileDescriptor
  290. ExpectedSize int64
  291. IsLead bool
  292. }
  293. func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *packages_model.PackageVersion, ref *blobReference) (*packages_model.PackageFile, error) {
  294. if ref.File.Blob.Size != ref.ExpectedSize {
  295. return nil, errSizeInvalid
  296. }
  297. if ref.Name == "" {
  298. ref.Name = strings.ToLower("sha256_" + ref.File.Blob.HashSHA256)
  299. }
  300. pf := &packages_model.PackageFile{
  301. VersionID: pv.ID,
  302. BlobID: ref.File.Blob.ID,
  303. Name: ref.Name,
  304. LowerName: ref.Name,
  305. CompositeKey: string(ref.Digest),
  306. IsLead: ref.IsLead,
  307. }
  308. var err error
  309. if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
  310. if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
  311. // Skip this blob because the manifest contains the same filesystem layer multiple times.
  312. return pf, nil
  313. }
  314. log.Error("Error inserting package file: %v", err)
  315. return nil, err
  316. }
  317. props := map[string]string{
  318. container_module.PropertyMediaType: ref.MediaType,
  319. container_module.PropertyDigest: string(ref.Digest),
  320. }
  321. for name, value := range props {
  322. if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, name, value); err != nil {
  323. log.Error("Error setting package file property: %v", err)
  324. return nil, err
  325. }
  326. }
  327. // Remove the ref file (old file) from the blob upload version
  328. if uploadVersion != nil && ref.File.File != nil && uploadVersion.ID == ref.File.File.VersionID {
  329. if err := packages_service.DeletePackageFile(ctx, ref.File.File); err != nil {
  330. return nil, err
  331. }
  332. }
  333. return pf, nil
  334. }
  335. func createManifestBlob(ctx context.Context, contentStore *packages_module.ContentStore, mci *manifestCreationInfo, pv *packages_model.PackageVersion, buf *packages_module.HashedBuffer) (_ *packages_model.PackageBlob, created bool, manifestDigest string, _ error) {
  336. pb, exists, err := packages_model.GetOrInsertBlob(ctx, packages_service.NewPackageBlob(buf))
  337. if err != nil {
  338. log.Error("Error inserting package blob: %v", err)
  339. return nil, false, "", err
  340. }
  341. // FIXME: Workaround to be removed in v1.20
  342. // https://github.com/go-gitea/gitea/issues/19586
  343. if exists {
  344. err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
  345. if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
  346. log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
  347. exists = false
  348. }
  349. }
  350. if !exists {
  351. if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
  352. log.Error("Error saving package blob in content store: %v", err)
  353. return nil, false, "", err
  354. }
  355. }
  356. manifestDigest = digestFromHashSummer(buf)
  357. pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{
  358. Digest: digest.Digest(manifestDigest),
  359. MediaType: mci.MediaType,
  360. Name: container_module.ManifestFilename,
  361. File: &packages_model.PackageFileDescriptor{Blob: pb},
  362. ExpectedSize: pb.Size,
  363. IsLead: true,
  364. })
  365. if err != nil {
  366. return nil, false, "", err
  367. }
  368. oldManifestFiles, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  369. OwnerID: mci.Owner.ID,
  370. PackageType: packages_model.TypeContainer,
  371. VersionID: pv.ID,
  372. Query: container_module.ManifestFilename,
  373. })
  374. if err != nil {
  375. return nil, false, "", err
  376. }
  377. for _, oldManifestFile := range oldManifestFiles {
  378. if oldManifestFile.ID != pf.ID && oldManifestFile.IsLead {
  379. err = packages_model.UpdateFile(ctx, &packages_model.PackageFile{ID: oldManifestFile.ID, IsLead: false}, []string{"is_lead"})
  380. if err != nil {
  381. return nil, false, "", err
  382. }
  383. }
  384. }
  385. return pb, !exists, manifestDigest, err
  386. }