mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-21 00:48:43 +03:00
Add workflow_job webhook (#33694)
Provide external Integration information about the Queue lossly based on https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=completed#workflow_job Naming conflicts between GitHub & Gitea are here, Blocked => Waiting, Waiting => Queued Rationale Enhancement for ephemeral runners management #33570
This commit is contained in:
@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||||||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||||
"pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release",
|
"pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release",
|
||||||
"package", "status",
|
"package", "status", "workflow_job",
|
||||||
},
|
},
|
||||||
(&Webhook{
|
(&Webhook{
|
||||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||||
|
@ -469,3 +469,18 @@ type CommitStatusPayload struct {
|
|||||||
func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
|
func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
|
||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkflowJobPayload represents a payload information of workflow job event.
|
||||||
|
type WorkflowJobPayload struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
WorkflowJob *ActionWorkflowJob `json:"workflow_job"`
|
||||||
|
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||||
|
Organization *Organization `json:"organization,omitempty"`
|
||||||
|
Repo *Repository `json:"repository"`
|
||||||
|
Sender *User `json:"sender"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONPayload implements Payload
|
||||||
|
func (p *WorkflowJobPayload) JSONPayload() ([]byte, error) {
|
||||||
|
return json.MarshalIndent(p, "", " ")
|
||||||
|
}
|
||||||
|
@ -96,3 +96,40 @@ type ActionArtifactsResponse struct {
|
|||||||
Entries []*ActionArtifact `json:"artifacts"`
|
Entries []*ActionArtifact `json:"artifacts"`
|
||||||
TotalCount int64 `json:"total_count"`
|
TotalCount int64 `json:"total_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionWorkflowStep represents a step of a WorkflowJob
|
||||||
|
type ActionWorkflowStep struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number int64 `json:"number"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Conclusion string `json:"conclusion,omitempty"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
StartedAt time.Time `json:"started_at,omitempty"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
CompletedAt time.Time `json:"completed_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionWorkflowJob represents a WorkflowJob
|
||||||
|
type ActionWorkflowJob struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
|
RunID int64 `json:"run_id"`
|
||||||
|
RunURL string `json:"run_url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
RunAttempt int64 `json:"run_attempt"`
|
||||||
|
HeadSha string `json:"head_sha"`
|
||||||
|
HeadBranch string `json:"head_branch,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Conclusion string `json:"conclusion,omitempty"`
|
||||||
|
RunnerID int64 `json:"runner_id,omitempty"`
|
||||||
|
RunnerName string `json:"runner_name,omitempty"`
|
||||||
|
Steps []*ActionWorkflowStep `json:"steps"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
StartedAt time.Time `json:"started_at,omitempty"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
CompletedAt time.Time `json:"completed_at,omitempty"`
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ const (
|
|||||||
HookEventPullRequestReview HookEventType = "pull_request_review"
|
HookEventPullRequestReview HookEventType = "pull_request_review"
|
||||||
// Actions event only
|
// Actions event only
|
||||||
HookEventSchedule HookEventType = "schedule"
|
HookEventSchedule HookEventType = "schedule"
|
||||||
|
HookEventWorkflowJob HookEventType = "workflow_job"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AllEvents() []HookEventType {
|
func AllEvents() []HookEventType {
|
||||||
@ -66,6 +67,7 @@ func AllEvents() []HookEventType {
|
|||||||
HookEventRelease,
|
HookEventRelease,
|
||||||
HookEventPackage,
|
HookEventPackage,
|
||||||
HookEventStatus,
|
HookEventStatus,
|
||||||
|
HookEventWorkflowJob,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2380,6 +2380,9 @@ settings.event_pull_request_review_request = Pull Request Review Requested
|
|||||||
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
|
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
|
||||||
settings.event_pull_request_approvals = Pull Request Approvals
|
settings.event_pull_request_approvals = Pull Request Approvals
|
||||||
settings.event_pull_request_merge = Pull Request Merge
|
settings.event_pull_request_merge = Pull Request Merge
|
||||||
|
settings.event_header_workflow = Workflow Events
|
||||||
|
settings.event_workflow_job = Workflow Jobs
|
||||||
|
settings.event_workflow_job_desc = Gitea Actions Workflow job queued, waiting, in progress, or completed.
|
||||||
settings.event_package = Package
|
settings.event_package = Package
|
||||||
settings.event_package_desc = Package created or deleted in a repository.
|
settings.event_package_desc = Package created or deleted in a repository.
|
||||||
settings.branch_filter = Branch filter
|
settings.branch_filter = Branch filter
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
||||||
@ -210,7 +211,7 @@ func (s *Service) UpdateTask(
|
|||||||
if err := task.LoadJob(ctx); err != nil {
|
if err := task.LoadJob(ctx); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "load job: %v", err)
|
return nil, status.Errorf(codes.Internal, "load job: %v", err)
|
||||||
}
|
}
|
||||||
if err := task.Job.LoadRun(ctx); err != nil {
|
if err := task.Job.LoadAttributes(ctx); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "load run: %v", err)
|
return nil, status.Errorf(codes.Internal, "load run: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +220,10 @@ func (s *Service) UpdateTask(
|
|||||||
actions_service.CreateCommitStatus(ctx, task.Job)
|
actions_service.CreateCommitStatus(ctx, task.Job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if task.Status.IsDone() {
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, task.Job.Run.Repo, task.Job.Run.TriggerUser, task.Job, task)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
|
log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
|
||||||
|
@ -207,6 +207,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
|||||||
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
||||||
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
||||||
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
||||||
|
webhook_module.HookEventWorkflowJob: util.SliceContainsString(form.Events, string(webhook_module.HookEventWorkflowJob), true),
|
||||||
},
|
},
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
context_module "code.gitea.io/gitea/services/context"
|
context_module "code.gitea.io/gitea/services/context"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
@ -458,6 +459,9 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou
|
|||||||
}
|
}
|
||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, job)
|
actions_service.CreateCommitStatus(ctx, job)
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,6 +522,8 @@ func Cancel(ctx *context_module.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updatedjobs []*actions_model.ActionRunJob
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
status := job.Status
|
status := job.Status
|
||||||
@ -534,6 +540,9 @@ func Cancel(ctx *context_module.Context) {
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
return fmt.Errorf("job has changed, try again")
|
return fmt.Errorf("job has changed, try again")
|
||||||
}
|
}
|
||||||
|
if n > 0 {
|
||||||
|
updatedjobs = append(updatedjobs, job)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil {
|
if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil {
|
||||||
@ -548,6 +557,11 @@ func Cancel(ctx *context_module.Context) {
|
|||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, jobs...)
|
actions_service.CreateCommitStatus(ctx, jobs...)
|
||||||
|
|
||||||
|
for _, job := range updatedjobs {
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,6 +575,8 @@ func Approve(ctx *context_module.Context) {
|
|||||||
run := current.Run
|
run := current.Run
|
||||||
doer := ctx.Doer
|
doer := ctx.Doer
|
||||||
|
|
||||||
|
var updatedjobs []*actions_model.ActionRunJob
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
run.NeedApproval = false
|
run.NeedApproval = false
|
||||||
run.ApprovedBy = doer.ID
|
run.ApprovedBy = doer.ID
|
||||||
@ -570,10 +586,13 @@ func Approve(ctx *context_module.Context) {
|
|||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
if len(job.Needs) == 0 && job.Status.IsBlocked() {
|
if len(job.Needs) == 0 && job.Status.IsBlocked() {
|
||||||
job.Status = actions_model.StatusWaiting
|
job.Status = actions_model.StatusWaiting
|
||||||
_, err := actions_model.UpdateRunJob(ctx, job, nil, "status")
|
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if n > 0 {
|
||||||
|
updatedjobs = append(updatedjobs, job)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -584,6 +603,11 @@ func Approve(ctx *context_module.Context) {
|
|||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, jobs...)
|
actions_service.CreateCommitStatus(ctx, jobs...)
|
||||||
|
|
||||||
|
for _, job := range updatedjobs {
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +185,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
|
|||||||
webhook_module.HookEventRepository: form.Repository,
|
webhook_module.HookEventRepository: form.Repository,
|
||||||
webhook_module.HookEventPackage: form.Package,
|
webhook_module.HookEventPackage: form.Package,
|
||||||
webhook_module.HookEventStatus: form.Status,
|
webhook_module.HookEventStatus: form.Status,
|
||||||
|
webhook_module.HookEventWorkflowJob: form.WorkflowJob,
|
||||||
},
|
},
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time
|
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time
|
||||||
@ -37,6 +38,10 @@ func StopEndlessTasks(ctx context.Context) error {
|
|||||||
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
|
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
|
||||||
if len(jobs) > 0 {
|
if len(jobs) > 0 {
|
||||||
CreateCommitStatus(ctx, jobs...)
|
CreateCommitStatus(ctx, jobs...)
|
||||||
|
for _, job := range jobs {
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,14 +112,20 @@ func CancelAbandonedJobs(ctx context.Context) error {
|
|||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
job.Status = actions_model.StatusCancelled
|
job.Status = actions_model.StatusCancelled
|
||||||
job.Stopped = now
|
job.Stopped = now
|
||||||
|
updated := false
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
_, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
|
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
|
||||||
|
updated = err == nil && n > 0
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Warn("cancel abandoned job %v: %v", job.ID, err)
|
log.Warn("cancel abandoned job %v: %v", job.ID, err)
|
||||||
// go on
|
// go on
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, job)
|
CreateCommitStatus(ctx, job)
|
||||||
|
if updated {
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/jobparser"
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
@ -49,6 +50,7 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var updatedjobs []*actions_model.ActionRunJob
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
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 {
|
||||||
@ -64,6 +66,7 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
|||||||
} else if n != 1 {
|
} else if n != 1 {
|
||||||
return fmt.Errorf("no affected for updating blocked job %v", job.ID)
|
return fmt.Errorf("no affected for updating blocked job %v", job.ID)
|
||||||
}
|
}
|
||||||
|
updatedjobs = append(updatedjobs, job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -71,6 +74,10 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, jobs...)
|
CreateCommitStatus(ctx, jobs...)
|
||||||
|
for _, job := range updatedjobs {
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
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"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/jobparser"
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
@ -363,6 +364,9 @@ func handleWorkflows(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, alljobs...)
|
CreateCommitStatus(ctx, alljobs...)
|
||||||
|
for _, job := range alljobs {
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, input.Repo, input.Doer, job, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/jobparser"
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
)
|
)
|
||||||
@ -148,6 +149,17 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
|
|||||||
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
|
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
allJobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("FindRunJobs: %v", err)
|
||||||
|
}
|
||||||
|
err = run.LoadAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LoadAttributes: %v", err)
|
||||||
|
}
|
||||||
|
for _, job := range allJobs {
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, run.Repo, run.TriggerUser, job, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Return nil if no errors occurred
|
// Return nil if no errors occurred
|
||||||
return nil
|
return nil
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
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"
|
||||||
secret_model "code.gitea.io/gitea/models/secret"
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
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"
|
||||||
@ -19,6 +20,7 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||||||
var (
|
var (
|
||||||
task *runnerv1.Task
|
task *runnerv1.Task
|
||||||
job *actions_model.ActionRunJob
|
job *actions_model.ActionRunJob
|
||||||
|
actionTask *actions_model.ActionTask
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
@ -34,6 +36,7 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||||||
return fmt.Errorf("task LoadAttributes: %w", err)
|
return fmt.Errorf("task LoadAttributes: %w", err)
|
||||||
}
|
}
|
||||||
job = t.Job
|
job = t.Job
|
||||||
|
actionTask = t
|
||||||
|
|
||||||
secrets, err := secret_model.GetSecretsOfTask(ctx, t)
|
secrets, err := secret_model.GetSecretsOfTask(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,6 +77,7 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||||||
}
|
}
|
||||||
|
|
||||||
CreateCommitStatus(ctx, job)
|
CreateCommitStatus(ctx, job)
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, actionTask)
|
||||||
|
|
||||||
return task, true, nil
|
return task, true, nil
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/jobparser"
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
@ -276,6 +277,9 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
|||||||
log.Error("FindRunJobs: %v", err)
|
log.Error("FindRunJobs: %v", err)
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, allJobs...)
|
CreateCommitStatus(ctx, allJobs...)
|
||||||
|
for _, job := range allJobs {
|
||||||
|
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -237,6 +237,7 @@ type WebhookForm struct {
|
|||||||
Release bool
|
Release bool
|
||||||
Package bool
|
Package bool
|
||||||
Status bool
|
Status bool
|
||||||
|
WorkflowJob bool
|
||||||
Active bool
|
Active bool
|
||||||
BranchFilter string `binding:"GlobPattern"`
|
BranchFilter string `binding:"GlobPattern"`
|
||||||
AuthorizationHeader string
|
AuthorizationHeader string
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@ -77,4 +78,6 @@ type Notifier interface {
|
|||||||
ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository)
|
ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository)
|
||||||
|
|
||||||
CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus)
|
CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus)
|
||||||
|
|
||||||
|
WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@ -374,3 +375,9 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit
|
|||||||
notifier.CreateCommitStatus(ctx, repo, commit, sender, status)
|
notifier.CreateCommitStatus(ctx, repo, commit, sender, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
|
||||||
|
for _, notifier := range notifiers {
|
||||||
|
notifier.WorkflowJobStatusUpdate(ctx, repo, sender, job, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@ -212,3 +213,6 @@ func (*NullNotifier) ChangeDefaultBranch(ctx context.Context, repo *repo_model.R
|
|||||||
|
|
||||||
func (*NullNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
func (*NullNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*NullNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
|
||||||
|
}
|
||||||
|
@ -176,6 +176,12 @@ func (dc dingtalkConvertor) Status(p *api.CommitStatusPayload) (DingtalkPayload,
|
|||||||
return createDingtalkPayload(text, text, "Status Changed", p.TargetURL), nil
|
return createDingtalkPayload(text, text, "Status Changed", p.TargetURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dingtalkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DingtalkPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return createDingtalkPayload(text, text, "Workflow Job", p.WorkflowJob.HTMLURL), nil
|
||||||
|
}
|
||||||
|
|
||||||
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
|
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
|
||||||
return DingtalkPayload{
|
return DingtalkPayload{
|
||||||
MsgType: "actionCard",
|
MsgType: "actionCard",
|
||||||
|
@ -271,6 +271,12 @@ func (d discordConvertor) Status(p *api.CommitStatusPayload) (DiscordPayload, er
|
|||||||
return d.createPayload(p.Sender, text, "", p.TargetURL, color), nil
|
return d.createPayload(p.Sender, text, "", p.TargetURL, color), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d discordConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DiscordPayload, error) {
|
||||||
|
text, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
return d.createPayload(p.Sender, text, "", p.WorkflowJob.HTMLURL, color), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
meta := &DiscordMeta{}
|
meta := &DiscordMeta{}
|
||||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||||
|
@ -172,6 +172,12 @@ func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, err
|
|||||||
return newFeishuTextPayload(text), nil
|
return newFeishuTextPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return newFeishuTextPayload(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
|
var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
|
||||||
return newJSONRequest(pc, w, t, true)
|
return newJSONRequest(pc, w, t, true)
|
||||||
|
@ -325,6 +325,37 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte
|
|||||||
return text, color
|
return text, color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
||||||
|
description := p.WorkflowJob.Conclusion
|
||||||
|
if description == "" {
|
||||||
|
description = p.WorkflowJob.Status
|
||||||
|
}
|
||||||
|
refLink := linkFormatter(p.WorkflowJob.HTMLURL, fmt.Sprintf("%s(#%d)", p.WorkflowJob.Name, p.WorkflowJob.RunID)+"["+base.ShortSha(p.WorkflowJob.HeadSha)+"]:"+description)
|
||||||
|
|
||||||
|
text = fmt.Sprintf("Workflow Job %s: %s", p.Action, refLink)
|
||||||
|
switch description {
|
||||||
|
case "waiting":
|
||||||
|
color = orangeColor
|
||||||
|
case "queued":
|
||||||
|
color = orangeColorLight
|
||||||
|
case "success":
|
||||||
|
color = greenColor
|
||||||
|
case "failure":
|
||||||
|
color = redColor
|
||||||
|
case "cancelled":
|
||||||
|
color = yellowColor
|
||||||
|
case "skipped":
|
||||||
|
color = purpleColor
|
||||||
|
default:
|
||||||
|
color = greyColor
|
||||||
|
}
|
||||||
|
if withSender {
|
||||||
|
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return text, color
|
||||||
|
}
|
||||||
|
|
||||||
// ToHook convert models.Webhook to api.Hook
|
// ToHook convert models.Webhook to api.Hook
|
||||||
// This function is not part of the convert package to prevent an import cycle
|
// This function is not part of the convert package to prevent an import cycle
|
||||||
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
||||||
|
@ -252,6 +252,12 @@ func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, erro
|
|||||||
return m.newPayload(text)
|
return m.newPayload(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m matrixConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MatrixPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
return m.newPayload(text)
|
||||||
|
}
|
||||||
|
|
||||||
var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)
|
var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)
|
||||||
|
|
||||||
func getMessageBody(htmlText string) string {
|
func getMessageBody(htmlText string) string {
|
||||||
|
@ -317,6 +317,20 @@ func (m msteamsConvertor) Status(p *api.CommitStatusPayload) (MSTeamsPayload, er
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload, error) {
|
||||||
|
title, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
return createMSTeamsPayload(
|
||||||
|
p.Repo,
|
||||||
|
p.Sender,
|
||||||
|
title,
|
||||||
|
"",
|
||||||
|
p.WorkflowJob.HTMLURL,
|
||||||
|
color,
|
||||||
|
&MSTeamsFact{"WorkflowJob:", p.WorkflowJob.Name},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
|
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
|
||||||
facts := make([]MSTeamsFact, 0, 2)
|
facts := make([]MSTeamsFact, 0, 2)
|
||||||
if r != nil {
|
if r != nil {
|
||||||
|
@ -5,7 +5,10 @@ package webhook
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
@ -941,3 +944,114 @@ func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_mo
|
|||||||
log.Error("PrepareWebhooks: %v", err)
|
log.Error("PrepareWebhooks: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*webhookNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
|
||||||
|
source := EventSource{
|
||||||
|
Repository: repo,
|
||||||
|
Owner: repo.Owner,
|
||||||
|
}
|
||||||
|
|
||||||
|
var org *api.Organization
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := job.LoadAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error loading job attributes: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jobIndex := 0
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error loading getting run jobs: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, j := range jobs {
|
||||||
|
if j.ID == job.ID {
|
||||||
|
jobIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status, conclusion := toActionStatus(job.Status)
|
||||||
|
var runnerID int64
|
||||||
|
var runnerName string
|
||||||
|
var steps []*api.ActionWorkflowStep
|
||||||
|
|
||||||
|
if task != nil {
|
||||||
|
runnerID = task.RunnerID
|
||||||
|
if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
|
||||||
|
runnerName = runner.Name
|
||||||
|
}
|
||||||
|
for i, step := range task.Steps {
|
||||||
|
stepStatus, stepConclusion := toActionStatus(job.Status)
|
||||||
|
steps = append(steps, &api.ActionWorkflowStep{
|
||||||
|
Name: step.Name,
|
||||||
|
Number: int64(i),
|
||||||
|
Status: stepStatus,
|
||||||
|
Conclusion: stepConclusion,
|
||||||
|
StartedAt: step.Started.AsTime().UTC(),
|
||||||
|
CompletedAt: step.Stopped.AsTime().UTC(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowJob, &api.WorkflowJobPayload{
|
||||||
|
Action: status,
|
||||||
|
WorkflowJob: &api.ActionWorkflowJob{
|
||||||
|
ID: job.ID,
|
||||||
|
// missing api endpoint for this location
|
||||||
|
URL: fmt.Sprintf("%s/actions/runs/%d/jobs/%d", repo.APIURL(), job.RunID, job.ID),
|
||||||
|
HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
|
||||||
|
RunID: job.RunID,
|
||||||
|
// Missing api endpoint for this location, artifacts are available under a nested url
|
||||||
|
RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
|
||||||
|
Name: job.Name,
|
||||||
|
Labels: job.RunsOn,
|
||||||
|
RunAttempt: job.Attempt,
|
||||||
|
HeadSha: job.Run.CommitSHA,
|
||||||
|
HeadBranch: git.RefName(job.Run.Ref).BranchName(),
|
||||||
|
Status: status,
|
||||||
|
Conclusion: conclusion,
|
||||||
|
RunnerID: runnerID,
|
||||||
|
RunnerName: runnerName,
|
||||||
|
Steps: steps,
|
||||||
|
CreatedAt: job.Created.AsTime().UTC(),
|
||||||
|
StartedAt: job.Started.AsTime().UTC(),
|
||||||
|
CompletedAt: job.Stopped.AsTime().UTC(),
|
||||||
|
},
|
||||||
|
Organization: org,
|
||||||
|
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
|
||||||
|
Sender: convert.ToUser(ctx, sender, nil),
|
||||||
|
}); err != nil {
|
||||||
|
log.Error("PrepareWebhooks: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toActionStatus(status actions_model.Status) (string, string) {
|
||||||
|
var action string
|
||||||
|
var conclusion string
|
||||||
|
switch status {
|
||||||
|
// This is a naming conflict of the webhook between Gitea and GitHub Actions
|
||||||
|
case actions_model.StatusWaiting:
|
||||||
|
action = "queued"
|
||||||
|
case actions_model.StatusBlocked:
|
||||||
|
action = "waiting"
|
||||||
|
case actions_model.StatusRunning:
|
||||||
|
action = "in_progress"
|
||||||
|
}
|
||||||
|
if status.IsDone() {
|
||||||
|
action = "completed"
|
||||||
|
switch status {
|
||||||
|
case actions_model.StatusSuccess:
|
||||||
|
conclusion = "success"
|
||||||
|
case actions_model.StatusCancelled:
|
||||||
|
conclusion = "cancelled"
|
||||||
|
case actions_model.StatusFailure:
|
||||||
|
conclusion = "failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action, conclusion
|
||||||
|
}
|
||||||
|
@ -114,6 +114,10 @@ func (pc packagistConvertor) Status(_ *api.CommitStatusPayload) (PackagistPayloa
|
|||||||
return PackagistPayload{}, nil
|
return PackagistPayload{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc packagistConvertor) WorkflowJob(_ *api.WorkflowJobPayload) (PackagistPayload, error) {
|
||||||
|
return PackagistPayload{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
meta := &PackagistMeta{}
|
meta := &PackagistMeta{}
|
||||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||||
|
@ -29,6 +29,7 @@ type payloadConvertor[T any] interface {
|
|||||||
Wiki(*api.WikiPayload) (T, error)
|
Wiki(*api.WikiPayload) (T, error)
|
||||||
Package(*api.PackagePayload) (T, error)
|
Package(*api.PackagePayload) (T, error)
|
||||||
Status(*api.CommitStatusPayload) (T, error)
|
Status(*api.CommitStatusPayload) (T, error)
|
||||||
|
WorkflowJob(*api.WorkflowJobPayload) (T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (t T, err error) {
|
func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (t T, err error) {
|
||||||
@ -80,6 +81,8 @@ func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module
|
|||||||
return convertUnmarshalledJSON(rc.Package, data)
|
return convertUnmarshalledJSON(rc.Package, data)
|
||||||
case webhook_module.HookEventStatus:
|
case webhook_module.HookEventStatus:
|
||||||
return convertUnmarshalledJSON(rc.Status, data)
|
return convertUnmarshalledJSON(rc.Status, data)
|
||||||
|
case webhook_module.HookEventWorkflowJob:
|
||||||
|
return convertUnmarshalledJSON(rc.WorkflowJob, data)
|
||||||
}
|
}
|
||||||
return t, fmt.Errorf("newPayload unsupported event: %s", event)
|
return t, fmt.Errorf("newPayload unsupported event: %s", event)
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,12 @@ func (s slackConvertor) Status(p *api.CommitStatusPayload) (SlackPayload, error)
|
|||||||
return s.createPayload(text, nil), nil
|
return s.createPayload(text, nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s slackConvertor) WorkflowJob(p *api.WorkflowJobPayload) (SlackPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, SlackLinkFormatter, true)
|
||||||
|
|
||||||
|
return s.createPayload(text, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Push implements payloadConvertor Push method
|
// Push implements payloadConvertor Push method
|
||||||
func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
|
func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
|
||||||
// n new commits
|
// n new commits
|
||||||
|
@ -180,6 +180,12 @@ func (t telegramConvertor) Status(p *api.CommitStatusPayload) (TelegramPayload,
|
|||||||
return createTelegramPayloadHTML(text), nil
|
return createTelegramPayloadHTML(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
return createTelegramPayloadHTML(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
||||||
// https://core.telegram.org/bots/api#formatting-options
|
// https://core.telegram.org/bots/api#formatting-options
|
||||||
return TelegramPayload{
|
return TelegramPayload{
|
||||||
|
@ -181,6 +181,12 @@ func (wc wechatworkConvertor) Status(p *api.CommitStatusPayload) (WechatworkPayl
|
|||||||
return newWechatworkMarkdownPayload(text), nil
|
return newWechatworkMarkdownPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wc wechatworkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (WechatworkPayload, error) {
|
||||||
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return newWechatworkMarkdownPayload(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
|
var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
|
||||||
return newJSONRequest(pc, w, t, true)
|
return newJSONRequest(pc, w, t, true)
|
||||||
|
@ -259,6 +259,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Workflow Events -->
|
||||||
|
<div class="fourteen wide column">
|
||||||
|
<label>{{ctx.Locale.Tr "repo.settings.event_header_workflow"}}</label>
|
||||||
|
</div>
|
||||||
|
<!-- Workflow Job Event -->
|
||||||
|
<div class="seven wide column">
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="workflow_job" type="checkbox" {{if .Webhook.HookEvents.Get "workflow_job"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.settings.event_workflow_job"}}</label>
|
||||||
|
<span class="help">{{ctx.Locale.Tr "repo.settings.event_workflow_job_desc"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,10 +11,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/repo"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/models/webhook"
|
"code.gitea.io/gitea/models/webhook"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
@ -22,6 +24,7 @@ import (
|
|||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -605,3 +608,146 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
|
|||||||
assert.EqualValues(t, "push", trigger)
|
assert.EqualValues(t, "push", trigger)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_WebhookWorkflowJob(t *testing.T) {
|
||||||
|
var payloads []api.WorkflowJobPayload
|
||||||
|
var triggeredEvent string
|
||||||
|
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||||
|
assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
|
||||||
|
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
|
||||||
|
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
|
||||||
|
content, _ := io.ReadAll(r.Body)
|
||||||
|
var payload api.WorkflowJobPayload
|
||||||
|
err := json.Unmarshal(content, &payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
payloads = append(payloads, payload)
|
||||||
|
triggeredEvent = "workflow_job"
|
||||||
|
}, http.StatusOK)
|
||||||
|
defer provider.Close()
|
||||||
|
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
// 1. create a new webhook with special webhook for repo1
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "workflow_job")
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||||
|
|
||||||
|
gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
runner := newMockRunner()
|
||||||
|
runner.registerAsRepoRunner(t, "user2", "repo1", "mock-runner", []string{"ubuntu-latest"})
|
||||||
|
|
||||||
|
// 2. trigger the webhooks
|
||||||
|
|
||||||
|
// add workflow file to the repo
|
||||||
|
// init the workflow
|
||||||
|
wfTreePath := ".gitea/workflows/push.yml"
|
||||||
|
wfFileContent := `name: Push
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
wf1-job:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo 'test the webhook'
|
||||||
|
wf2-job:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: wf1-job
|
||||||
|
steps:
|
||||||
|
- run: echo 'cmd 1'
|
||||||
|
- run: echo 'cmd 2'
|
||||||
|
`
|
||||||
|
opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent)
|
||||||
|
createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
|
||||||
|
|
||||||
|
commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 3. validate the webhook is triggered
|
||||||
|
assert.EqualValues(t, "workflow_job", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 2)
|
||||||
|
assert.EqualValues(t, "queued", payloads[0].Action)
|
||||||
|
assert.EqualValues(t, "queued", payloads[0].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, []string{"ubuntu-latest"}, payloads[0].WorkflowJob.Labels)
|
||||||
|
assert.EqualValues(t, commitID, payloads[0].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "waiting", payloads[1].Action)
|
||||||
|
assert.EqualValues(t, "waiting", payloads[1].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, commitID, payloads[1].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[1].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[1].Repo.FullName)
|
||||||
|
|
||||||
|
// 4. Execute a single Job
|
||||||
|
task := runner.fetchTask(t)
|
||||||
|
outcome := &mockTaskOutcome{
|
||||||
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
|
execTime: time.Millisecond,
|
||||||
|
}
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
|
||||||
|
// 5. validate the webhook is triggered
|
||||||
|
assert.EqualValues(t, "workflow_job", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 5)
|
||||||
|
assert.EqualValues(t, "in_progress", payloads[2].Action)
|
||||||
|
assert.EqualValues(t, "in_progress", payloads[2].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, "mock-runner", payloads[2].WorkflowJob.RunnerName)
|
||||||
|
assert.EqualValues(t, commitID, payloads[2].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[2].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[2].Repo.FullName)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "completed", payloads[3].Action)
|
||||||
|
assert.EqualValues(t, "completed", payloads[3].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, "mock-runner", payloads[3].WorkflowJob.RunnerName)
|
||||||
|
assert.EqualValues(t, "success", payloads[3].WorkflowJob.Conclusion)
|
||||||
|
assert.EqualValues(t, commitID, payloads[3].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[3].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[3].Repo.FullName)
|
||||||
|
assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[3].WorkflowJob.RunID, payloads[3].WorkflowJob.ID))
|
||||||
|
assert.Contains(t, payloads[3].WorkflowJob.URL, payloads[3].WorkflowJob.RunURL)
|
||||||
|
assert.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0))
|
||||||
|
assert.Len(t, payloads[3].WorkflowJob.Steps, 1)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "queued", payloads[4].Action)
|
||||||
|
assert.EqualValues(t, "queued", payloads[4].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, []string{"ubuntu-latest"}, payloads[4].WorkflowJob.Labels)
|
||||||
|
assert.EqualValues(t, commitID, payloads[4].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[4].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[4].Repo.FullName)
|
||||||
|
|
||||||
|
// 6. Execute a single Job
|
||||||
|
task = runner.fetchTask(t)
|
||||||
|
outcome = &mockTaskOutcome{
|
||||||
|
result: runnerv1.Result_RESULT_FAILURE,
|
||||||
|
execTime: time.Millisecond,
|
||||||
|
}
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
|
||||||
|
// 7. validate the webhook is triggered
|
||||||
|
assert.EqualValues(t, "workflow_job", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 7)
|
||||||
|
assert.EqualValues(t, "in_progress", payloads[5].Action)
|
||||||
|
assert.EqualValues(t, "in_progress", payloads[5].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, "mock-runner", payloads[5].WorkflowJob.RunnerName)
|
||||||
|
|
||||||
|
assert.EqualValues(t, commitID, payloads[5].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[5].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[5].Repo.FullName)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "completed", payloads[6].Action)
|
||||||
|
assert.EqualValues(t, "completed", payloads[6].WorkflowJob.Status)
|
||||||
|
assert.EqualValues(t, "failure", payloads[6].WorkflowJob.Conclusion)
|
||||||
|
assert.EqualValues(t, "mock-runner", payloads[6].WorkflowJob.RunnerName)
|
||||||
|
assert.EqualValues(t, commitID, payloads[6].WorkflowJob.HeadSha)
|
||||||
|
assert.EqualValues(t, "repo1", payloads[6].Repo.Name)
|
||||||
|
assert.EqualValues(t, "user2/repo1", payloads[6].Repo.FullName)
|
||||||
|
assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[6].WorkflowJob.RunID, payloads[6].WorkflowJob.ID))
|
||||||
|
assert.Contains(t, payloads[6].WorkflowJob.URL, payloads[6].WorkflowJob.RunURL)
|
||||||
|
assert.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1))
|
||||||
|
assert.Len(t, payloads[6].WorkflowJob.Steps, 2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user