mirror of https://github.com/go-gitea/gitea.git
Browse Source
Related to #33709, #31416 It's similar with https://docs.github.com/en/rest/actions/workflow-jobs?apiVersion=2022-11-28#download-job-logs-for-a-workflow-run--code-samples. This use `job_id` as path parameter which is consistent with Github's APIs. --------- Co-authored-by: ChristopherHX <christopher.homberger@web.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>pull/34032/head
8 changed files with 264 additions and 52 deletions
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo |
||||
|
||||
import ( |
||||
"errors" |
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions" |
||||
"code.gitea.io/gitea/modules/util" |
||||
"code.gitea.io/gitea/routers/common" |
||||
"code.gitea.io/gitea/services/context" |
||||
) |
||||
|
||||
func DownloadActionsRunJobLogs(ctx *context.APIContext) { |
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs repository downloadActionsRunJobLogs
|
||||
// ---
|
||||
// summary: Downloads the job logs 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: job_id
|
||||
// in: path
|
||||
// description: id of the job
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// description: output blob content
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
jobID := ctx.PathParamInt64("job_id") |
||||
curJob, err := actions_model.GetRunJobByID(ctx, jobID) |
||||
if err != nil { |
||||
ctx.APIErrorInternal(err) |
||||
return |
||||
} |
||||
if err = curJob.LoadRepo(ctx); err != nil { |
||||
ctx.APIErrorInternal(err) |
||||
return |
||||
} |
||||
|
||||
err = common.DownloadActionsRunJobLogs(ctx.Base, ctx.Repo.Repository, curJob) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrNotExist) { |
||||
ctx.APIErrorNotFound(err) |
||||
} else { |
||||
ctx.APIErrorInternal(err) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions" |
||||
repo_model "code.gitea.io/gitea/models/repo" |
||||
"code.gitea.io/gitea/modules/actions" |
||||
"code.gitea.io/gitea/modules/util" |
||||
"code.gitea.io/gitea/services/context" |
||||
) |
||||
|
||||
func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobIndex int64) error { |
||||
runJobs, err := actions_model.GetRunJobsByRunID(ctx, runID) |
||||
if err != nil { |
||||
return fmt.Errorf("GetRunJobsByRunID: %w", err) |
||||
} |
||||
if err = runJobs.LoadRepos(ctx); err != nil { |
||||
return fmt.Errorf("LoadRepos: %w", err) |
||||
} |
||||
if 0 < jobIndex || jobIndex >= int64(len(runJobs)) { |
||||
return util.NewNotExistErrorf("job index is out of range: %d", jobIndex) |
||||
} |
||||
return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex]) |
||||
} |
||||
|
||||
func DownloadActionsRunJobLogs(ctx *context.Base, ctxRepo *repo_model.Repository, curJob *actions_model.ActionRunJob) error { |
||||
if curJob.Repo.ID != ctxRepo.ID { |
||||
return util.NewNotExistErrorf("job not found") |
||||
} |
||||
|
||||
if curJob.TaskID == 0 { |
||||
return util.NewNotExistErrorf("job not started") |
||||
} |
||||
|
||||
if err := curJob.LoadRun(ctx); err != nil { |
||||
return fmt.Errorf("LoadRun: %w", err) |
||||
} |
||||
|
||||
task, err := actions_model.GetTaskByID(ctx, curJob.TaskID) |
||||
if err != nil { |
||||
return fmt.Errorf("GetTaskByID: %w", err) |
||||
} |
||||
|
||||
if task.LogExpired { |
||||
return util.NewNotExistErrorf("logs have been cleaned up") |
||||
} |
||||
|
||||
reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) |
||||
if err != nil { |
||||
return fmt.Errorf("OpenLogs: %w", err) |
||||
} |
||||
defer reader.Close() |
||||
|
||||
workflowName := curJob.Run.WorkflowID |
||||
if p := strings.Index(workflowName, "."); p > 0 { |
||||
workflowName = workflowName[0:p] |
||||
} |
||||
ctx.ServeContent(reader, &context.ServeHeaderOptions{ |
||||
Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, curJob.Name, task.ID), |
||||
ContentLength: &task.LogSize, |
||||
ContentType: "text/plain", |
||||
ContentTypeCharset: "utf-8", |
||||
Disposition: "attachment", |
||||
}) |
||||
return nil |
||||
} |
||||
Loading…
Reference in new issue