Refactor commit status for Actions jobs (#23786)
Before: <img width="353" alt="xnip_230329_163852" src="https://user-images.githubusercontent.com/9418365/228479807-424452df-10fa-45cf-ae4b-09939c0ed54c.png"> After: <img width="508" alt="xnip_230329_163358" src="https://user-images.githubusercontent.com/9418365/228479923-537b54fe-9564-4105-a068-bcc75fa2a7ea.png"> Highlights: - Treat `StatusSkipped` as `CommitStatusSuccess` instead of `CommitStatusFailure`, so it fixed #23599. - Use the bot user `gitea-actions` instead of the trigger as the creator of commit status. - New format `<run_name> / <job_name> / (<event>)` for the context of commit status to avoid conflicts. - Add descriptions for commit status. - Add the missing calls to `CreateCommitStatus`. - Refactor `CreateCommitStatus` to make it easier to use.
This commit is contained in:
parent
e57e1144c5
commit
3e8db31a5b
|
@ -149,10 +149,7 @@ func (s *Service) UpdateTask(
|
||||||
return nil, status.Errorf(codes.Internal, "load job: %v", err)
|
return nil, status.Errorf(codes.Internal, "load job: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := actions_service.CreateCommitStatus(ctx, task.Job); err != nil {
|
actions_service.CreateCommitStatus(ctx, task.Job)
|
||||||
log.Error("Update commit status for job %v failed: %v", task.Job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||||
if err := actions_service.EmitJobsIfReady(task.Job.RunID); err != nil {
|
if err := actions_service.EmitJobsIfReady(task.Job.RunID); err != nil {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
secret_module "code.gitea.io/gitea/modules/secret"
|
secret_module "code.gitea.io/gitea/modules/secret"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/services/actions"
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
@ -27,6 +28,8 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actions.CreateCommitStatus(ctx, t.Job)
|
||||||
|
|
||||||
task := &runnerv1.Task{
|
task := &runnerv1.Task{
|
||||||
Id: t.ID,
|
Id: t.ID,
|
||||||
WorkflowPayload: t.Job.WorkflowPayload,
|
WorkflowPayload: t.Job.WorkflowPayload,
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/actions"
|
"code.gitea.io/gitea/modules/actions"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
context_module "code.gitea.io/gitea/modules/context"
|
context_module "code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -264,10 +263,7 @@ func Rerun(ctx *context_module.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
|
actions_service.CreateCommitStatus(ctx, job)
|
||||||
log.Error("Update commit status for job %v failed: %v", job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
@ -308,12 +304,7 @@ func Cancel(ctx *context_module.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range jobs {
|
actions_service.CreateCommitStatus(ctx, jobs...)
|
||||||
if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
|
|
||||||
log.Error("Update commit status for job %v failed: %v", job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
@ -349,6 +340,8 @@ func Approve(ctx *context_module.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actions_service.CreateCommitStatus(ctx, jobs...)
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,12 +64,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range jobs {
|
CreateCommitStatus(ctx, jobs...)
|
||||||
if err := CreateCommitStatus(ctx, job); err != nil {
|
|
||||||
log.Error("Update commit status for job %v failed: %v", job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -96,10 +91,7 @@ func CancelAbandonedJobs(ctx context.Context) error {
|
||||||
log.Warn("cancel abandoned job %v: %v", job.ID, err)
|
log.Warn("cancel abandoned job %v: %v", job.ID, err)
|
||||||
// go on
|
// go on
|
||||||
}
|
}
|
||||||
if err := CreateCommitStatus(ctx, job); err != nil {
|
CreateCommitStatus(ctx, job)
|
||||||
log.Error("Update commit status for job %v failed: %v", job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,79 +6,80 @@ package actions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
|
||||||
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) error {
|
// CreateCommitStatus creates a commit status for the given job.
|
||||||
|
// It won't return an error failed, but will log it, because it's not critical.
|
||||||
|
func CreateCommitStatus(ctx context.Context, jobs ...*actions_model.ActionRunJob) {
|
||||||
|
for _, job := range jobs {
|
||||||
|
if err := createCommitStatus(ctx, job); err != nil {
|
||||||
|
log.Error("Failed to create commit status for job %d: %v", job.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) error {
|
||||||
if err := job.LoadAttributes(ctx); err != nil {
|
if err := job.LoadAttributes(ctx); err != nil {
|
||||||
return fmt.Errorf("load run: %w", err)
|
return fmt.Errorf("load run: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
run := job.Run
|
run := job.Run
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sha string
|
sha string
|
||||||
creatorID int64
|
event string
|
||||||
)
|
)
|
||||||
|
|
||||||
switch run.Event {
|
switch run.Event {
|
||||||
case webhook_module.HookEventPush:
|
case webhook_module.HookEventPush:
|
||||||
|
event = "push"
|
||||||
payload, err := run.GetPushEventPayload()
|
payload, err := run.GetPushEventPayload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetPushEventPayload: %w", err)
|
return fmt.Errorf("GetPushEventPayload: %w", err)
|
||||||
}
|
}
|
||||||
|
if payload.HeadCommit == nil {
|
||||||
// Since the payload comes from json data, we should check if it's broken, or it will cause panic
|
|
||||||
switch {
|
|
||||||
case payload.Repo == nil:
|
|
||||||
return fmt.Errorf("repo is missing in event payload")
|
|
||||||
case payload.Pusher == nil:
|
|
||||||
return fmt.Errorf("pusher is missing in event payload")
|
|
||||||
case payload.HeadCommit == nil:
|
|
||||||
return fmt.Errorf("head commit is missing in event payload")
|
return fmt.Errorf("head commit is missing in event payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
sha = payload.HeadCommit.ID
|
sha = payload.HeadCommit.ID
|
||||||
creatorID = payload.Pusher.ID
|
|
||||||
case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync:
|
case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync:
|
||||||
|
event = "pull_request"
|
||||||
payload, err := run.GetPullRequestEventPayload()
|
payload, err := run.GetPullRequestEventPayload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetPullRequestEventPayload: %w", err)
|
return fmt.Errorf("GetPullRequestEventPayload: %w", err)
|
||||||
}
|
}
|
||||||
|
if payload.PullRequest == nil {
|
||||||
switch {
|
|
||||||
case payload.PullRequest == nil:
|
|
||||||
return fmt.Errorf("pull request is missing in event payload")
|
return fmt.Errorf("pull request is missing in event payload")
|
||||||
case payload.PullRequest.Head == nil:
|
} else if payload.PullRequest.Head == nil {
|
||||||
return fmt.Errorf("head of pull request is missing in event payload")
|
return fmt.Errorf("head of pull request is missing in event payload")
|
||||||
case payload.PullRequest.Head.Repository == nil:
|
|
||||||
return fmt.Errorf("head repository of pull request is missing in event payload")
|
|
||||||
case payload.PullRequest.Head.Repository.Owner == nil:
|
|
||||||
return fmt.Errorf("owner of head repository of pull request is missing in evnt payload")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sha = payload.PullRequest.Head.Sha
|
sha = payload.PullRequest.Head.Sha
|
||||||
creatorID = payload.PullRequest.Head.Repository.Owner.ID
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repo := run.Repo
|
repo := run.Repo
|
||||||
ctxname := job.Name
|
// TODO: store workflow name as a field in ActionRun to avoid parsing
|
||||||
state := toCommitStatus(job.Status)
|
runName := path.Base(run.WorkflowID)
|
||||||
creator, err := user_model.GetUserByID(ctx, creatorID)
|
if wfs, err := jobparser.Parse(job.WorkflowPayload); err == nil && len(wfs) > 0 {
|
||||||
if err != nil {
|
runName = wfs[0].Name
|
||||||
return fmt.Errorf("GetUserByID: %w", err)
|
|
||||||
}
|
}
|
||||||
|
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
|
||||||
|
state := toCommitStatus(job.Status)
|
||||||
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
|
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
|
||||||
for _, v := range statuses {
|
for _, v := range statuses {
|
||||||
if v.Context == ctxname {
|
if v.Context == ctxname {
|
||||||
if v.State == state {
|
if v.State == state {
|
||||||
|
// no need to update
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -88,6 +89,26 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
||||||
return fmt.Errorf("GetLatestCommitStatus: %w", err)
|
return fmt.Errorf("GetLatestCommitStatus: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
description := ""
|
||||||
|
switch job.Status {
|
||||||
|
// TODO: if we want support description in different languages, we need to support i18n placeholders in it
|
||||||
|
case actions_model.StatusSuccess:
|
||||||
|
description = fmt.Sprintf("Successful in %s", job.Duration())
|
||||||
|
case actions_model.StatusFailure:
|
||||||
|
description = fmt.Sprintf("Failing after %s", job.Duration())
|
||||||
|
case actions_model.StatusCancelled:
|
||||||
|
description = "Has been cancelled"
|
||||||
|
case actions_model.StatusSkipped:
|
||||||
|
description = "Has been skipped"
|
||||||
|
case actions_model.StatusRunning:
|
||||||
|
description = "Has started running"
|
||||||
|
case actions_model.StatusWaiting:
|
||||||
|
description = "Waiting to run"
|
||||||
|
case actions_model.StatusBlocked:
|
||||||
|
description = "Blocked by required conditions"
|
||||||
|
}
|
||||||
|
|
||||||
|
creator := user_model.NewActionsUser()
|
||||||
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
|
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
|
@ -95,9 +116,9 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
||||||
CommitStatus: &git_model.CommitStatus{
|
CommitStatus: &git_model.CommitStatus{
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
TargetURL: run.Link(),
|
TargetURL: run.Link(),
|
||||||
Description: "",
|
Description: description,
|
||||||
Context: ctxname,
|
Context: ctxname,
|
||||||
CreatorID: creatorID,
|
CreatorID: creator.ID,
|
||||||
State: state,
|
State: state,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -109,9 +130,9 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
||||||
|
|
||||||
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
|
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
|
||||||
switch status {
|
switch status {
|
||||||
case actions_model.StatusSuccess:
|
case actions_model.StatusSuccess, actions_model.StatusSkipped:
|
||||||
return api.CommitStatusSuccess
|
return api.CommitStatusSuccess
|
||||||
case actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped:
|
case actions_model.StatusFailure, actions_model.StatusCancelled:
|
||||||
return api.CommitStatusFailure
|
return api.CommitStatusFailure
|
||||||
case actions_model.StatusWaiting, actions_model.StatusBlocked:
|
case actions_model.StatusWaiting, actions_model.StatusBlocked:
|
||||||
return api.CommitStatusPending
|
return api.CommitStatusPending
|
||||||
|
|
|
@ -45,11 +45,11 @@ func jobEmitterQueueHandle(data ...queue.Data) []queue.Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkJobsOfRun(ctx context.Context, runID int64) error {
|
func checkJobsOfRun(ctx context.Context, runID int64) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
||||||
jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: runID})
|
jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: runID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
|
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
|
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
|
||||||
|
@ -67,7 +67,11 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
CreateCommitStatus(ctx, jobs...)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type jobStatusResolver struct {
|
type jobStatusResolver struct {
|
||||||
|
|
|
@ -185,12 +185,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||||
if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {
|
if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {
|
||||||
log.Error("FindRunJobs: %v", err)
|
log.Error("FindRunJobs: %v", err)
|
||||||
} else {
|
} else {
|
||||||
for _, job := range jobs {
|
CreateCommitStatus(ctx, jobs...)
|
||||||
if err := CreateCommitStatus(ctx, job); err != nil {
|
|
||||||
log.Error("Update commit status for job %v failed: %v", job.ID, err)
|
|
||||||
// go on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue