From b1b0ef3a49213dca828213a2e9175a5ff5cd3ed2 Mon Sep 17 00:00:00 2001 From: Christopher Homberger Date: Thu, 6 Feb 2025 11:54:58 +0100 Subject: [PATCH] add DeleteArtifact api --- routers/api/v1/api.go | 5 +- routers/api/v1/repo/action.go | 48 ++++++++++++++++++ templates/swagger/v1_json.tmpl | 44 ++++++++++++++++ .../api_actions_artifact_v4_test.go | 50 ++++++++++++++++++- 4 files changed, 145 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 709ef255955d9..3517c780d31ec 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1237,7 +1237,10 @@ func Routes() *web.Router { m.Get("/tasks", repo.ListActionTasks) m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun) m.Get("/artifacts", repo.GetArtifacts) - m.Get("/artifacts/{artifact_id}", repo.GetArtifact) + m.Group("/artifacts/{artifact_id}", func() { + m.Get("", repo.GetArtifact) + m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact) + }) m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact) m.Get("/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw) }, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true)) diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 6ff671e95e44c..5016a9e66e4cf 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -756,6 +756,54 @@ func GetArtifact(ctx *context.APIContext) { ctx.Error(http.StatusNotFound, "artifact not found", fmt.Errorf("artifact not found")) } +// DeleteArtifact Deletes a specific artifact for a workflow run. +func DeleteArtifact(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id} repository deleteArtifact + // --- + // summary: Deletes a specific artifact for a workflow run + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: artifact_id + // in: path + // description: id of the artifact + // type: string + // required: true + // responses: + // "204": + // description: "No Content" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + art, ok := getArtifactByID(ctx) + if !ok { + return + } + + if actions.IsArtifactV4(art) { + if err := actions_model.SetArtifactNeedDelete(ctx, art.RunID, art.ArtifactName); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error(), err) + return + } + ctx.Status(http.StatusNoContent) + return + } + // v3 not supported due to not having one unique id + ctx.Error(http.StatusNotFound, "artifact not found", fmt.Errorf("artifact not found")) +} + // DownloadArtifact Downloads a specific artifact for a workflow run redirects to blob url. func DownloadArtifact(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/zip repository downloadArtifact diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index afcd1942c03cf..f107cf064a75d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4002,6 +4002,50 @@ "$ref": "#/responses/notFound" } } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Deletes a specific artifact for a workflow run", + "operationId": "deleteArtifact", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "id of the artifact", + "name": "artifact_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } } }, "/repos/{owner}/{repo}/actions/artifacts/{artifact_id}/zip": { diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index aee75e6354a53..268ccb6e08c77 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -477,7 +477,7 @@ func TestActionsArtifactV4DownloadArtifactCorrectRepoOwnerFound(t *testing.T) { session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - // confirm artifacts of wrong owner or repo is not visible + // confirm artifacts of correct owner and repo is visible req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). AddTokenAuth(token) MakeRequest(t, req, http.StatusFound) @@ -514,3 +514,51 @@ func TestActionsArtifactV4Delete(t *testing.T) { protojson.Unmarshal(resp.Body.Bytes(), &deleteResp) assert.True(t, deleteResp.Ok) } + +func TestActionsArtifactV4DeletePublicApi(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // confirm artifacts has been deleted + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestActionsArtifactV4DeletePublicApiNotAllowedReadScope(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // try delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + // confirm artifacts has not been deleted + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) +}