| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864 |
- // Copyright 2022 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package actions
-
- import (
- "archive/zip"
- "compress/gzip"
- "context"
- "errors"
- "fmt"
- "html/template"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "time"
-
- actions_model "code.gitea.io/gitea/models/actions"
- "code.gitea.io/gitea/models/db"
- git_model "code.gitea.io/gitea/models/git"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/actions"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/routers/common"
- actions_service "code.gitea.io/gitea/services/actions"
- context_module "code.gitea.io/gitea/services/context"
- notify_service "code.gitea.io/gitea/services/notify"
-
- "github.com/nektos/act/pkg/model"
- "xorm.io/builder"
- )
-
- func getRunIndex(ctx *context_module.Context) int64 {
- // if run param is "latest", get the latest run index
- if ctx.PathParam("run") == "latest" {
- if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil {
- return run.Index
- }
- }
- return ctx.PathParamInt64("run")
- }
-
- func View(ctx *context_module.Context) {
- ctx.Data["PageIsActions"] = true
- runIndex := getRunIndex(ctx)
- jobIndex := ctx.PathParamInt64("job")
- ctx.Data["RunIndex"] = runIndex
- ctx.Data["JobIndex"] = jobIndex
- ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions"
-
- if getRunJobs(ctx, runIndex, jobIndex); ctx.Written() {
- return
- }
-
- ctx.HTML(http.StatusOK, tplViewActions)
- }
-
- func ViewWorkflowFile(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
- return errors.Is(err, util.ErrNotExist)
- }, err)
- return
- }
- commit, err := ctx.Repo.GitRepo.GetCommit(run.CommitSHA)
- if err != nil {
- ctx.NotFoundOrServerError("GetCommit", func(err error) bool {
- return errors.Is(err, util.ErrNotExist)
- }, err)
- return
- }
- rpath, entries, err := actions.ListWorkflows(commit)
- if err != nil {
- ctx.ServerError("ListWorkflows", err)
- return
- }
- for _, entry := range entries {
- if entry.Name() == run.WorkflowID {
- ctx.Redirect(fmt.Sprintf("%s/src/commit/%s/%s/%s", ctx.Repo.RepoLink, url.PathEscape(run.CommitSHA), util.PathEscapeSegments(rpath), util.PathEscapeSegments(run.WorkflowID)))
- return
- }
- }
- ctx.NotFound(nil)
- }
-
- type LogCursor struct {
- Step int `json:"step"`
- Cursor int64 `json:"cursor"`
- Expanded bool `json:"expanded"`
- }
-
- type ViewRequest struct {
- LogCursors []LogCursor `json:"logCursors"`
- }
-
- type ArtifactsViewItem struct {
- Name string `json:"name"`
- Size int64 `json:"size"`
- Status string `json:"status"`
- }
-
- type ViewResponse struct {
- Artifacts []*ArtifactsViewItem `json:"artifacts"`
-
- State struct {
- Run struct {
- Link string `json:"link"`
- Title string `json:"title"`
- TitleHTML template.HTML `json:"titleHTML"`
- Status string `json:"status"`
- CanCancel bool `json:"canCancel"`
- CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
- CanRerun bool `json:"canRerun"`
- CanDeleteArtifact bool `json:"canDeleteArtifact"`
- Done bool `json:"done"`
- WorkflowID string `json:"workflowID"`
- WorkflowLink string `json:"workflowLink"`
- IsSchedule bool `json:"isSchedule"`
- Jobs []*ViewJob `json:"jobs"`
- Commit ViewCommit `json:"commit"`
- } `json:"run"`
- CurrentJob struct {
- Title string `json:"title"`
- Detail string `json:"detail"`
- Steps []*ViewJobStep `json:"steps"`
- } `json:"currentJob"`
- } `json:"state"`
- Logs struct {
- StepsLog []*ViewStepLog `json:"stepsLog"`
- } `json:"logs"`
- }
-
- type ViewJob struct {
- ID int64 `json:"id"`
- Name string `json:"name"`
- Status string `json:"status"`
- CanRerun bool `json:"canRerun"`
- Duration string `json:"duration"`
- }
-
- type ViewCommit struct {
- ShortSha string `json:"shortSHA"`
- Link string `json:"link"`
- Pusher ViewUser `json:"pusher"`
- Branch ViewBranch `json:"branch"`
- }
-
- type ViewUser struct {
- DisplayName string `json:"displayName"`
- Link string `json:"link"`
- }
-
- type ViewBranch struct {
- Name string `json:"name"`
- Link string `json:"link"`
- IsDeleted bool `json:"isDeleted"`
- }
-
- type ViewJobStep struct {
- Summary string `json:"summary"`
- Duration string `json:"duration"`
- Status string `json:"status"`
- }
-
- type ViewStepLog struct {
- Step int `json:"step"`
- Cursor int64 `json:"cursor"`
- Lines []*ViewStepLogLine `json:"lines"`
- Started int64 `json:"started"`
- }
-
- type ViewStepLogLine struct {
- Index int64 `json:"index"`
- Message string `json:"message"`
- Timestamp float64 `json:"timestamp"`
- }
-
- func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artifactsViewItems []*ArtifactsViewItem, err error) {
- run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
- if err != nil {
- return nil, err
- }
- artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
- if err != nil {
- return nil, err
- }
- for _, art := range artifacts {
- artifactsViewItems = append(artifactsViewItems, &ArtifactsViewItem{
- Name: art.ArtifactName,
- Size: art.FileSize,
- Status: util.Iif(art.Status == actions_model.ArtifactStatusExpired, "expired", "completed"),
- })
- }
- return artifactsViewItems, nil
- }
-
- func ViewPost(ctx *context_module.Context) {
- req := web.GetForm(ctx).(*ViewRequest)
- runIndex := getRunIndex(ctx)
- jobIndex := ctx.PathParamInt64("job")
-
- current, jobs := getRunJobs(ctx, runIndex, jobIndex)
- if ctx.Written() {
- return
- }
- run := current.Run
- if err := run.LoadAttributes(ctx); err != nil {
- ctx.ServerError("run.LoadAttributes", err)
- return
- }
-
- var err error
- resp := &ViewResponse{}
- resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- if !errors.Is(err, util.ErrNotExist) {
- ctx.ServerError("getActionsViewArtifacts", err)
- return
- }
- }
-
- // the title for the "run" is from the commit message
- resp.State.Run.Title = run.Title
- resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository)
- resp.State.Run.Link = run.Link()
- resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
- resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
- resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
- resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
- resp.State.Run.Done = run.Status.IsDone()
- resp.State.Run.WorkflowID = run.WorkflowID
- resp.State.Run.WorkflowLink = run.WorkflowLink()
- resp.State.Run.IsSchedule = run.IsSchedule()
- resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
- resp.State.Run.Status = run.Status.String()
- for _, v := range jobs {
- resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{
- ID: v.ID,
- Name: v.Name,
- Status: v.Status.String(),
- CanRerun: resp.State.Run.CanRerun,
- Duration: v.Duration().String(),
- })
- }
-
- pusher := ViewUser{
- DisplayName: run.TriggerUser.GetDisplayName(),
- Link: run.TriggerUser.HomeLink(),
- }
- branch := ViewBranch{
- Name: run.PrettyRef(),
- Link: run.RefLink(),
- }
- refName := git.RefName(run.Ref)
- if refName.IsBranch() {
- b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, refName.ShortName())
- if err != nil && !git_model.IsErrBranchNotExist(err) {
- log.Error("GetBranch: %v", err)
- } else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) {
- branch.IsDeleted = true
- }
- }
-
- resp.State.Run.Commit = ViewCommit{
- ShortSha: base.ShortSha(run.CommitSHA),
- Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
- Pusher: pusher,
- Branch: branch,
- }
-
- var task *actions_model.ActionTask
- if current.TaskID > 0 {
- var err error
- task, err = actions_model.GetTaskByID(ctx, current.TaskID)
- if err != nil {
- ctx.ServerError("actions_model.GetTaskByID", err)
- return
- }
- task.Job = current
- if err := task.LoadAttributes(ctx); err != nil {
- ctx.ServerError("task.LoadAttributes", err)
- return
- }
- }
-
- resp.State.CurrentJob.Title = current.Name
- resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale)
- if run.NeedApproval {
- resp.State.CurrentJob.Detail = ctx.Locale.TrString("actions.need_approval_desc")
- }
- resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json
- resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json
- if task != nil {
- steps, logs, err := convertToViewModel(ctx, req.LogCursors, task)
- if err != nil {
- ctx.ServerError("convertToViewModel", err)
- return
- }
- resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, steps...)
- resp.Logs.StepsLog = append(resp.Logs.StepsLog, logs...)
- }
-
- ctx.JSON(http.StatusOK, resp)
- }
-
- func convertToViewModel(ctx *context_module.Context, cursors []LogCursor, task *actions_model.ActionTask) ([]*ViewJobStep, []*ViewStepLog, error) {
- var viewJobs []*ViewJobStep
- var logs []*ViewStepLog
-
- steps := actions.FullSteps(task)
-
- for _, v := range steps {
- viewJobs = append(viewJobs, &ViewJobStep{
- Summary: v.Name,
- Duration: v.Duration().String(),
- Status: v.Status.String(),
- })
- }
-
- for _, cursor := range cursors {
- if !cursor.Expanded {
- continue
- }
-
- step := steps[cursor.Step]
-
- // if task log is expired, return a consistent log line
- if task.LogExpired {
- if cursor.Cursor == 0 {
- logs = append(logs, &ViewStepLog{
- Step: cursor.Step,
- Cursor: 1,
- Lines: []*ViewStepLogLine{
- {
- Index: 1,
- Message: ctx.Locale.TrString("actions.runs.expire_log_message"),
- // Timestamp doesn't mean anything when the log is expired.
- // Set it to the task's updated time since it's probably the time when the log has expired.
- Timestamp: float64(task.Updated.AsTime().UnixNano()) / float64(time.Second),
- },
- },
- Started: int64(step.Started),
- })
- }
- continue
- }
-
- logLines := make([]*ViewStepLogLine, 0) // marshal to '[]' instead fo 'null' in json
-
- index := step.LogIndex + cursor.Cursor
- validCursor := cursor.Cursor >= 0 &&
- // !(cursor.Cursor < step.LogLength) when the frontend tries to fetch next line before it's ready.
- // So return the same cursor and empty lines to let the frontend retry.
- cursor.Cursor < step.LogLength &&
- // !(index < task.LogIndexes[index]) when task data is older than step data.
- // It can be fixed by making sure write/read tasks and steps in the same transaction,
- // but it's easier to just treat it as fetching the next line before it's ready.
- index < int64(len(task.LogIndexes))
-
- if validCursor {
- length := step.LogLength - cursor.Cursor
- offset := task.LogIndexes[index]
- logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length)
- if err != nil {
- return nil, nil, fmt.Errorf("actions.ReadLogs: %w", err)
- }
-
- for i, row := range logRows {
- logLines = append(logLines, &ViewStepLogLine{
- Index: cursor.Cursor + int64(i) + 1, // start at 1
- Message: row.Content,
- Timestamp: float64(row.Time.AsTime().UnixNano()) / float64(time.Second),
- })
- }
- }
-
- logs = append(logs, &ViewStepLog{
- Step: cursor.Step,
- Cursor: cursor.Cursor + int64(len(logLines)),
- Lines: logLines,
- Started: int64(step.Started),
- })
- }
-
- return viewJobs, logs, nil
- }
-
- // Rerun will rerun jobs in the given run
- // If jobIndexStr is a blank string, it means rerun all jobs
- func Rerun(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- jobIndexStr := ctx.PathParam("job")
- var jobIndex int64
- if jobIndexStr != "" {
- jobIndex, _ = strconv.ParseInt(jobIndexStr, 10, 64)
- }
-
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- ctx.ServerError("GetRunByIndex", err)
- return
- }
-
- // can not rerun job when workflow is disabled
- cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
- cfg := cfgUnit.ActionsConfig()
- if cfg.IsWorkflowDisabled(run.WorkflowID) {
- ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
- return
- }
-
- // reset run's start and stop time when it is done
- if run.Status.IsDone() {
- run.PreviousDuration = run.Duration()
- run.Started = 0
- run.Stopped = 0
- if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil {
- ctx.ServerError("UpdateRun", err)
- return
- }
-
- if err := run.LoadAttributes(ctx); err != nil {
- ctx.ServerError("run.LoadAttributes", err)
- return
- }
- notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
- }
-
- job, jobs := getRunJobs(ctx, runIndex, jobIndex)
- if ctx.Written() {
- return
- }
-
- if jobIndexStr == "" { // rerun all jobs
- for _, j := range jobs {
- // if the job has needs, it should be set to "blocked" status to wait for other jobs
- shouldBlock := len(j.Needs) > 0
- if err := rerunJob(ctx, j, shouldBlock); err != nil {
- ctx.ServerError("RerunJob", err)
- return
- }
- }
- ctx.JSONOK()
- return
- }
-
- rerunJobs := actions_service.GetAllRerunJobs(job, jobs)
-
- for _, j := range rerunJobs {
- // jobs other than the specified one should be set to "blocked" status
- shouldBlock := j.JobID != job.JobID
- if err := rerunJob(ctx, j, shouldBlock); err != nil {
- ctx.ServerError("RerunJob", err)
- return
- }
- }
-
- ctx.JSONOK()
- }
-
- func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
- status := job.Status
- if !status.IsDone() || !job.Run.Status.IsDone() {
- return nil
- }
-
- job.TaskID = 0
- job.Status = actions_model.StatusWaiting
- if shouldBlock {
- job.Status = actions_model.StatusBlocked
- }
- job.Started = 0
- job.Stopped = 0
-
- if err := db.WithTx(ctx, func(ctx context.Context) error {
- _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped")
- return err
- }); err != nil {
- return err
- }
-
- actions_service.CreateCommitStatus(ctx, job)
- notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
-
- return nil
- }
-
- func Logs(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- jobIndex := ctx.PathParamInt64("job")
-
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
- return errors.Is(err, util.ErrNotExist)
- }, err)
- return
- }
-
- if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, run.ID, jobIndex); err != nil {
- ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithIndex", func(err error) bool {
- return errors.Is(err, util.ErrNotExist)
- }, err)
- }
- }
-
- func Cancel(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
-
- _, jobs := getRunJobs(ctx, runIndex, -1)
- if ctx.Written() {
- return
- }
-
- var updatedjobs []*actions_model.ActionRunJob
-
- if err := db.WithTx(ctx, func(ctx context.Context) error {
- for _, job := range jobs {
- status := job.Status
- if status.IsDone() {
- continue
- }
- if job.TaskID == 0 {
- job.Status = actions_model.StatusCancelled
- job.Stopped = timeutil.TimeStampNow()
- n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
- if err != nil {
- return err
- }
- if n == 0 {
- return errors.New("job has changed, try again")
- }
- if n > 0 {
- updatedjobs = append(updatedjobs, job)
- }
- continue
- }
- if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil {
- return err
- }
- }
- return nil
- }); err != nil {
- ctx.ServerError("StopTask", err)
- return
- }
-
- actions_service.CreateCommitStatus(ctx, jobs...)
-
- for _, job := range updatedjobs {
- _ = job.LoadAttributes(ctx)
- notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
- }
- if len(updatedjobs) > 0 {
- job := updatedjobs[0]
- actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
- }
- ctx.JSONOK()
- }
-
- func Approve(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
-
- current, jobs := getRunJobs(ctx, runIndex, -1)
- if ctx.Written() {
- return
- }
- run := current.Run
- doer := ctx.Doer
-
- var updatedjobs []*actions_model.ActionRunJob
-
- if err := db.WithTx(ctx, func(ctx context.Context) error {
- run.NeedApproval = false
- run.ApprovedBy = doer.ID
- if err := actions_model.UpdateRun(ctx, run, "need_approval", "approved_by"); err != nil {
- return err
- }
- for _, job := range jobs {
- if len(job.Needs) == 0 && job.Status.IsBlocked() {
- job.Status = actions_model.StatusWaiting
- n, err := actions_model.UpdateRunJob(ctx, job, nil, "status")
- if err != nil {
- return err
- }
- if n > 0 {
- updatedjobs = append(updatedjobs, job)
- }
- }
- }
- return nil
- }); err != nil {
- ctx.ServerError("UpdateRunJob", err)
- return
- }
-
- actions_service.CreateCommitStatus(ctx, jobs...)
-
- if len(updatedjobs) > 0 {
- job := updatedjobs[0]
- actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
- }
-
- for _, job := range updatedjobs {
- _ = job.LoadAttributes(ctx)
- notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
- }
-
- ctx.JSONOK()
- }
-
- func Delete(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- repoID := ctx.Repo.Repository.ID
-
- run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.JSONErrorNotFound()
- return
- }
- ctx.ServerError("GetRunByIndex", err)
- return
- }
-
- if !run.Status.IsDone() {
- ctx.JSONError(ctx.Tr("actions.runs.not_done"))
- return
- }
-
- if err := actions_service.DeleteRun(ctx, run); err != nil {
- ctx.ServerError("DeleteRun", err)
- return
- }
-
- ctx.JSONOK()
- }
-
- // getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
- // Any error will be written to the ctx.
- // It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
- func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) {
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.NotFound(nil)
- return nil, nil
- }
- ctx.ServerError("GetRunByIndex", err)
- return nil, nil
- }
- run.Repo = ctx.Repo.Repository
- jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
- if err != nil {
- ctx.ServerError("GetRunJobsByRunID", err)
- return nil, nil
- }
- if len(jobs) == 0 {
- ctx.NotFound(nil)
- return nil, nil
- }
-
- for _, v := range jobs {
- v.Run = run
- }
-
- if jobIndex >= 0 && jobIndex < int64(len(jobs)) {
- return jobs[jobIndex], jobs
- }
- return jobs[0], jobs
- }
-
- func ArtifactsDeleteView(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- artifactName := ctx.PathParam("artifact_name")
-
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
- return errors.Is(err, util.ErrNotExist)
- }, err)
- return
- }
- if err = actions_model.SetArtifactNeedDelete(ctx, run.ID, artifactName); err != nil {
- ctx.ServerError("SetArtifactNeedDelete", err)
- return
- }
- ctx.JSON(http.StatusOK, struct{}{})
- }
-
- func ArtifactsDownloadView(ctx *context_module.Context) {
- runIndex := getRunIndex(ctx)
- artifactName := ctx.PathParam("artifact_name")
-
- run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.HTTPError(http.StatusNotFound, err.Error())
- return
- }
- ctx.ServerError("GetRunByIndex", err)
- return
- }
-
- artifacts, err := db.Find[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
- RunID: run.ID,
- ArtifactName: artifactName,
- })
- if err != nil {
- ctx.ServerError("FindArtifacts", err)
- return
- }
- if len(artifacts) == 0 {
- ctx.HTTPError(http.StatusNotFound, "artifact not found")
- return
- }
-
- // if artifacts status is not uploaded-confirmed, treat it as not found
- for _, art := range artifacts {
- if art.Status != actions_model.ArtifactStatusUploadConfirmed {
- ctx.HTTPError(http.StatusNotFound, "artifact not found")
- return
- }
- }
-
- ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
-
- if len(artifacts) == 1 && actions.IsArtifactV4(artifacts[0]) {
- err := actions.DownloadArtifactV4(ctx.Base, artifacts[0])
- if err != nil {
- ctx.ServerError("DownloadArtifactV4", err)
- return
- }
- return
- }
-
- // Artifacts using the v1-v3 backend are stored as multiple individual files per artifact on the backend
- // Those need to be zipped for download
- writer := zip.NewWriter(ctx.Resp)
- defer writer.Close()
- for _, art := range artifacts {
- f, err := storage.ActionsArtifacts.Open(art.StoragePath)
- if err != nil {
- ctx.ServerError("ActionsArtifacts.Open", err)
- return
- }
-
- var r io.ReadCloser
- if art.ContentEncoding == "gzip" {
- r, err = gzip.NewReader(f)
- if err != nil {
- ctx.ServerError("gzip.NewReader", err)
- return
- }
- } else {
- r = f
- }
- defer r.Close()
-
- w, err := writer.Create(art.ArtifactPath)
- if err != nil {
- ctx.ServerError("writer.Create", err)
- return
- }
- if _, err := io.Copy(w, r); err != nil {
- ctx.ServerError("io.Copy", err)
- return
- }
- }
- }
-
- func DisableWorkflowFile(ctx *context_module.Context) {
- disableOrEnableWorkflowFile(ctx, false)
- }
-
- func EnableWorkflowFile(ctx *context_module.Context) {
- disableOrEnableWorkflowFile(ctx, true)
- }
-
- func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
- workflow := ctx.FormString("workflow")
- if len(workflow) == 0 {
- ctx.ServerError("workflow", nil)
- return
- }
-
- cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
- cfg := cfgUnit.ActionsConfig()
-
- if isEnable {
- cfg.EnableWorkflow(workflow)
- } else {
- cfg.DisableWorkflow(workflow)
- }
-
- if err := repo_model.UpdateRepoUnit(ctx, cfgUnit); err != nil {
- ctx.ServerError("UpdateRepoUnit", err)
- return
- }
-
- if isEnable {
- ctx.Flash.Success(ctx.Tr("actions.workflow.enable_success", workflow))
- } else {
- ctx.Flash.Success(ctx.Tr("actions.workflow.disable_success", workflow))
- }
-
- redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(workflow),
- url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
- ctx.JSONRedirect(redirectURL)
- }
-
- func Run(ctx *context_module.Context) {
- redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(ctx.FormString("workflow")),
- url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
-
- workflowID := ctx.FormString("workflow")
- if len(workflowID) == 0 {
- ctx.ServerError("workflow", nil)
- return
- }
-
- ref := ctx.FormString("ref")
- if len(ref) == 0 {
- ctx.ServerError("ref", nil)
- return
- }
- err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
- for name, config := range workflowDispatch.Inputs {
- value := ctx.Req.PostFormValue(name)
- if config.Type == "boolean" {
- inputs[name] = strconv.FormatBool(ctx.FormBool(name))
- } else if value != "" {
- inputs[name] = value
- } else {
- inputs[name] = config.Default
- }
- }
- return nil
- })
- if err != nil {
- if errLocale := util.ErrorAsLocale(err); errLocale != nil {
- ctx.Flash.Error(ctx.Tr(errLocale.TrKey, errLocale.TrArgs...))
- ctx.Redirect(redirectURL)
- } else {
- ctx.ServerError("DispatchActionWorkflow", err)
- }
- return
- }
-
- ctx.Flash.Success(ctx.Tr("actions.workflow.run_success", workflowID))
- ctx.Redirect(redirectURL)
- }
|