1
1
mirror of https://github.com/go-gitea/gitea synced 2025-08-06 09:38:21 +00:00

Add endpoint deleting workflow run (#34337)

Add endpoint deleting workflow run
Resolves #26219

/claim #26219

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
NorthRealm
2025-05-14 03:18:13 +08:00
committed by GitHub
parent a0595add72
commit 1e2f3514b9
22 changed files with 691 additions and 43 deletions

View File

@@ -5,12 +5,14 @@ package actions
import (
"context"
"errors"
"fmt"
"time"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
@@ -27,7 +29,7 @@ func Cleanup(ctx context.Context) error {
}
// clean up old logs
if err := CleanupLogs(ctx); err != nil {
if err := CleanupExpiredLogs(ctx); err != nil {
return fmt.Errorf("cleanup logs: %w", err)
}
@@ -98,8 +100,15 @@ func cleanNeedDeleteArtifacts(taskCtx context.Context) error {
const deleteLogBatchSize = 100
// CleanupLogs removes logs which are older than the configured retention time
func CleanupLogs(ctx context.Context) error {
func removeTaskLog(ctx context.Context, task *actions_model.ActionTask) {
if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
// do not return error here, go on
}
}
// CleanupExpiredLogs removes logs which are older than the configured retention time
func CleanupExpiredLogs(ctx context.Context) error {
olderThan := timeutil.TimeStampNow().AddDuration(-time.Duration(setting.Actions.LogRetentionDays) * 24 * time.Hour)
count := 0
@@ -109,10 +118,7 @@ func CleanupLogs(ctx context.Context) error {
return fmt.Errorf("find old tasks: %w", err)
}
for _, task := range tasks {
if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
// do not return error here, go on
}
removeTaskLog(ctx, task)
task.LogIndexes = nil // clear log indexes since it's a heavy field
task.LogExpired = true
if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_expired"); err != nil {
@@ -148,3 +154,91 @@ func CleanupEphemeralRunners(ctx context.Context) error {
log.Info("Removed %d runners", affected)
return nil
}
// DeleteRun deletes workflow run, including all logs and artifacts.
func DeleteRun(ctx context.Context, run *actions_model.ActionRun) error {
if !run.Status.IsDone() {
return errors.New("run is not done")
}
repoID := run.RepoID
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
return err
}
jobIDs := container.FilterSlice(jobs, func(j *actions_model.ActionRunJob) (int64, bool) {
return j.ID, true
})
tasks := make(actions_model.TaskList, 0)
if len(jobIDs) > 0 {
if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).In("job_id", jobIDs).Find(&tasks); err != nil {
return err
}
}
artifacts, err := db.Find[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
RepoID: repoID,
RunID: run.ID,
})
if err != nil {
return err
}
var recordsToDelete []any
recordsToDelete = append(recordsToDelete, &actions_model.ActionRun{
RepoID: repoID,
ID: run.ID,
})
recordsToDelete = append(recordsToDelete, &actions_model.ActionRunJob{
RepoID: repoID,
RunID: run.ID,
})
for _, tas := range tasks {
recordsToDelete = append(recordsToDelete, &actions_model.ActionTask{
RepoID: repoID,
ID: tas.ID,
})
recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskStep{
RepoID: repoID,
TaskID: tas.ID,
})
recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskOutput{
TaskID: tas.ID,
})
}
recordsToDelete = append(recordsToDelete, &actions_model.ActionArtifact{
RepoID: repoID,
RunID: run.ID,
})
if err := db.WithTx(ctx, func(ctx context.Context) error {
// TODO: Deleting task records could break current ephemeral runner implementation. This is a temporary workaround suggested by ChristopherHX.
// Since you delete potentially the only task an ephemeral act_runner has ever run, please delete the affected runners first.
// one of
// call cleanup ephemeral runners first
// delete affected ephemeral act_runners
// I would make ephemeral runners fully delete directly before formally finishing the task
//
// See also: https://github.com/go-gitea/gitea/pull/34337#issuecomment-2862222788
if err := CleanupEphemeralRunners(ctx); err != nil {
return err
}
return db.DeleteBeans(ctx, recordsToDelete...)
}); err != nil {
return err
}
// Delete files on storage
for _, tas := range tasks {
removeTaskLog(ctx, tas)
}
for _, art := range artifacts {
if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil {
log.Error("remove artifact file %q: %v", art.StoragePath, err)
}
}
return nil
}

View File

@@ -245,7 +245,7 @@ func APIContexter() func(http.Handler) http.Handler {
// APIErrorNotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) APIErrorNotFound(objs ...any) {
message := ctx.Locale.TrString("error.not_found")
var message string
var errs []string
for _, obj := range objs {
// Ignore nil
@@ -259,9 +259,8 @@ func (ctx *APIContext) APIErrorNotFound(objs ...any) {
message = obj.(string)
}
}
ctx.JSON(http.StatusNotFound, map[string]any{
"message": message,
"message": util.IfZero(message, "not found"), // do not use locale in API
"url": setting.API.SwaggerURL,
"errors": errs,
})

View File

@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
@@ -261,3 +262,11 @@ func (ctx *Context) JSONError(msg any) {
panic(fmt.Sprintf("unsupported type: %T", msg))
}
}
func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
msg := util.OptionalArg(optMsg)
if msg == "" {
msg = ctx.Locale.TrString("error.not_found")
}
ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
}