新增:流水线接口代码
This commit is contained in:
parent
520306d632
commit
0333de46ce
|
@ -29,6 +29,7 @@ import (
|
|||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/admin"
|
||||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/org"
|
||||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
|
||||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo/actions"
|
||||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/user"
|
||||
"github.com/go-chi/cors"
|
||||
)
|
||||
|
@ -122,6 +123,12 @@ func Routers() *web.Route {
|
|||
m.Post("/create_pr_version", bind(gitea_api.PullRequestPayload{}), repo.CreatePrVersion)
|
||||
m.Group("/repos", func() {
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/actions", func() {
|
||||
m.Get("", context.ReferencesGitRepo(), actions.ListActions)
|
||||
m.Post("/disable", reqAdmin(), actions.DisableWorkflowFile)
|
||||
m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile)
|
||||
m.Post("/runs/{run}/jobs/{job}", context.ReferencesGitRepo(), bind(actions.ViewRequest{}), actions.ListJobs)
|
||||
})
|
||||
m.Post("/transfer", reqOwner(), bind(gitea_api.TransferRepoOption{}), repo.Transfer)
|
||||
m.Get("/branch_name_set", context.ReferencesGitRepo(), repo.BranchNameSet)
|
||||
m.Get("/tag_name_set", context.ReferencesGitRepo(), repo.TagNameSet)
|
||||
|
|
|
@ -0,0 +1,459 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
type Workflow struct {
|
||||
Name string
|
||||
ErrMsg string
|
||||
}
|
||||
|
||||
type ResponseAction struct {
|
||||
Workflows []Workflow
|
||||
CurWorkflow string
|
||||
ActionsConfig *repo_model.ActionsConfig
|
||||
AllowDisableOrEnableWorkflow bool
|
||||
CurWorkflowDisabled bool
|
||||
CurActor int64
|
||||
CurStatus int
|
||||
IsFiltered bool
|
||||
Runs actions_model.RunList
|
||||
Actors []*user.User
|
||||
StatusInfoList []actions_model.StatusInfo
|
||||
}
|
||||
|
||||
func ListActions(ctx *context.APIContext) {
|
||||
|
||||
workflow := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
|
||||
var workflows []Workflow
|
||||
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "IsEmpty", err.Error())
|
||||
return
|
||||
} else if !empty {
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err.Error())
|
||||
return
|
||||
}
|
||||
entries, err := actions.ListWorkflows(commit)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ListWorkflows", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
opts := actions_model.FindRunnerOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WithAvailable: true,
|
||||
}
|
||||
runners, err := actions_model.FindRunners(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindRunners", err.Error())
|
||||
}
|
||||
|
||||
allRunnerLabels := make(container.Set[string])
|
||||
for _, r := range runners {
|
||||
allRunnerLabels.AddMultiple(r.AgentLabels...)
|
||||
}
|
||||
|
||||
workflows = make([]Workflow, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
workflow := Workflow{Name: entry.Name()}
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetContentFromEntry", err.Error())
|
||||
return
|
||||
}
|
||||
wf, err := model.ReadWorkflow(bytes.NewReader(content))
|
||||
if err != nil {
|
||||
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error())
|
||||
workflows = append(workflows, workflow)
|
||||
continue
|
||||
}
|
||||
// Check whether have matching runner
|
||||
for _, j := range wf.Jobs {
|
||||
runsOnList := j.RunsOn()
|
||||
for _, ro := range runsOnList {
|
||||
if strings.Contains(ro, "${{") {
|
||||
// Skip if it contains expressions.
|
||||
// The expressions could be very complex and could not be evaluated here,
|
||||
// so just skip it, it's OK since it's just a tooltip message.
|
||||
continue
|
||||
}
|
||||
if !allRunnerLabels.Contains(ro) {
|
||||
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
|
||||
break
|
||||
}
|
||||
}
|
||||
if workflow.ErrMsg != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
workflows = append(workflows, workflow)
|
||||
}
|
||||
}
|
||||
responseAction := ResponseAction{}
|
||||
responseAction.Workflows = workflows
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
responseAction.CurWorkflow = workflow
|
||||
|
||||
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||
responseAction.ActionsConfig = actionsConfig
|
||||
|
||||
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
|
||||
responseAction.AllowDisableOrEnableWorkflow = true
|
||||
responseAction.CurWorkflowDisabled = actionsConfig.IsWorkflowDisabled(workflow)
|
||||
}
|
||||
|
||||
responseAction.CurActor = actorID
|
||||
responseAction.CurStatus = status
|
||||
if actorID > 0 && status > int(actions_model.StatusUnknown) {
|
||||
responseAction.IsFiltered = true
|
||||
}
|
||||
|
||||
opts := actions_model.FindRunOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
},
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflow,
|
||||
TriggerUserID: actorID,
|
||||
}
|
||||
|
||||
if actions_model.Status(status) != actions_model.StatusUnknown {
|
||||
opts.Status = []actions_model.Status{actions_model.Status(status)}
|
||||
}
|
||||
|
||||
runs, total, err := actions_model.FindRuns(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "FindRuns", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, run := range runs {
|
||||
run.Repo = ctx.Repo.Repository
|
||||
}
|
||||
|
||||
if err := runs.LoadTriggerUser(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadTriggerUser", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
responseAction.Runs = runs
|
||||
|
||||
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetActors", err.Error())
|
||||
return
|
||||
}
|
||||
responseAction.Actors = repo.MakeSelfOnTop(ctx.Doer, actors)
|
||||
responseAction.StatusInfoList = actions_model.GetStatusInfoList(ctx)
|
||||
ctx.SetLinkHeader(int(total), ctx.FormInt("limit"))
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, responseAction)
|
||||
}
|
||||
|
||||
type ViewRequest struct {
|
||||
LogCursors []struct {
|
||||
Step int `json:"step"`
|
||||
Cursor int64 `json:"cursor"`
|
||||
Expanded bool `json:"expanded"`
|
||||
} `json:"logCursors"`
|
||||
}
|
||||
|
||||
type ViewResponse struct {
|
||||
State struct {
|
||||
Run struct {
|
||||
Link string `json:"link"`
|
||||
Title string `json:"title"`
|
||||
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"`
|
||||
Done bool `json:"done"`
|
||||
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 {
|
||||
LocaleCommit string `json:"localeCommit"`
|
||||
LocalePushedBy string `json:"localePushedBy"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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 ListJobs(ctx *context.APIContext) {
|
||||
req := web.GetForm(ctx).(*ViewRequest)
|
||||
|
||||
runIndex := ctx.ParamsInt64("run")
|
||||
jobIndex := ctx.ParamsInt64("job")
|
||||
|
||||
current, jobs := getRunJobs(ctx, runIndex, jobIndex)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
run := current.Run
|
||||
if err := run.LoadAttributes(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := &ViewResponse{}
|
||||
resp.State.Run.Title = run.Title
|
||||
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.Done = run.Status.IsDone()
|
||||
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: v.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions),
|
||||
Duration: v.Duration().String(),
|
||||
})
|
||||
}
|
||||
|
||||
pusher := ViewUser{
|
||||
DisplayName: run.TriggerUser.GetDisplayName(),
|
||||
Link: run.TriggerUser.HomeLink(),
|
||||
}
|
||||
branch := ViewBranch{
|
||||
Name: run.PrettyRef(),
|
||||
Link: run.RefLink(),
|
||||
}
|
||||
resp.State.Run.Commit = ViewCommit{
|
||||
LocaleCommit: ctx.Tr("actions.runs.commit"),
|
||||
LocalePushedBy: ctx.Tr("actions.runs.pushed_by"),
|
||||
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.Error(http.StatusInternalServerError, "GetTaskByID", err.Error())
|
||||
return
|
||||
}
|
||||
task.Job = current
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err.Error())
|
||||
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.Tr("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 := actions.FullSteps(task)
|
||||
|
||||
for _, v := range steps {
|
||||
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &ViewJobStep{
|
||||
Summary: v.Name,
|
||||
Duration: v.Duration().String(),
|
||||
Status: v.Status.String(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, cursor := range req.LogCursors {
|
||||
if !cursor.Expanded {
|
||||
continue
|
||||
}
|
||||
|
||||
step := steps[cursor.Step]
|
||||
|
||||
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]
|
||||
var err error
|
||||
logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ReadLogs", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resp.Logs.StepsLog = append(resp.Logs.StepsLog, &ViewStepLog{
|
||||
Step: cursor.Step,
|
||||
Cursor: cursor.Cursor + int64(len(logLines)),
|
||||
Lines: logLines,
|
||||
Started: int64(step.Started),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func getRunJobs(ctx *context.APIContext, 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.Error(http.StatusNotFound, "GetRunByIndex", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetRunByIndex", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
run.Repo = ctx.Repo.Repository
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetRunJobsByRunID", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
if len(jobs) == 0 {
|
||||
ctx.Error(http.StatusNotFound, "", err.Error())
|
||||
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 DisableWorkflowFile(ctx *context.APIContext) {
|
||||
disableOrEnableWorkflowFile(ctx, false)
|
||||
}
|
||||
|
||||
func EnableWorkflowFile(ctx *context.APIContext) {
|
||||
disableOrEnableWorkflowFile(ctx, true)
|
||||
}
|
||||
|
||||
func disableOrEnableWorkflowFile(ctx *context.APIContext, 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(cfgUnit); err != nil {
|
||||
ctx.ServerError("UpdateRepoUnit", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
Loading…
Reference in New Issue