diff --git a/models/actions/run.go b/models/actions/run.go index 60fbbcd323..d958218650 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -435,3 +435,56 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error { } type ActionRunIndex db.ResourceIndex + +// DeleteActionRunAndChild delete action_task_step, action_task_output, action_task, action_run and action_run_job. +func DeleteActionRunAndChild(ctx context.Context, runIDs, jobIDs, taskIDs []int64) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + _, err = db.GetEngine(ctx).In("task_id", taskIDs). + Delete(ActionTaskStep{}) + if err != nil { + return err + } + + _, err = db.GetEngine(ctx).In("task_id", taskIDs). + Delete(ActionTaskOutput{}) + if err != nil { + return err + } + + _, err = db.GetEngine(ctx).In("id", taskIDs).Delete(ActionTask{}) + if err != nil { + return err + } + + _, err = db.GetEngine(ctx).In("id", jobIDs).Delete(ActionRunJob{}) + if err != nil { + return err + } + + _, err = db.GetEngine(ctx).In("id", runIDs).Delete(ActionRun{}) + if err != nil { + return err + } + + return committer.Commit() +} + +// GetRunsByIDsAndTriggerUserID -- get all action run by trigger user with selected ids +func GetRunsByIDsAndTriggerUserID(ctx context.Context, ids []int64, triggerUserID int64) ([]*ActionRun, error) { + var runs []*ActionRun + err := db.GetEngine(ctx).Where("trigger_user_id=?", triggerUserID). + In("id", ids).Find(&runs) + if err != nil { + return nil, err + } + if len(runs) < 1 { + return nil, fmt.Errorf("run with ids %d: %w", ids, util.ErrNotExist) + } + + return runs, nil +} diff --git a/models/actions/run_job.go b/models/actions/run_job.go index de4b6aab66..17b0fe9c63 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -184,3 +184,19 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status { return StatusUnknown // it shouldn't happen } } + +func GetRunJobsByRunIDs(ctx context.Context, runIDs []int64) ([]*ActionRunJob, error) { + var jobs []*ActionRunJob + if err := db.GetEngine(ctx).In("run_id", runIDs).Find(&jobs); err != nil { + return nil, err + } + return jobs, nil +} + +func GetRunTasksByJobIDs(ctx context.Context, jobIDs []int64) ([]*ActionTask, error) { + var tasks []*ActionTask + if err := db.GetEngine(ctx).In("job_id", jobIDs).Find(&tasks); err != nil { + return nil, err + } + return tasks, nil +} diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index d07d195713..e711651b88 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -7,6 +7,8 @@ import ( "bytes" stdCtx "context" "net/http" + "os" + "path/filepath" "slices" "strings" @@ -18,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -28,6 +31,7 @@ import ( "code.gitea.io/gitea/services/convert" "github.com/nektos/act/pkg/model" + "golang.org/x/sync/errgroup" "gopkg.in/yaml.v3" ) @@ -312,6 +316,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0 + ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin) } // loadIsRefDeleted loads the IsRefDeleted field for each run in the list. @@ -424,3 +429,94 @@ func decodeNode(node yaml.Node, out any) bool { } return true } + +func DeleteRuns(ctx *context.Context) { + rd := ctx.Req.Body + defer rd.Close() + + req := DeleteRunsRequest{} + if err := json.NewDecoder(rd).Decode(&req); err != nil { + ctx.ServerError("failed to decode request body into delte runs request", err) + return + } + if len(req.ActionIDs) < 1 { + ctx.ServerError("missing action_run.id for delete action run", nil) + return + } + + var ( + eg = new(errgroup.Group) + actionRuns []*actions_model.ActionRun + jobIDs, taskIDs []int64 + taskLogFileNames []string + ) + eg.Go(func() error { + var err error + actionRuns, err = actions_model.GetRunsByIDsAndTriggerUserID(ctx, req.ActionIDs, ctx.Doer.ID) + return err + }) + + eg.Go(func() error { + actionRunJobs, err := actions_model.GetRunJobsByRunIDs(ctx, req.ActionIDs) + if err != nil { + return err + } + + for _, actionRunJob := range actionRunJobs { + jobIDs = append(jobIDs, actionRunJob.ID) + } + actionTasks, err := actions_model.GetRunTasksByJobIDs(ctx, jobIDs) + if err != nil { + return err + } + + for _, actionTask := range actionTasks { + taskIDs = append(taskIDs, actionTask.ID) + taskLogFileNames = append(taskLogFileNames, actionTask.LogFilename) + } + return nil + }) + + err := eg.Wait() + if err != nil { + ctx.ServerError("failed to get action runs and action run jobs", err) + return + } + + if len(actionRuns) != len(req.ActionIDs) { + ctx.ServerError("action ids not match with request", nil) + return + } + + err = actions_model.DeleteActionRunAndChild(ctx, req.ActionIDs, jobIDs, taskIDs) + if err != nil { + ctx.ServerError("failed to delete action_run", err) + return + } + + removeActionTaskLogFilenames(taskLogFileNames) + + ctx.Status(http.StatusNoContent) +} + +type DeleteRunsRequest struct { + ActionIDs []int64 `json:"actionIds"` +} + +func removeActionTaskLogFilenames(taskLogFileNames []string) { + dirNameActionLog := "actions_log" + go func() { + for _, taskLogFileName := range taskLogFileNames { + var fileName string + if filepath.IsAbs(setting.AppDataPath) { + fileName = filepath.Join(setting.AppDataPath, dirNameActionLog, taskLogFileName) + } else { + fileName = filepath.Join(setting.AppWorkPath, setting.AppDataPath, dirNameActionLog, taskLogFileName) + } + + if err := os.Remove(fileName); err != nil { + log.Error("failed to remove actions_log file %s: %v", fileName, err) + } + } + }() +} diff --git a/routers/web/web.go b/routers/web/web.go index a5175e8830..bcca5f68c4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1447,6 +1447,8 @@ func registerRoutes(m *web.Router) { m.Group("/workflows/{workflow_name}", func() { m.Get("/badge.svg", actions.GetWorkflowBadge) }) + + m.Post("/runs/delete", reqRepoActionsWriter, actions.DeleteRuns) }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions) // end "/{username}/{reponame}/actions" diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 7d782c0ade..c781d4ee01 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -25,7 +25,19 @@