Skip to content

Commit 4ed0724

Browse files
actions artifacts api list/download check status upload confirmed (#34273)
* fixes a fixture status to upload confirmed * add another fixture as noise to break tests as soon they are exposed to api * v4 delete test added check that artifact is no longer visible in internal api with status pending delete * removal of http 404 on empty list: actions/upload-artifact@v4 now backoff on http 404 of ListArtifacts endpoint * fixes artifacts with pending delete etc. are able to be found and downloaded if the storage is not freed
1 parent 533b8b2 commit 4ed0724

File tree

5 files changed

+81
-7
lines changed

5 files changed

+81
-7
lines changed

models/actions/artifact.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ const (
3030
ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
3131
)
3232

33+
func (status ArtifactStatus) ToString() string {
34+
switch status {
35+
case ArtifactStatusUploadPending:
36+
return "upload is not yet completed"
37+
case ArtifactStatusUploadConfirmed:
38+
return "upload is completed"
39+
case ArtifactStatusUploadError:
40+
return "upload failed"
41+
case ArtifactStatusExpired:
42+
return "expired"
43+
case ArtifactStatusPendingDeletion:
44+
return "pending deletion"
45+
case ArtifactStatusDeleted:
46+
return "deleted"
47+
default:
48+
return "unknown"
49+
}
50+
}
51+
3352
func init() {
3453
db.RegisterModel(new(ActionArtifact))
3554
}

models/fixtures/action_artifact.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@
1111
content_encoding: ""
1212
artifact_path: "abc.txt"
1313
artifact_name: "artifact-download"
14+
status: 2
15+
created_unix: 1712338649
16+
updated_unix: 1712338649
17+
expired_unix: 1720114649
18+
19+
-
20+
id: 2
21+
run_id: 791
22+
runner_id: 1
23+
repo_id: 4
24+
owner_id: 1
25+
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
26+
storage_path: ""
27+
file_size: 1024
28+
file_compressed_size: 1024
29+
content_encoding: "30/20/1712348022422036662.chunk"
30+
artifact_path: "abc.txt"
31+
artifact_name: "artifact-download-incomplete"
1432
status: 1
1533
created_unix: 1712338649
1634
updated_unix: 1712338649

routers/api/actions/artifacts.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,10 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
337337
return
338338
}
339339

340-
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
340+
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
341+
RunID: runID,
342+
Status: int(actions.ArtifactStatusUploadConfirmed),
343+
})
341344
if err != nil {
342345
log.Error("Error getting artifacts: %v", err)
343346
ctx.HTTPError(http.StatusInternalServerError, err.Error())
@@ -402,6 +405,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
402405
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
403406
RunID: runID,
404407
ArtifactName: itemPath,
408+
Status: int(actions.ArtifactStatusUploadConfirmed),
405409
})
406410
if err != nil {
407411
log.Error("Error getting artifacts: %v", err)
@@ -473,6 +477,11 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
473477
ctx.HTTPError(http.StatusBadRequest)
474478
return
475479
}
480+
if artifact.Status != actions.ArtifactStatusUploadConfirmed {
481+
log.Error("Error artifact not found: %s", artifact.Status.ToString())
482+
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
483+
return
484+
}
476485

477486
fd, err := ar.fs.Open(artifact.StoragePath)
478487
if err != nil {

routers/api/actions/artifactsv4.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -448,17 +448,15 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
448448
return
449449
}
450450

451-
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
451+
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
452+
RunID: runID,
453+
Status: int(actions.ArtifactStatusUploadConfirmed),
454+
})
452455
if err != nil {
453456
log.Error("Error getting artifacts: %v", err)
454457
ctx.HTTPError(http.StatusInternalServerError, err.Error())
455458
return
456459
}
457-
if len(artifacts) == 0 {
458-
log.Debug("[artifact] handleListArtifacts, no artifacts")
459-
ctx.HTTPError(http.StatusNotFound)
460-
return
461-
}
462460

463461
list := []*ListArtifactsResponse_MonolithArtifact{}
464462

@@ -510,6 +508,11 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
510508
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
511509
return
512510
}
511+
if artifact.Status != actions.ArtifactStatusUploadConfirmed {
512+
log.Error("Error artifact not found: %s", artifact.Status.ToString())
513+
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
514+
return
515+
}
513516

514517
respData := GetSignedArtifactURLResponse{}
515518

@@ -538,6 +541,11 @@ func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
538541
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
539542
return
540543
}
544+
if artifact.Status != actions.ArtifactStatusUploadConfirmed {
545+
log.Error("Error artifact not found: %s", artifact.Status.ToString())
546+
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
547+
return
548+
}
541549

542550
file, _ := r.fs.Open(artifact.StoragePath)
543551

tests/integration/api_actions_artifact_v4_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,26 @@ func TestActionsArtifactV4Delete(t *testing.T) {
557557
var deleteResp actions.DeleteArtifactResponse
558558
protojson.Unmarshal(resp.Body.Bytes(), &deleteResp)
559559
assert.True(t, deleteResp.Ok)
560+
561+
// confirm artifact is no longer accessible by GetSignedArtifactURL
562+
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{
563+
Name: "artifact-v4-download",
564+
WorkflowRunBackendId: "792",
565+
WorkflowJobRunBackendId: "193",
566+
})).
567+
AddTokenAuth(token)
568+
_ = MakeRequest(t, req, http.StatusNotFound)
569+
570+
// confirm artifact is no longer enumerateable by ListArtifacts and returns length == 0 without error
571+
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{
572+
NameFilter: wrapperspb.String("artifact-v4-download"),
573+
WorkflowRunBackendId: "792",
574+
WorkflowJobRunBackendId: "193",
575+
})).AddTokenAuth(token)
576+
resp = MakeRequest(t, req, http.StatusOK)
577+
var listResp actions.ListArtifactsResponse
578+
protojson.Unmarshal(resp.Body.Bytes(), &listResp)
579+
assert.Empty(t, listResp.Artifacts)
560580
}
561581

562582
func TestActionsArtifactV4DeletePublicApi(t *testing.T) {

0 commit comments

Comments
 (0)