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.

conan.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package conan
  4. import (
  5. std_ctx "context"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "code.gitea.io/gitea/models/db"
  12. packages_model "code.gitea.io/gitea/models/packages"
  13. conan_model "code.gitea.io/gitea/models/packages/conan"
  14. "code.gitea.io/gitea/modules/container"
  15. "code.gitea.io/gitea/modules/json"
  16. "code.gitea.io/gitea/modules/log"
  17. packages_module "code.gitea.io/gitea/modules/packages"
  18. conan_module "code.gitea.io/gitea/modules/packages/conan"
  19. "code.gitea.io/gitea/modules/setting"
  20. "code.gitea.io/gitea/routers/api/packages/helper"
  21. "code.gitea.io/gitea/services/context"
  22. notify_service "code.gitea.io/gitea/services/notify"
  23. packages_service "code.gitea.io/gitea/services/packages"
  24. )
  25. const (
  26. conanfileFile = "conanfile.py"
  27. conaninfoFile = "conaninfo.txt"
  28. recipeReferenceKey = "RecipeReference"
  29. packageReferenceKey = "PackageReference"
  30. )
  31. var (
  32. recipeFileList = container.SetOf(
  33. conanfileFile,
  34. "conanmanifest.txt",
  35. "conan_sources.tgz",
  36. "conan_export.tgz",
  37. )
  38. packageFileList = container.SetOf(
  39. conaninfoFile,
  40. "conanmanifest.txt",
  41. "conan_package.tgz",
  42. )
  43. )
  44. func jsonResponse(ctx *context.Context, status int, obj any) {
  45. // https://github.com/conan-io/conan/issues/6613
  46. ctx.Resp.Header().Set("Content-Type", "application/json")
  47. ctx.Status(status)
  48. if err := json.NewEncoder(ctx.Resp).Encode(obj); err != nil {
  49. log.Error("JSON encode: %v", err)
  50. }
  51. }
  52. func apiError(ctx *context.Context, status int, obj any) {
  53. helper.LogAndProcessError(ctx, status, obj, func(message string) {
  54. jsonResponse(ctx, status, map[string]string{
  55. "message": message,
  56. })
  57. })
  58. }
  59. func baseURL(ctx *context.Context) string {
  60. return setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/conan"
  61. }
  62. // ExtractPathParameters is a middleware to extract common parameters from path
  63. func ExtractPathParameters(ctx *context.Context) {
  64. rref, err := conan_module.NewRecipeReference(
  65. ctx.Params("name"),
  66. ctx.Params("version"),
  67. ctx.Params("user"),
  68. ctx.Params("channel"),
  69. ctx.Params("recipe_revision"),
  70. )
  71. if err != nil {
  72. apiError(ctx, http.StatusBadRequest, err)
  73. return
  74. }
  75. ctx.Data[recipeReferenceKey] = rref
  76. reference := ctx.Params("package_reference")
  77. var pref *conan_module.PackageReference
  78. if reference != "" {
  79. pref, err = conan_module.NewPackageReference(
  80. rref,
  81. reference,
  82. ctx.Params("package_revision"),
  83. )
  84. if err != nil {
  85. apiError(ctx, http.StatusBadRequest, err)
  86. return
  87. }
  88. }
  89. ctx.Data[packageReferenceKey] = pref
  90. }
  91. // Ping reports the server capabilities
  92. func Ping(ctx *context.Context) {
  93. ctx.RespHeader().Add("X-Conan-Server-Capabilities", "revisions") // complex_search,checksum_deploy,matrix_params
  94. ctx.Status(http.StatusOK)
  95. }
  96. // Authenticate creates an authentication token for the user
  97. func Authenticate(ctx *context.Context) {
  98. if ctx.Doer == nil {
  99. apiError(ctx, http.StatusBadRequest, nil)
  100. return
  101. }
  102. token, err := packages_service.CreateAuthorizationToken(ctx.Doer)
  103. if err != nil {
  104. apiError(ctx, http.StatusInternalServerError, err)
  105. return
  106. }
  107. ctx.PlainText(http.StatusOK, token)
  108. }
  109. // CheckCredentials tests if the provided authentication token is valid
  110. func CheckCredentials(ctx *context.Context) {
  111. if ctx.Doer == nil {
  112. ctx.Status(http.StatusUnauthorized)
  113. } else {
  114. ctx.Status(http.StatusOK)
  115. }
  116. }
  117. // RecipeSnapshot displays the recipe files with their md5 hash
  118. func RecipeSnapshot(ctx *context.Context) {
  119. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  120. serveSnapshot(ctx, rref.AsKey())
  121. }
  122. // RecipeSnapshot displays the package files with their md5 hash
  123. func PackageSnapshot(ctx *context.Context) {
  124. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  125. serveSnapshot(ctx, pref.AsKey())
  126. }
  127. func serveSnapshot(ctx *context.Context, fileKey string) {
  128. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  129. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  130. if err != nil {
  131. if err == packages_model.ErrPackageNotExist {
  132. apiError(ctx, http.StatusNotFound, err)
  133. } else {
  134. apiError(ctx, http.StatusInternalServerError, err)
  135. }
  136. return
  137. }
  138. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  139. VersionID: pv.ID,
  140. CompositeKey: fileKey,
  141. })
  142. if err != nil {
  143. apiError(ctx, http.StatusInternalServerError, err)
  144. return
  145. }
  146. if len(pfs) == 0 {
  147. apiError(ctx, http.StatusNotFound, nil)
  148. return
  149. }
  150. files := make(map[string]string)
  151. for _, pf := range pfs {
  152. pb, err := packages_model.GetBlobByID(ctx, pf.BlobID)
  153. if err != nil {
  154. apiError(ctx, http.StatusInternalServerError, err)
  155. return
  156. }
  157. files[pf.Name] = pb.HashMD5
  158. }
  159. jsonResponse(ctx, http.StatusOK, files)
  160. }
  161. // RecipeDownloadURLs displays the recipe files with their download url
  162. func RecipeDownloadURLs(ctx *context.Context) {
  163. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  164. serveDownloadURLs(
  165. ctx,
  166. rref.AsKey(),
  167. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
  168. )
  169. }
  170. // PackageDownloadURLs displays the package files with their download url
  171. func PackageDownloadURLs(ctx *context.Context) {
  172. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  173. serveDownloadURLs(
  174. ctx,
  175. pref.AsKey(),
  176. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
  177. )
  178. }
  179. func serveDownloadURLs(ctx *context.Context, fileKey, downloadURL string) {
  180. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  181. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  182. if err != nil {
  183. if err == packages_model.ErrPackageNotExist {
  184. apiError(ctx, http.StatusNotFound, err)
  185. } else {
  186. apiError(ctx, http.StatusInternalServerError, err)
  187. }
  188. return
  189. }
  190. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  191. VersionID: pv.ID,
  192. CompositeKey: fileKey,
  193. })
  194. if err != nil {
  195. apiError(ctx, http.StatusInternalServerError, err)
  196. return
  197. }
  198. if len(pfs) == 0 {
  199. apiError(ctx, http.StatusNotFound, nil)
  200. return
  201. }
  202. urls := make(map[string]string)
  203. for _, pf := range pfs {
  204. urls[pf.Name] = fmt.Sprintf("%s/%s", downloadURL, pf.Name)
  205. }
  206. jsonResponse(ctx, http.StatusOK, urls)
  207. }
  208. // RecipeUploadURLs displays the upload urls for the provided recipe files
  209. func RecipeUploadURLs(ctx *context.Context) {
  210. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  211. serveUploadURLs(
  212. ctx,
  213. recipeFileList,
  214. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
  215. )
  216. }
  217. // PackageUploadURLs displays the upload urls for the provided package files
  218. func PackageUploadURLs(ctx *context.Context) {
  219. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  220. serveUploadURLs(
  221. ctx,
  222. packageFileList,
  223. fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
  224. )
  225. }
  226. func serveUploadURLs(ctx *context.Context, fileFilter container.Set[string], uploadURL string) {
  227. defer ctx.Req.Body.Close()
  228. var files map[string]int64
  229. if err := json.NewDecoder(ctx.Req.Body).Decode(&files); err != nil {
  230. apiError(ctx, http.StatusBadRequest, err)
  231. return
  232. }
  233. urls := make(map[string]string)
  234. for file := range files {
  235. if fileFilter.Contains(file) {
  236. urls[file] = fmt.Sprintf("%s/%s", uploadURL, file)
  237. }
  238. }
  239. jsonResponse(ctx, http.StatusOK, urls)
  240. }
  241. // UploadRecipeFile handles the upload of a recipe file
  242. func UploadRecipeFile(ctx *context.Context) {
  243. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  244. uploadFile(ctx, recipeFileList, rref.AsKey())
  245. }
  246. // UploadPackageFile handles the upload of a package file
  247. func UploadPackageFile(ctx *context.Context) {
  248. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  249. uploadFile(ctx, packageFileList, pref.AsKey())
  250. }
  251. func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
  252. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  253. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  254. filename := ctx.Params("filename")
  255. if !fileFilter.Contains(filename) {
  256. apiError(ctx, http.StatusBadRequest, nil)
  257. return
  258. }
  259. upload, needToClose, err := ctx.UploadStream()
  260. if err != nil {
  261. apiError(ctx, http.StatusBadRequest, err)
  262. return
  263. }
  264. if needToClose {
  265. defer upload.Close()
  266. }
  267. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  268. if err != nil {
  269. apiError(ctx, http.StatusInternalServerError, err)
  270. return
  271. }
  272. defer buf.Close()
  273. isConanfileFile := filename == conanfileFile
  274. isConaninfoFile := filename == conaninfoFile
  275. pci := &packages_service.PackageCreationInfo{
  276. PackageInfo: packages_service.PackageInfo{
  277. Owner: ctx.Package.Owner,
  278. PackageType: packages_model.TypeConan,
  279. Name: rref.Name,
  280. Version: rref.Version,
  281. },
  282. Creator: ctx.Doer,
  283. }
  284. pfci := &packages_service.PackageFileCreationInfo{
  285. PackageFileInfo: packages_service.PackageFileInfo{
  286. Filename: strings.ToLower(filename),
  287. CompositeKey: fileKey,
  288. },
  289. Creator: ctx.Doer,
  290. Data: buf,
  291. IsLead: isConanfileFile,
  292. Properties: map[string]string{
  293. conan_module.PropertyRecipeUser: rref.User,
  294. conan_module.PropertyRecipeChannel: rref.Channel,
  295. conan_module.PropertyRecipeRevision: rref.RevisionOrDefault(),
  296. },
  297. OverwriteExisting: true,
  298. }
  299. if pref != nil {
  300. pfci.Properties[conan_module.PropertyPackageReference] = pref.Reference
  301. pfci.Properties[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
  302. }
  303. if isConanfileFile || isConaninfoFile {
  304. if isConanfileFile {
  305. metadata, err := conan_module.ParseConanfile(buf)
  306. if err != nil {
  307. log.Error("Error parsing package metadata: %v", err)
  308. apiError(ctx, http.StatusInternalServerError, err)
  309. return
  310. }
  311. pv, err := packages_model.GetVersionByNameAndVersion(ctx, pci.Owner.ID, pci.PackageType, pci.Name, pci.Version)
  312. if err != nil && err != packages_model.ErrPackageNotExist {
  313. apiError(ctx, http.StatusInternalServerError, err)
  314. return
  315. }
  316. if pv != nil {
  317. raw, err := json.Marshal(metadata)
  318. if err != nil {
  319. apiError(ctx, http.StatusInternalServerError, err)
  320. return
  321. }
  322. pv.MetadataJSON = string(raw)
  323. if err := packages_model.UpdateVersion(ctx, pv); err != nil {
  324. apiError(ctx, http.StatusInternalServerError, err)
  325. return
  326. }
  327. } else {
  328. pci.Metadata = metadata
  329. }
  330. } else {
  331. info, err := conan_module.ParseConaninfo(buf)
  332. if err != nil {
  333. log.Error("Error parsing conan info: %v", err)
  334. apiError(ctx, http.StatusInternalServerError, err)
  335. return
  336. }
  337. raw, err := json.Marshal(info)
  338. if err != nil {
  339. apiError(ctx, http.StatusInternalServerError, err)
  340. return
  341. }
  342. pfci.Properties[conan_module.PropertyPackageInfo] = string(raw)
  343. }
  344. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  345. apiError(ctx, http.StatusInternalServerError, err)
  346. return
  347. }
  348. }
  349. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  350. ctx,
  351. pci,
  352. pfci,
  353. )
  354. if err != nil {
  355. switch err {
  356. case packages_model.ErrDuplicatePackageFile:
  357. apiError(ctx, http.StatusConflict, err)
  358. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  359. apiError(ctx, http.StatusForbidden, err)
  360. default:
  361. apiError(ctx, http.StatusInternalServerError, err)
  362. }
  363. return
  364. }
  365. ctx.Status(http.StatusCreated)
  366. }
  367. // DownloadRecipeFile serves the content of the requested recipe file
  368. func DownloadRecipeFile(ctx *context.Context) {
  369. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  370. downloadFile(ctx, recipeFileList, rref.AsKey())
  371. }
  372. // DownloadPackageFile serves the content of the requested package file
  373. func DownloadPackageFile(ctx *context.Context) {
  374. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  375. downloadFile(ctx, packageFileList, pref.AsKey())
  376. }
  377. func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
  378. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  379. filename := ctx.Params("filename")
  380. if !fileFilter.Contains(filename) {
  381. apiError(ctx, http.StatusBadRequest, nil)
  382. return
  383. }
  384. s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
  385. ctx,
  386. &packages_service.PackageInfo{
  387. Owner: ctx.Package.Owner,
  388. PackageType: packages_model.TypeConan,
  389. Name: rref.Name,
  390. Version: rref.Version,
  391. },
  392. &packages_service.PackageFileInfo{
  393. Filename: filename,
  394. CompositeKey: fileKey,
  395. },
  396. )
  397. if err != nil {
  398. if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
  399. apiError(ctx, http.StatusNotFound, err)
  400. return
  401. }
  402. apiError(ctx, http.StatusInternalServerError, err)
  403. return
  404. }
  405. helper.ServePackageFile(ctx, s, u, pf)
  406. }
  407. // DeleteRecipeV1 deletes the requested recipe(s)
  408. func DeleteRecipeV1(ctx *context.Context) {
  409. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  410. if err := deleteRecipeOrPackage(ctx, rref, true, nil, false); err != nil {
  411. if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
  412. apiError(ctx, http.StatusNotFound, err)
  413. } else {
  414. apiError(ctx, http.StatusInternalServerError, err)
  415. }
  416. return
  417. }
  418. ctx.Status(http.StatusOK)
  419. }
  420. // DeleteRecipeV2 deletes the requested recipe(s) respecting its revisions
  421. func DeleteRecipeV2(ctx *context.Context) {
  422. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  423. if err := deleteRecipeOrPackage(ctx, rref, rref.Revision == "", nil, false); err != nil {
  424. if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
  425. apiError(ctx, http.StatusNotFound, err)
  426. } else {
  427. apiError(ctx, http.StatusInternalServerError, err)
  428. }
  429. return
  430. }
  431. ctx.Status(http.StatusOK)
  432. }
  433. // DeletePackageV1 deletes the requested package(s)
  434. func DeletePackageV1(ctx *context.Context) {
  435. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  436. type PackageReferences struct {
  437. References []string `json:"package_ids"`
  438. }
  439. var ids *PackageReferences
  440. if err := json.NewDecoder(ctx.Req.Body).Decode(&ids); err != nil {
  441. apiError(ctx, http.StatusInternalServerError, err)
  442. return
  443. }
  444. revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
  445. if err != nil {
  446. apiError(ctx, http.StatusInternalServerError, err)
  447. return
  448. }
  449. for _, revision := range revisions {
  450. currentRref := rref.WithRevision(revision.Value)
  451. var references []*conan_model.PropertyValue
  452. if len(ids.References) == 0 {
  453. if references, err = conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, currentRref); err != nil {
  454. apiError(ctx, http.StatusInternalServerError, err)
  455. return
  456. }
  457. } else {
  458. for _, reference := range ids.References {
  459. references = append(references, &conan_model.PropertyValue{Value: reference})
  460. }
  461. }
  462. for _, reference := range references {
  463. pref, _ := conan_module.NewPackageReference(currentRref, reference.Value, conan_module.DefaultRevision)
  464. if err := deleteRecipeOrPackage(ctx, currentRref, true, pref, true); err != nil {
  465. if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
  466. apiError(ctx, http.StatusNotFound, err)
  467. } else {
  468. apiError(ctx, http.StatusInternalServerError, err)
  469. }
  470. return
  471. }
  472. }
  473. }
  474. ctx.Status(http.StatusOK)
  475. }
  476. // DeletePackageV2 deletes the requested package(s) respecting its revisions
  477. func DeletePackageV2(ctx *context.Context) {
  478. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  479. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  480. if pref != nil { // has package reference
  481. if err := deleteRecipeOrPackage(ctx, rref, false, pref, pref.Revision == ""); err != nil {
  482. if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
  483. apiError(ctx, http.StatusNotFound, err)
  484. } else {
  485. apiError(ctx, http.StatusInternalServerError, err)
  486. }
  487. } else {
  488. ctx.Status(http.StatusOK)
  489. }
  490. return
  491. }
  492. references, err := conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, rref)
  493. if err != nil {
  494. apiError(ctx, http.StatusInternalServerError, err)
  495. return
  496. }
  497. if len(references) == 0 {
  498. apiError(ctx, http.StatusNotFound, conan_model.ErrPackageReferenceNotExist)
  499. return
  500. }
  501. for _, reference := range references {
  502. pref, _ := conan_module.NewPackageReference(rref, reference.Value, conan_module.DefaultRevision)
  503. if err := deleteRecipeOrPackage(ctx, rref, false, pref, true); err != nil {
  504. if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
  505. apiError(ctx, http.StatusNotFound, err)
  506. } else {
  507. apiError(ctx, http.StatusInternalServerError, err)
  508. }
  509. return
  510. }
  511. }
  512. ctx.Status(http.StatusOK)
  513. }
  514. func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeReference, ignoreRecipeRevision bool, pref *conan_module.PackageReference, ignorePackageRevision bool) error {
  515. var pd *packages_model.PackageDescriptor
  516. versionDeleted := false
  517. err := db.WithTx(apictx, func(ctx std_ctx.Context) error {
  518. pv, err := packages_model.GetVersionByNameAndVersion(ctx, apictx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  519. if err != nil {
  520. return err
  521. }
  522. pd, err = packages_model.GetPackageDescriptor(ctx, pv)
  523. if err != nil {
  524. return err
  525. }
  526. filter := map[string]string{
  527. conan_module.PropertyRecipeUser: rref.User,
  528. conan_module.PropertyRecipeChannel: rref.Channel,
  529. }
  530. if !ignoreRecipeRevision {
  531. filter[conan_module.PropertyRecipeRevision] = rref.RevisionOrDefault()
  532. }
  533. if pref != nil {
  534. filter[conan_module.PropertyPackageReference] = pref.Reference
  535. if !ignorePackageRevision {
  536. filter[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
  537. }
  538. }
  539. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  540. VersionID: pv.ID,
  541. Properties: filter,
  542. })
  543. if err != nil {
  544. return err
  545. }
  546. if len(pfs) == 0 {
  547. return conan_model.ErrPackageReferenceNotExist
  548. }
  549. for _, pf := range pfs {
  550. if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
  551. return err
  552. }
  553. }
  554. has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
  555. if err != nil {
  556. return err
  557. }
  558. if !has {
  559. versionDeleted = true
  560. return packages_service.DeletePackageVersionAndReferences(ctx, pv)
  561. }
  562. return nil
  563. })
  564. if err != nil {
  565. return err
  566. }
  567. if versionDeleted {
  568. notify_service.PackageDelete(apictx, apictx.Doer, pd)
  569. }
  570. return nil
  571. }
  572. // ListRecipeRevisions gets a list of all recipe revisions
  573. func ListRecipeRevisions(ctx *context.Context) {
  574. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  575. revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
  576. if err != nil {
  577. apiError(ctx, http.StatusInternalServerError, err)
  578. return
  579. }
  580. listRevisions(ctx, revisions)
  581. }
  582. // ListPackageRevisions gets a list of all package revisions
  583. func ListPackageRevisions(ctx *context.Context) {
  584. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  585. revisions, err := conan_model.GetPackageRevisions(ctx, ctx.Package.Owner.ID, pref)
  586. if err != nil {
  587. apiError(ctx, http.StatusInternalServerError, err)
  588. return
  589. }
  590. listRevisions(ctx, revisions)
  591. }
  592. type revisionInfo struct {
  593. Revision string `json:"revision"`
  594. Time time.Time `json:"time"`
  595. }
  596. func listRevisions(ctx *context.Context, revisions []*conan_model.PropertyValue) {
  597. if len(revisions) == 0 {
  598. apiError(ctx, http.StatusNotFound, conan_model.ErrRecipeReferenceNotExist)
  599. return
  600. }
  601. type RevisionList struct {
  602. Revisions []*revisionInfo `json:"revisions"`
  603. }
  604. revs := make([]*revisionInfo, 0, len(revisions))
  605. for _, rev := range revisions {
  606. revs = append(revs, &revisionInfo{Revision: rev.Value, Time: rev.CreatedUnix.AsLocalTime()})
  607. }
  608. jsonResponse(ctx, http.StatusOK, &RevisionList{revs})
  609. }
  610. // LatestRecipeRevision gets the latest recipe revision
  611. func LatestRecipeRevision(ctx *context.Context) {
  612. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  613. revision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref)
  614. if err != nil {
  615. if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist {
  616. apiError(ctx, http.StatusNotFound, err)
  617. } else {
  618. apiError(ctx, http.StatusInternalServerError, err)
  619. }
  620. return
  621. }
  622. jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: revision.CreatedUnix.AsLocalTime()})
  623. }
  624. // LatestPackageRevision gets the latest package revision
  625. func LatestPackageRevision(ctx *context.Context) {
  626. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  627. revision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref)
  628. if err != nil {
  629. if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist {
  630. apiError(ctx, http.StatusNotFound, err)
  631. } else {
  632. apiError(ctx, http.StatusInternalServerError, err)
  633. }
  634. return
  635. }
  636. jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: revision.CreatedUnix.AsLocalTime()})
  637. }
  638. // ListRecipeRevisionFiles gets a list of all recipe revision files
  639. func ListRecipeRevisionFiles(ctx *context.Context) {
  640. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  641. listRevisionFiles(ctx, rref.AsKey())
  642. }
  643. // ListPackageRevisionFiles gets a list of all package revision files
  644. func ListPackageRevisionFiles(ctx *context.Context) {
  645. pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
  646. listRevisionFiles(ctx, pref.AsKey())
  647. }
  648. func listRevisionFiles(ctx *context.Context, fileKey string) {
  649. rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
  650. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
  651. if err != nil {
  652. if err == packages_model.ErrPackageNotExist {
  653. apiError(ctx, http.StatusNotFound, err)
  654. } else {
  655. apiError(ctx, http.StatusInternalServerError, err)
  656. }
  657. return
  658. }
  659. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  660. VersionID: pv.ID,
  661. CompositeKey: fileKey,
  662. })
  663. if err != nil {
  664. apiError(ctx, http.StatusInternalServerError, err)
  665. return
  666. }
  667. if len(pfs) == 0 {
  668. apiError(ctx, http.StatusNotFound, nil)
  669. return
  670. }
  671. files := make(map[string]any)
  672. for _, pf := range pfs {
  673. files[pf.Name] = nil
  674. }
  675. type FileList struct {
  676. Files map[string]any `json:"files"`
  677. }
  678. jsonResponse(ctx, http.StatusOK, &FileList{
  679. Files: files,
  680. })
  681. }