gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. // GitHub Actions Artifacts V4 API Simple Description
  5. //
  6. // 1. Upload artifact
  7. // 1.1. CreateArtifact
  8. // Post: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact
  9. // Request:
  10. // {
  11. // "workflow_run_backend_id": "21",
  12. // "workflow_job_run_backend_id": "49",
  13. // "name": "test",
  14. // "version": 4
  15. // }
  16. // Response:
  17. // {
  18. // "ok": true,
  19. // "signedUploadUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75"
  20. // }
  21. // 1.2. Upload Zip Content to Blobstorage (unauthenticated request)
  22. // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block
  23. // 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
  24. // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
  25. // 1.4. BlockList xml payload to Blobstorage (unauthenticated request)
  26. // Files of about 800MB are parallel in parallel and / or out of order, this file is needed to ensure the correct order
  27. // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
  28. // Request
  29. // <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  30. // <BlockList>
  31. // <Latest>blockId1</Latest>
  32. // <Latest>blockId2</Latest>
  33. // </BlockList>
  34. // 1.5. FinalizeArtifact
  35. // Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
  36. // Request
  37. // {
  38. // "workflow_run_backend_id": "21",
  39. // "workflow_job_run_backend_id": "49",
  40. // "name": "test",
  41. // "size": "2097",
  42. // "hash": "sha256:b6325614d5649338b87215d9536b3c0477729b8638994c74cdefacb020a2cad4"
  43. // }
  44. // Response
  45. // {
  46. // "ok": true,
  47. // "artifactId": "4"
  48. // }
  49. // 2. Download artifact
  50. // 2.1. ListArtifacts and optionally filter by artifact exact name or id
  51. // Post: /twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts
  52. // Request
  53. // {
  54. // "workflow_run_backend_id": "21",
  55. // "workflow_job_run_backend_id": "49",
  56. // "name_filter": "test"
  57. // }
  58. // Response
  59. // {
  60. // "artifacts": [
  61. // {
  62. // "workflowRunBackendId": "21",
  63. // "workflowJobRunBackendId": "49",
  64. // "databaseId": "4",
  65. // "name": "test",
  66. // "size": "2093",
  67. // "createdAt": "2024-01-23T00:13:28Z"
  68. // }
  69. // ]
  70. // }
  71. // 2.2. GetSignedArtifactURL get the URL to download the artifact zip file of a specific artifact
  72. // Post: /twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL
  73. // Request
  74. // {
  75. // "workflow_run_backend_id": "21",
  76. // "workflow_job_run_backend_id": "49",
  77. // "name": "test"
  78. // }
  79. // Response
  80. // {
  81. // "signedUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76"
  82. // }
  83. // 2.3. Download Zip from Blobstorage (unauthenticated request)
  84. // GET: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76
  85. import (
  86. "crypto/hmac"
  87. "crypto/sha256"
  88. "encoding/base64"
  89. "encoding/xml"
  90. "fmt"
  91. "io"
  92. "net/http"
  93. "net/url"
  94. "strconv"
  95. "strings"
  96. "time"
  97. "code.gitea.io/gitea/models/actions"
  98. "code.gitea.io/gitea/models/db"
  99. "code.gitea.io/gitea/modules/httplib"
  100. "code.gitea.io/gitea/modules/log"
  101. "code.gitea.io/gitea/modules/setting"
  102. "code.gitea.io/gitea/modules/storage"
  103. "code.gitea.io/gitea/modules/util"
  104. "code.gitea.io/gitea/modules/web"
  105. "code.gitea.io/gitea/services/context"
  106. "google.golang.org/protobuf/encoding/protojson"
  107. protoreflect "google.golang.org/protobuf/reflect/protoreflect"
  108. "google.golang.org/protobuf/types/known/timestamppb"
  109. )
  110. const (
  111. ArtifactV4RouteBase = "/twirp/github.actions.results.api.v1.ArtifactService"
  112. ArtifactV4ContentEncoding = "application/zip"
  113. )
  114. type artifactV4Routes struct {
  115. prefix string
  116. fs storage.ObjectStorage
  117. }
  118. func ArtifactV4Contexter() func(next http.Handler) http.Handler {
  119. return func(next http.Handler) http.Handler {
  120. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  121. base := context.NewBaseContext(resp, req)
  122. ctx := &ArtifactContext{Base: base}
  123. ctx.SetContextValue(artifactContextKey, ctx)
  124. next.ServeHTTP(ctx.Resp, ctx.Req)
  125. })
  126. }
  127. }
  128. func ArtifactsV4Routes(prefix string) *web.Router {
  129. m := web.NewRouter()
  130. r := artifactV4Routes{
  131. prefix: prefix,
  132. fs: storage.ActionsArtifacts,
  133. }
  134. m.Group("", func() {
  135. m.Post("CreateArtifact", r.createArtifact)
  136. m.Post("FinalizeArtifact", r.finalizeArtifact)
  137. m.Post("ListArtifacts", r.listArtifacts)
  138. m.Post("GetSignedArtifactURL", r.getSignedArtifactURL)
  139. m.Post("DeleteArtifact", r.deleteArtifact)
  140. }, ArtifactContexter())
  141. m.Group("", func() {
  142. m.Put("UploadArtifact", r.uploadArtifact)
  143. m.Get("DownloadArtifact", r.downloadArtifact)
  144. }, ArtifactV4Contexter())
  145. return m
  146. }
  147. func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID, artifactID int64) []byte {
  148. mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
  149. mac.Write([]byte(endp))
  150. mac.Write([]byte(expires))
  151. mac.Write([]byte(artifactName))
  152. fmt.Fprint(mac, taskID)
  153. fmt.Fprint(mac, artifactID)
  154. return mac.Sum(nil)
  155. }
  156. func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string {
  157. expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
  158. uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") +
  159. "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + strconv.FormatInt(taskID, 10) + "&artifactID=" + strconv.FormatInt(artifactID, 10)
  160. return uploadURL
  161. }
  162. func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) {
  163. rawTaskID := ctx.Req.URL.Query().Get("taskID")
  164. rawArtifactID := ctx.Req.URL.Query().Get("artifactID")
  165. sig := ctx.Req.URL.Query().Get("sig")
  166. expires := ctx.Req.URL.Query().Get("expires")
  167. artifactName := ctx.Req.URL.Query().Get("artifactName")
  168. dsig, _ := base64.URLEncoding.DecodeString(sig)
  169. taskID, _ := strconv.ParseInt(rawTaskID, 10, 64)
  170. artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64)
  171. expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID)
  172. if !hmac.Equal(dsig, expecedsig) {
  173. log.Error("Error unauthorized")
  174. ctx.HTTPError(http.StatusUnauthorized, "Error unauthorized")
  175. return nil, "", false
  176. }
  177. t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", expires)
  178. if err != nil || t.Before(time.Now()) {
  179. log.Error("Error link expired")
  180. ctx.HTTPError(http.StatusUnauthorized, "Error link expired")
  181. return nil, "", false
  182. }
  183. task, err := actions.GetTaskByID(ctx, taskID)
  184. if err != nil {
  185. log.Error("Error runner api getting task by ID: %v", err)
  186. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task by ID")
  187. return nil, "", false
  188. }
  189. if task.Status != actions.StatusRunning {
  190. log.Error("Error runner api getting task: task is not running")
  191. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
  192. return nil, "", false
  193. }
  194. if err := task.LoadJob(ctx); err != nil {
  195. log.Error("Error runner api getting job: %v", err)
  196. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting job")
  197. return nil, "", false
  198. }
  199. return task, artifactName, true
  200. }
  201. func (r *artifactV4Routes) getArtifactByName(ctx *ArtifactContext, runID int64, name string) (*actions.ActionArtifact, error) {
  202. var art actions.ActionArtifact
  203. has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ? AND content_encoding = ?", runID, name, name+".zip", ArtifactV4ContentEncoding).Get(&art)
  204. if err != nil {
  205. return nil, err
  206. } else if !has {
  207. return nil, util.ErrNotExist
  208. }
  209. return &art, nil
  210. }
  211. func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) bool {
  212. body, err := io.ReadAll(ctx.Req.Body)
  213. if err != nil {
  214. log.Error("Error decode request body: %v", err)
  215. ctx.HTTPError(http.StatusInternalServerError, "Error decode request body")
  216. return false
  217. }
  218. err = protojson.Unmarshal(body, req)
  219. if err != nil {
  220. log.Error("Error decode request body: %v", err)
  221. ctx.HTTPError(http.StatusInternalServerError, "Error decode request body")
  222. return false
  223. }
  224. return true
  225. }
  226. func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) {
  227. resp, err := protojson.Marshal(req)
  228. if err != nil {
  229. log.Error("Error encode response body: %v", err)
  230. ctx.HTTPError(http.StatusInternalServerError, "Error encode response body")
  231. return
  232. }
  233. ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
  234. ctx.Resp.WriteHeader(http.StatusOK)
  235. _, _ = ctx.Resp.Write(resp)
  236. }
  237. func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
  238. var req CreateArtifactRequest
  239. if ok := r.parseProtbufBody(ctx, &req); !ok {
  240. return
  241. }
  242. _, _, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
  243. if !ok {
  244. return
  245. }
  246. artifactName := req.Name
  247. rententionDays := setting.Actions.ArtifactRetentionDays
  248. if req.ExpiresAt != nil {
  249. rententionDays = int64(time.Until(req.ExpiresAt.AsTime()).Hours() / 24)
  250. }
  251. // create or get artifact with name and path
  252. artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", rententionDays)
  253. if err != nil {
  254. log.Error("Error create or get artifact: %v", err)
  255. ctx.HTTPError(http.StatusInternalServerError, "Error create or get artifact")
  256. return
  257. }
  258. artifact.ContentEncoding = ArtifactV4ContentEncoding
  259. artifact.FileSize = 0
  260. artifact.FileCompressedSize = 0
  261. if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
  262. log.Error("Error UpdateArtifactByID: %v", err)
  263. ctx.HTTPError(http.StatusInternalServerError, "Error UpdateArtifactByID")
  264. return
  265. }
  266. respData := CreateArtifactResponse{
  267. Ok: true,
  268. SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID),
  269. }
  270. r.sendProtbufBody(ctx, &respData)
  271. }
  272. func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
  273. task, artifactName, ok := r.verifySignature(ctx, "UploadArtifact")
  274. if !ok {
  275. return
  276. }
  277. comp := ctx.Req.URL.Query().Get("comp")
  278. switch comp {
  279. case "block", "appendBlock":
  280. blockid := ctx.Req.URL.Query().Get("blockid")
  281. if blockid == "" {
  282. // get artifact by name
  283. artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
  284. if err != nil {
  285. log.Error("Error artifact not found: %v", err)
  286. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  287. return
  288. }
  289. _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID)
  290. if err != nil {
  291. log.Error("Error runner api getting task: task is not running")
  292. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
  293. return
  294. }
  295. artifact.FileCompressedSize += ctx.Req.ContentLength
  296. artifact.FileSize += ctx.Req.ContentLength
  297. if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
  298. log.Error("Error UpdateArtifactByID: %v", err)
  299. ctx.HTTPError(http.StatusInternalServerError, "Error UpdateArtifactByID")
  300. return
  301. }
  302. } else {
  303. _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/block-%d-%d-%s", task.Job.RunID, task.Job.RunID, ctx.Req.ContentLength, base64.URLEncoding.EncodeToString([]byte(blockid))), ctx.Req.Body, -1)
  304. if err != nil {
  305. log.Error("Error runner api getting task: task is not running")
  306. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
  307. return
  308. }
  309. }
  310. ctx.JSON(http.StatusCreated, "appended")
  311. case "blocklist":
  312. rawArtifactID := ctx.Req.URL.Query().Get("artifactID")
  313. artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64)
  314. _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1)
  315. if err != nil {
  316. log.Error("Error runner api getting task: task is not running")
  317. ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running")
  318. return
  319. }
  320. ctx.JSON(http.StatusCreated, "created")
  321. }
  322. }
  323. type BlockList struct {
  324. Latest []string `xml:"Latest"`
  325. }
  326. type Latest struct {
  327. Value string `xml:",chardata"`
  328. }
  329. func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, error) {
  330. blockListName := fmt.Sprintf("tmpv4%d/%d-%d-blocklist", runID, runID, artifactID)
  331. s, err := r.fs.Open(blockListName)
  332. if err != nil {
  333. return nil, err
  334. }
  335. xdec := xml.NewDecoder(s)
  336. blockList := &BlockList{}
  337. err = xdec.Decode(blockList)
  338. delerr := r.fs.Delete(blockListName)
  339. if delerr != nil {
  340. log.Warn("Failed to delete blockList %s: %v", blockListName, delerr)
  341. }
  342. return blockList, err
  343. }
  344. func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
  345. var req FinalizeArtifactRequest
  346. if ok := r.parseProtbufBody(ctx, &req); !ok {
  347. return
  348. }
  349. _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
  350. if !ok {
  351. return
  352. }
  353. // get artifact by name
  354. artifact, err := r.getArtifactByName(ctx, runID, req.Name)
  355. if err != nil {
  356. log.Error("Error artifact not found: %v", err)
  357. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  358. return
  359. }
  360. var chunks []*chunkFileItem
  361. blockList, err := r.readBlockList(runID, artifact.ID)
  362. if err != nil {
  363. log.Warn("Failed to read BlockList, fallback to old behavior: %v", err)
  364. chunkMap, err := listChunksByRunID(r.fs, runID)
  365. if err != nil {
  366. log.Error("Error merge chunks: %v", err)
  367. ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
  368. return
  369. }
  370. chunks, ok = chunkMap[artifact.ID]
  371. if !ok {
  372. log.Error("Error merge chunks")
  373. ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
  374. return
  375. }
  376. } else {
  377. chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList)
  378. if err != nil {
  379. log.Error("Error merge chunks: %v", err)
  380. ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
  381. return
  382. }
  383. artifact.FileSize = chunks[len(chunks)-1].End + 1
  384. artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1
  385. }
  386. checksum := ""
  387. if req.Hash != nil {
  388. checksum = req.Hash.Value
  389. }
  390. if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil {
  391. log.Error("Error merge chunks: %v", err)
  392. ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks")
  393. return
  394. }
  395. respData := FinalizeArtifactResponse{
  396. Ok: true,
  397. ArtifactId: artifact.ID,
  398. }
  399. r.sendProtbufBody(ctx, &respData)
  400. }
  401. func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
  402. var req ListArtifactsRequest
  403. if ok := r.parseProtbufBody(ctx, &req); !ok {
  404. return
  405. }
  406. _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
  407. if !ok {
  408. return
  409. }
  410. artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
  411. RunID: runID,
  412. Status: int(actions.ArtifactStatusUploadConfirmed),
  413. })
  414. if err != nil {
  415. log.Error("Error getting artifacts: %v", err)
  416. ctx.HTTPError(http.StatusInternalServerError, err.Error())
  417. return
  418. }
  419. list := []*ListArtifactsResponse_MonolithArtifact{}
  420. table := map[string]*ListArtifactsResponse_MonolithArtifact{}
  421. for _, artifact := range artifacts {
  422. if _, ok := table[artifact.ArtifactName]; ok || req.IdFilter != nil && artifact.ID != req.IdFilter.Value || req.NameFilter != nil && artifact.ArtifactName != req.NameFilter.Value || artifact.ArtifactName+".zip" != artifact.ArtifactPath || artifact.ContentEncoding != ArtifactV4ContentEncoding {
  423. table[artifact.ArtifactName] = nil
  424. continue
  425. }
  426. table[artifact.ArtifactName] = &ListArtifactsResponse_MonolithArtifact{
  427. Name: artifact.ArtifactName,
  428. CreatedAt: timestamppb.New(artifact.CreatedUnix.AsTime()),
  429. DatabaseId: artifact.ID,
  430. WorkflowRunBackendId: req.WorkflowRunBackendId,
  431. WorkflowJobRunBackendId: req.WorkflowJobRunBackendId,
  432. Size: artifact.FileSize,
  433. }
  434. }
  435. for _, artifact := range table {
  436. if artifact != nil {
  437. list = append(list, artifact)
  438. }
  439. }
  440. respData := ListArtifactsResponse{
  441. Artifacts: list,
  442. }
  443. r.sendProtbufBody(ctx, &respData)
  444. }
  445. func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
  446. var req GetSignedArtifactURLRequest
  447. if ok := r.parseProtbufBody(ctx, &req); !ok {
  448. return
  449. }
  450. _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
  451. if !ok {
  452. return
  453. }
  454. artifactName := req.Name
  455. // get artifact by name
  456. artifact, err := r.getArtifactByName(ctx, runID, artifactName)
  457. if err != nil {
  458. log.Error("Error artifact not found: %v", err)
  459. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  460. return
  461. }
  462. if artifact.Status != actions.ArtifactStatusUploadConfirmed {
  463. log.Error("Error artifact not found: %s", artifact.Status.ToString())
  464. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  465. return
  466. }
  467. respData := GetSignedArtifactURLResponse{}
  468. if setting.Actions.ArtifactStorage.ServeDirect() {
  469. u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath, ctx.Req.Method, nil)
  470. if u != nil && err == nil {
  471. respData.SignedUrl = u.String()
  472. }
  473. }
  474. if respData.SignedUrl == "" {
  475. respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID)
  476. }
  477. r.sendProtbufBody(ctx, &respData)
  478. }
  479. func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
  480. task, artifactName, ok := r.verifySignature(ctx, "DownloadArtifact")
  481. if !ok {
  482. return
  483. }
  484. // get artifact by name
  485. artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
  486. if err != nil {
  487. log.Error("Error artifact not found: %v", err)
  488. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  489. return
  490. }
  491. if artifact.Status != actions.ArtifactStatusUploadConfirmed {
  492. log.Error("Error artifact not found: %s", artifact.Status.ToString())
  493. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  494. return
  495. }
  496. file, _ := r.fs.Open(artifact.StoragePath)
  497. _, _ = io.Copy(ctx.Resp, file)
  498. }
  499. func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) {
  500. var req DeleteArtifactRequest
  501. if ok := r.parseProtbufBody(ctx, &req); !ok {
  502. return
  503. }
  504. _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
  505. if !ok {
  506. return
  507. }
  508. // get artifact by name
  509. artifact, err := r.getArtifactByName(ctx, runID, req.Name)
  510. if err != nil {
  511. log.Error("Error artifact not found: %v", err)
  512. ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
  513. return
  514. }
  515. err = actions.SetArtifactNeedDelete(ctx, runID, req.Name)
  516. if err != nil {
  517. log.Error("Error deleting artifacts: %v", err)
  518. ctx.HTTPError(http.StatusInternalServerError, err.Error())
  519. return
  520. }
  521. respData := DeleteArtifactResponse{
  522. Ok: true,
  523. ArtifactId: artifact.ID,
  524. }
  525. r.sendProtbufBody(ctx, &respData)
  526. }