mirror of https://github.com/go-gitea/gitea.git
Merge b5228937bf
into 24ce2058e8
commit
39de115b6d
|
@ -166,6 +166,17 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
|
||||||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (run *ActionRun) GetWorkflowRunEventPayload() (*api.WorkflowRunPayload, error) {
|
||||||
|
if run.Event == webhook_module.HookEventWorkflowRun {
|
||||||
|
var payload api.WorkflowRunPayload
|
||||||
|
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &payload, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("event %s is not a workflow run event", run.Event)
|
||||||
|
}
|
||||||
|
|
||||||
func (run *ActionRun) IsSchedule() bool {
|
func (run *ActionRun) IsSchedule() bool {
|
||||||
return run.ScheduleID > 0
|
return run.ScheduleID > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,22 +80,31 @@ type FindRunJobOptions struct {
|
||||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
if opts.RunID > 0 {
|
if opts.RunID > 0 {
|
||||||
cond = cond.And(builder.Eq{"run_id": opts.RunID})
|
cond = cond.And(builder.Eq{"`action_run_job`.run_id": opts.RunID})
|
||||||
}
|
}
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"`action_run_job`.repo_id": opts.RepoID})
|
||||||
}
|
|
||||||
if opts.OwnerID > 0 {
|
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
|
||||||
}
|
}
|
||||||
if opts.CommitSHA != "" {
|
if opts.CommitSHA != "" {
|
||||||
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
|
cond = cond.And(builder.Eq{"`action_run_job`.commit_sha": opts.CommitSHA})
|
||||||
}
|
}
|
||||||
if len(opts.Statuses) > 0 {
|
if len(opts.Statuses) > 0 {
|
||||||
cond = cond.And(builder.In("status", opts.Statuses))
|
cond = cond.And(builder.In("`action_run_job`.status", opts.Statuses))
|
||||||
}
|
}
|
||||||
if opts.UpdatedBefore > 0 {
|
if opts.UpdatedBefore > 0 {
|
||||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore})
|
||||||
}
|
}
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
|
||||||
|
if opts.OwnerID > 0 {
|
||||||
|
return []db.JoinFunc{
|
||||||
|
func(sess db.Engine) error {
|
||||||
|
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -72,39 +72,50 @@ type FindRunOptions struct {
|
||||||
TriggerEvent webhook_module.HookEventType
|
TriggerEvent webhook_module.HookEventType
|
||||||
Approved bool // not util.OptionalBool, it works only when it's true
|
Approved bool // not util.OptionalBool, it works only when it's true
|
||||||
Status []Status
|
Status []Status
|
||||||
|
CommitSHA string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"`action_run`.repo_id": opts.RepoID})
|
||||||
}
|
|
||||||
if opts.OwnerID > 0 {
|
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
|
||||||
}
|
}
|
||||||
if opts.WorkflowID != "" {
|
if opts.WorkflowID != "" {
|
||||||
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
|
cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID})
|
||||||
}
|
}
|
||||||
if opts.TriggerUserID > 0 {
|
if opts.TriggerUserID > 0 {
|
||||||
cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
|
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID})
|
||||||
}
|
}
|
||||||
if opts.Approved {
|
if opts.Approved {
|
||||||
cond = cond.And(builder.Gt{"approved_by": 0})
|
cond = cond.And(builder.Gt{"`action_run`.approved_by": 0})
|
||||||
}
|
}
|
||||||
if len(opts.Status) > 0 {
|
if len(opts.Status) > 0 {
|
||||||
cond = cond.And(builder.In("status", opts.Status))
|
cond = cond.And(builder.In("`action_run`.status", opts.Status))
|
||||||
}
|
}
|
||||||
if opts.Ref != "" {
|
if opts.Ref != "" {
|
||||||
cond = cond.And(builder.Eq{"ref": opts.Ref})
|
cond = cond.And(builder.Eq{"`action_run`.ref": opts.Ref})
|
||||||
}
|
}
|
||||||
if opts.TriggerEvent != "" {
|
if opts.TriggerEvent != "" {
|
||||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
cond = cond.And(builder.Eq{"`action_run`.trigger_event": opts.TriggerEvent})
|
||||||
|
}
|
||||||
|
if opts.CommitSHA != "" {
|
||||||
|
cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
|
||||||
}
|
}
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts FindRunOptions) ToJoins() []db.JoinFunc {
|
||||||
|
if opts.OwnerID > 0 {
|
||||||
|
return []db.JoinFunc{func(sess db.Engine) error {
|
||||||
|
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
|
||||||
|
return nil
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (opts FindRunOptions) ToOrders() string {
|
func (opts FindRunOptions) ToOrders() string {
|
||||||
return "`id` DESC"
|
return "`action_run`.`id` DESC"
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusInfo struct {
|
type StatusInfo struct {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 1
|
status: 1
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 1
|
status: 1
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
|
@ -47,6 +49,7 @@
|
||||||
ref: "refs/heads/master"
|
ref: "refs/heads/master"
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 6 # running
|
status: 6 # running
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
|
@ -66,6 +69,47 @@
|
||||||
ref: "refs/heads/test"
|
ref: "refs/heads/test"
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
-
|
||||||
|
id: 802
|
||||||
|
title: "workflow run list"
|
||||||
|
repo_id: 5
|
||||||
|
owner_id: 3
|
||||||
|
workflow_id: "test.yaml"
|
||||||
|
index: 191
|
||||||
|
trigger_user_id: 1
|
||||||
|
ref: "refs/heads/test"
|
||||||
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
-
|
||||||
|
id: 803
|
||||||
|
title: "workflow run list for user"
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 0
|
||||||
|
workflow_id: "test.yaml"
|
||||||
|
index: 192
|
||||||
|
trigger_user_id: 1
|
||||||
|
ref: "refs/heads/test"
|
||||||
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 1
|
status: 1
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
|
@ -86,6 +130,7 @@
|
||||||
ref: "refs/heads/test"
|
ref: "refs/heads/test"
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 2
|
status: 2
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
|
|
|
@ -99,3 +99,33 @@
|
||||||
status: 2
|
status: 2
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
|
-
|
||||||
|
id: 203
|
||||||
|
run_id: 802
|
||||||
|
repo_id: 5
|
||||||
|
owner_id: 0
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job2
|
||||||
|
needs: '["job1"]'
|
||||||
|
task_id: 51
|
||||||
|
status: 5
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
-
|
||||||
|
id: 204
|
||||||
|
run_id: 803
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 0
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job2
|
||||||
|
needs: '["job1"]'
|
||||||
|
task_id: 51
|
||||||
|
status: 5
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|
|
@ -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", "workflow_job",
|
"package", "status", "workflow_run", "workflow_job",
|
||||||
},
|
},
|
||||||
(&Webhook{
|
(&Webhook{
|
||||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||||
|
|
|
@ -245,6 +245,10 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
||||||
webhook_module.HookEventPackage:
|
webhook_module.HookEventPackage:
|
||||||
return matchPackageEvent(payload.(*api.PackagePayload), evt)
|
return matchPackageEvent(payload.(*api.PackagePayload), evt)
|
||||||
|
|
||||||
|
case // workflow_run
|
||||||
|
webhook_module.HookEventWorkflowRun:
|
||||||
|
return matchWorkflowRunEvent(payload.(*api.WorkflowRunPayload), evt)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Warn("unsupported event %q", triggedEvent)
|
log.Warn("unsupported event %q", triggedEvent)
|
||||||
return false
|
return false
|
||||||
|
@ -708,3 +712,53 @@ func matchPackageEvent(payload *api.PackagePayload, evt *jobparser.Event) bool {
|
||||||
}
|
}
|
||||||
return matchTimes == len(evt.Acts())
|
return matchTimes == len(evt.Acts())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event) bool {
|
||||||
|
// with no special filter parameters
|
||||||
|
if len(evt.Acts()) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
matchTimes := 0
|
||||||
|
// all acts conditions should be satisfied
|
||||||
|
for cond, vals := range evt.Acts() {
|
||||||
|
switch cond {
|
||||||
|
case "types":
|
||||||
|
action := payload.Action
|
||||||
|
for _, val := range vals {
|
||||||
|
if glob.MustCompile(val, '/').Match(action) {
|
||||||
|
matchTimes++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "workflows":
|
||||||
|
workflow := payload.Workflow
|
||||||
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !workflowpattern.Skip(patterns, []string{workflow.Name}, &workflowpattern.EmptyTraceWriter{}) {
|
||||||
|
matchTimes++
|
||||||
|
}
|
||||||
|
case "branches":
|
||||||
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !workflowpattern.Skip(patterns, []string{payload.WorkflowRun.HeadBranch}, &workflowpattern.EmptyTraceWriter{}) {
|
||||||
|
matchTimes++
|
||||||
|
}
|
||||||
|
case "branches-ignore":
|
||||||
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !workflowpattern.Filter(patterns, []string{payload.WorkflowRun.HeadBranch}, &workflowpattern.EmptyTraceWriter{}) {
|
||||||
|
matchTimes++
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Warn("workflow run event unsupported condition %q", cond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchTimes == len(evt.Acts())
|
||||||
|
}
|
||||||
|
|
|
@ -470,6 +470,22 @@ func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
|
||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkflowRunPayload represents a payload information of workflow run event.
|
||||||
|
type WorkflowRunPayload struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Workflow *ActionWorkflow `json:"workflow"`
|
||||||
|
WorkflowRun *ActionWorkflowRun `json:"workflow_run"`
|
||||||
|
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||||
|
Organization *Organization `json:"organization,omitempty"`
|
||||||
|
Repo *Repository `json:"repository"`
|
||||||
|
Sender *User `json:"sender"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONPayload implements Payload
|
||||||
|
func (p *WorkflowRunPayload) JSONPayload() ([]byte, error) {
|
||||||
|
return json.MarshalIndent(p, "", " ")
|
||||||
|
}
|
||||||
|
|
||||||
// WorkflowJobPayload represents a payload information of workflow job event.
|
// WorkflowJobPayload represents a payload information of workflow job event.
|
||||||
type WorkflowJobPayload struct {
|
type WorkflowJobPayload struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
|
|
|
@ -86,9 +86,39 @@ type ActionArtifact struct {
|
||||||
|
|
||||||
// ActionWorkflowRun represents a WorkflowRun
|
// ActionWorkflowRun represents a WorkflowRun
|
||||||
type ActionWorkflowRun struct {
|
type ActionWorkflowRun struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
RepositoryID int64 `json:"repository_id"`
|
URL string `json:"url"`
|
||||||
HeadSha string `json:"head_sha"`
|
HTMLURL string `json:"html_url"`
|
||||||
|
DisplayTitle string `json:"display_title"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunAttempt int64 `json:"run_attempt"`
|
||||||
|
RunNumber int64 `json:"run_number"`
|
||||||
|
RepositoryID int64 `json:"repository_id,omitempty"`
|
||||||
|
HeadSha string `json:"head_sha"`
|
||||||
|
HeadBranch string `json:"head_branch,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Actor *User `json:"actor,omitempty"`
|
||||||
|
TriggerActor *User `json:"trigger_actor,omitempty"`
|
||||||
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
|
HeadRepository *Repository `json:"head_repository,omitempty"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionWorkflowRunsResponse returns ActionWorkflowRuns
|
||||||
|
type ActionWorkflowRunsResponse struct {
|
||||||
|
Entries []*ActionWorkflowRun `json:"workflow_runs"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionWorkflowJobsResponse returns ActionWorkflowJobs
|
||||||
|
type ActionWorkflowJobsResponse struct {
|
||||||
|
Entries []*ActionWorkflowJob `json:"jobs"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionArtifactsResponse returns ActionArtifacts
|
// ActionArtifactsResponse returns ActionArtifacts
|
||||||
|
|
|
@ -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"
|
||||||
|
HookEventWorkflowRun HookEventType = "workflow_run"
|
||||||
HookEventWorkflowJob HookEventType = "workflow_job"
|
HookEventWorkflowJob HookEventType = "workflow_job"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ func AllEvents() []HookEventType {
|
||||||
HookEventRelease,
|
HookEventRelease,
|
||||||
HookEventPackage,
|
HookEventPackage,
|
||||||
HookEventStatus,
|
HookEventStatus,
|
||||||
|
HookEventWorkflowRun,
|
||||||
HookEventWorkflowJob,
|
HookEventWorkflowJob,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2401,6 +2401,8 @@ settings.event_pull_request_review_request_desc = Pull request review requested
|
||||||
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_header_workflow = Workflow Events
|
||||||
|
settings.event_workflow_run = Workflow Run
|
||||||
|
settings.event_workflow_run_desc = Gitea Actions Workflow run queued, waiting, in progress, or completed.
|
||||||
settings.event_workflow_job = Workflow Jobs
|
settings.event_workflow_job = Workflow Jobs
|
||||||
settings.event_workflow_job_desc = Gitea Actions Workflow job queued, waiting, in progress, or completed.
|
settings.event_workflow_job_desc = Gitea Actions Workflow job queued, waiting, in progress, or completed.
|
||||||
settings.event_package = Package
|
settings.event_package = Package
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListWorkflowJobs Lists all jobs
|
||||||
|
func ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /admin/actions/jobs admin listAdminWorkflowJobs
|
||||||
|
// ---
|
||||||
|
// summary: Lists all jobs
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJobsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
shared.ListJobs(ctx, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkflowRuns Lists all runs
|
||||||
|
func ListWorkflowRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /admin/actions/runs admin listAdminWorkflowRuns
|
||||||
|
// ---
|
||||||
|
// summary: Lists all runs
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: workflow event name
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: branch
|
||||||
|
// in: query
|
||||||
|
// description: workflow branch
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: actor
|
||||||
|
// in: query
|
||||||
|
// description: triggered by user
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: triggering sha of the workflow run
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowRunsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
shared.ListRuns(ctx, 0, 0)
|
||||||
|
}
|
|
@ -942,6 +942,8 @@ func Routes() *web.Router {
|
||||||
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
||||||
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
||||||
})
|
})
|
||||||
|
m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns)
|
||||||
|
m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,6 +1080,9 @@ func Routes() *web.Router {
|
||||||
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||||
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
||||||
|
m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Get("/followers", user.ListMyFollowers)
|
m.Get("/followers", user.ListMyFollowers)
|
||||||
|
@ -1202,6 +1207,7 @@ func Routes() *web.Router {
|
||||||
}, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
|
}, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
|
||||||
|
|
||||||
m.Group("/actions/jobs", func() {
|
m.Group("/actions/jobs", func() {
|
||||||
|
m.Get("/{job_id}", repo.GetWorkflowJob)
|
||||||
m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
|
m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
|
||||||
}, reqToken(), reqRepoReader(unit.TypeActions))
|
}, reqToken(), reqRepoReader(unit.TypeActions))
|
||||||
|
|
||||||
|
@ -1280,9 +1286,13 @@ func Routes() *web.Router {
|
||||||
}, reqToken(), reqAdmin())
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
m.Group("/runs/{run}", func() {
|
m.Group("/runs", func() {
|
||||||
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
m.Group("/{run}", func() {
|
||||||
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
m.Get("", repo.GetWorkflowRun)
|
||||||
|
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||||
|
m.Get("/jobs", repo.ListWorkflowRunJobs)
|
||||||
|
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
m.Get("/artifacts", repo.GetArtifacts)
|
m.Get("/artifacts", repo.GetArtifacts)
|
||||||
m.Group("/artifacts/{artifact_id}", func() {
|
m.Group("/artifacts/{artifact_id}", func() {
|
||||||
|
@ -1734,11 +1744,15 @@ func Routes() *web.Router {
|
||||||
Patch(bind(api.EditHookOption{}), admin.EditHook).
|
Patch(bind(api.EditHookOption{}), admin.EditHook).
|
||||||
Delete(admin.DeleteHook)
|
Delete(admin.DeleteHook)
|
||||||
})
|
})
|
||||||
m.Group("/actions/runners", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("", admin.ListRunners)
|
m.Group("/runners", func() {
|
||||||
m.Post("/registration-token", admin.CreateRegistrationToken)
|
m.Get("", admin.ListRunners)
|
||||||
m.Get("/{runner_id}", admin.GetRunner)
|
m.Post("/registration-token", admin.CreateRegistrationToken)
|
||||||
m.Delete("/{runner_id}", admin.DeleteRunner)
|
m.Get("/{runner_id}", admin.GetRunner)
|
||||||
|
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||||
|
})
|
||||||
|
m.Get("/runs", admin.ListWorkflowRuns)
|
||||||
|
m.Get("/jobs", admin.ListWorkflowJobs)
|
||||||
})
|
})
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
m.Get("/registration-token", admin.GetRegistrationToken)
|
m.Get("/registration-token", admin.GetRegistrationToken)
|
||||||
|
|
|
@ -570,6 +570,96 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||||
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
|
||||||
|
// ---
|
||||||
|
// summary: Get org-level workflow jobs
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJobsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/runs organization getOrgWorkflowRuns
|
||||||
|
// ---
|
||||||
|
// summary: Get org-level workflow runs
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: workflow event name
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: branch
|
||||||
|
// in: query
|
||||||
|
// description: workflow branch
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: actor
|
||||||
|
// in: query
|
||||||
|
// description: triggered by user
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: triggering sha of the workflow run
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowRunsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
// Action implements actions_service.API
|
// Action implements actions_service.API
|
||||||
|
|
|
@ -650,6 +650,114 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||||
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWorkflowRunJobs Lists all jobs for a workflow run.
|
||||||
|
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
|
||||||
|
// ---
|
||||||
|
// summary: Lists all jobs for a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJobsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
|
shared.ListJobs(ctx, 0, repoID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkflowRuns Lists all runs for a repository run.
|
||||||
|
func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs repository getWorkflowRuns
|
||||||
|
// ---
|
||||||
|
// summary: Lists all runs for a repository run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: workflow event name
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: branch
|
||||||
|
// in: query
|
||||||
|
// description: workflow branch
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: actor
|
||||||
|
// in: query
|
||||||
|
// description: triggered by user
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: triggering sha of the workflow run
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ArtifactsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
|
shared.ListRuns(ctx, 0, repoID)
|
||||||
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
// Action implements actions_service.API
|
// Action implements actions_service.API
|
||||||
|
@ -756,7 +864,7 @@ func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
|
||||||
// "500":
|
// "500":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
|
||||||
workflows, err := actions_service.ListActionWorkflows(ctx)
|
workflows, err := convert.ListActionWorkflows(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
@ -802,7 +910,7 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
|
||||||
workflowID := ctx.PathParam("workflow_id")
|
workflowID := ctx.PathParam("workflow_id")
|
||||||
workflow, err := actions_service.GetActionWorkflow(ctx, workflowID)
|
workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.APIError(http.StatusNotFound, err)
|
ctx.APIError(http.StatusNotFound, err)
|
||||||
|
@ -992,6 +1100,149 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWorkflowRun Gets a specific workflow run.
|
||||||
|
func GetWorkflowRun(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
|
||||||
|
// ---
|
||||||
|
// summary: Gets a specific workflow run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: run
|
||||||
|
// in: path
|
||||||
|
// description: id of the run
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowRun"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
runID := ctx.PathParamInt64("run")
|
||||||
|
job, _, err := db.GetByID[actions_model.ActionRun](ctx, runID)
|
||||||
|
|
||||||
|
if err != nil || job.RepoID != ctx.Repo.Repository.ID {
|
||||||
|
ctx.APIError(http.StatusNotFound, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedArtifact, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, convertedArtifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkflowRunJobs Lists all jobs for a workflow run.
|
||||||
|
func ListWorkflowRunJobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs
|
||||||
|
// ---
|
||||||
|
// summary: Lists all jobs for a workflow run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: run
|
||||||
|
// in: path
|
||||||
|
// description: runid of the workflow run
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJobsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
|
runID := ctx.PathParamInt64("run")
|
||||||
|
|
||||||
|
shared.ListJobs(ctx, 0, repoID, runID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorkflowJob Gets a specific workflow job for a workflow run.
|
||||||
|
func GetWorkflowJob(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id} repository getWorkflowJob
|
||||||
|
// ---
|
||||||
|
// summary: Gets a specific workflow job for a workflow run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: job_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the job
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJob"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
jobID := ctx.PathParamInt64("job_id")
|
||||||
|
job, _, err := db.GetByID[actions_model.ActionRunJob](ctx, jobID)
|
||||||
|
|
||||||
|
if err != nil || job.RepoID != ctx.Repo.Repository.ID {
|
||||||
|
ctx.APIError(http.StatusNotFound, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, job)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, convertedWorkflowJob)
|
||||||
|
}
|
||||||
|
|
||||||
// GetArtifacts Lists all artifacts for a repository.
|
// GetArtifacts Lists all artifacts for a repository.
|
||||||
func GetArtifactsOfRun(ctx *context.APIContext) {
|
func GetArtifactsOfRun(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/webhook"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListJobs lists jobs for api route validated ownerID and repoID
|
||||||
|
// ownerID == 0 and repoID == 0 means all jobs
|
||||||
|
// ownerID == 0 and repoID != 0 means all jobs for the given repo
|
||||||
|
// ownerID != 0 and repoID == 0 means all jobs for the given user/org
|
||||||
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// runID == 0 means all jobs
|
||||||
|
// Access rights are checked at the API route level
|
||||||
|
func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
opts := actions_model.FindRunJobOptions{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
RepoID: repoID,
|
||||||
|
RunID: runID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
if statuses, ok := ctx.Req.URL.Query()["status"]; ok {
|
||||||
|
for _, status := range statuses {
|
||||||
|
values, err := convertToInternal(status)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.Statuses = append(opts.Statuses, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(api.ActionWorkflowJobsResponse)
|
||||||
|
res.TotalCount = total
|
||||||
|
|
||||||
|
res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
|
||||||
|
|
||||||
|
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
|
||||||
|
for i := range jobs {
|
||||||
|
var repository *repo_model.Repository
|
||||||
|
if isRepoLevel {
|
||||||
|
repository = ctx.Repo.Repository
|
||||||
|
} else {
|
||||||
|
repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Entries[i] = convertedWorkflowJob
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToInternal(s string) ([]actions_model.Status, error) {
|
||||||
|
switch s {
|
||||||
|
case "pending", "waiting", "requested", "action_required":
|
||||||
|
return []actions_model.Status{actions_model.StatusBlocked}, nil
|
||||||
|
case "queued":
|
||||||
|
return []actions_model.Status{actions_model.StatusWaiting}, nil
|
||||||
|
case "in_progress":
|
||||||
|
return []actions_model.Status{actions_model.StatusRunning}, nil
|
||||||
|
case "completed":
|
||||||
|
return []actions_model.Status{
|
||||||
|
actions_model.StatusSuccess,
|
||||||
|
actions_model.StatusFailure,
|
||||||
|
actions_model.StatusSkipped,
|
||||||
|
actions_model.StatusCancelled,
|
||||||
|
}, nil
|
||||||
|
case "failure":
|
||||||
|
return []actions_model.Status{actions_model.StatusFailure}, nil
|
||||||
|
case "success":
|
||||||
|
return []actions_model.Status{actions_model.StatusSuccess}, nil
|
||||||
|
case "skipped", "neutral":
|
||||||
|
return []actions_model.Status{actions_model.StatusSkipped}, nil
|
||||||
|
case "cancelled", "timed_out":
|
||||||
|
return []actions_model.Status{actions_model.StatusCancelled}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid status %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRuns lists jobs for api route validated ownerID and repoID
|
||||||
|
// ownerID == 0 and repoID == 0 means all runs
|
||||||
|
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
||||||
|
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
||||||
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// Access rights are checked at the API route level
|
||||||
|
func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
opts := actions_model.FindRunOptions{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
RepoID: repoID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
if event := ctx.Req.URL.Query().Get("event"); event != "" {
|
||||||
|
opts.TriggerEvent = webhook.HookEventType(event)
|
||||||
|
}
|
||||||
|
if branch := ctx.Req.URL.Query().Get("branch"); branch != "" {
|
||||||
|
opts.Ref = string(git.RefNameFromBranch(branch))
|
||||||
|
}
|
||||||
|
if statuses, ok := ctx.Req.URL.Query()["status"]; ok {
|
||||||
|
for _, status := range statuses {
|
||||||
|
values, err := convertToInternal(status)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.Status = append(opts.Status, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if actor := ctx.Req.URL.Query().Get("actor"); actor != "" {
|
||||||
|
user, err := user_model.GetUserByName(ctx, actor)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.TriggerUserID = user.ID
|
||||||
|
}
|
||||||
|
if headSHA := ctx.Req.URL.Query().Get("head_sha"); headSHA != "" {
|
||||||
|
opts.CommitSHA = headSHA
|
||||||
|
}
|
||||||
|
|
||||||
|
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(api.ActionWorkflowRunsResponse)
|
||||||
|
res.TotalCount = total
|
||||||
|
|
||||||
|
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||||
|
isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
|
||||||
|
for i := range runs {
|
||||||
|
var repository *repo_model.Repository
|
||||||
|
if isRepoLevel {
|
||||||
|
repository = ctx.Repo.Repository
|
||||||
|
} else {
|
||||||
|
repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i])
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Entries[i] = convertedRun
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, &res)
|
||||||
|
}
|
|
@ -443,6 +443,34 @@ type swaggerRepoTasksList struct {
|
||||||
Body api.ActionTaskResponse `json:"body"`
|
Body api.ActionTaskResponse `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkflowRunsList
|
||||||
|
// swagger:response WorkflowRunsList
|
||||||
|
type swaggerActionWorkflowRunsResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionWorkflowRunsResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkflowRun
|
||||||
|
// swagger:response WorkflowRun
|
||||||
|
type swaggerWorkflowRun struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionWorkflowRun `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkflowJobsList
|
||||||
|
// swagger:response WorkflowJobsList
|
||||||
|
type swaggerActionWorkflowJobsResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionWorkflowJobsResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkflowJob
|
||||||
|
// swagger:response WorkflowJob
|
||||||
|
type swaggerWorkflowJob struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionWorkflowJob `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// ArtifactsList
|
// ArtifactsList
|
||||||
// swagger:response ArtifactsList
|
// swagger:response ArtifactsList
|
||||||
type swaggerRepoArtifactsList struct {
|
type swaggerRepoArtifactsList struct {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
@ -356,3 +357,86 @@ func ListVariables(ctx *context.APIContext) {
|
||||||
ctx.SetTotalCountHeader(count)
|
ctx.SetTotalCountHeader(count)
|
||||||
ctx.JSON(http.StatusOK, variables)
|
ctx.JSON(http.StatusOK, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListWorkflowRuns lists workflow runs
|
||||||
|
func ListWorkflowRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/runs user getUserWorkflowRuns
|
||||||
|
// ---
|
||||||
|
// summary: Get workflow runs
|
||||||
|
// parameters:
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: workflow event name
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: branch
|
||||||
|
// in: query
|
||||||
|
// description: workflow branch
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: actor
|
||||||
|
// in: query
|
||||||
|
// description: triggered by user
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: triggering sha of the workflow run
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowRunsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRuns(ctx, ctx.Doer.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWorkflowJobs lists workflow jobs
|
||||||
|
func ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/jobs user getUserWorkflowJobs
|
||||||
|
// ---
|
||||||
|
// summary: Get workflow jobs
|
||||||
|
// parameters:
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowJobsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
shared.ListJobs(ctx, ctx.Doer.ID, 0, 0)
|
||||||
|
}
|
||||||
|
|
|
@ -173,6 +173,7 @@ func updateHookEvents(events []string) webhook_module.HookEvents {
|
||||||
hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
|
hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
|
||||||
hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
|
hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
|
||||||
hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
|
hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
|
||||||
|
hookEvents[webhook_module.HookEventWorkflowRun] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowRun), true)
|
||||||
hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
|
hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
|
||||||
|
|
||||||
// Issues
|
// Issues
|
||||||
|
|
|
@ -485,7 +485,12 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou
|
||||||
}
|
}
|
||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, job)
|
actions_service.CreateCommitStatus(ctx, job)
|
||||||
_ = job.LoadAttributes(ctx)
|
// Sync run status with db
|
||||||
|
job.Run = nil
|
||||||
|
if err := job.LoadAttributes(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -557,7 +562,16 @@ func Cancel(ctx *context_module.Context) {
|
||||||
_ = job.LoadAttributes(ctx)
|
_ = job.LoadAttributes(ctx)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
}
|
}
|
||||||
|
if len(updatedjobs) > 0 {
|
||||||
|
job := updatedjobs[0]
|
||||||
|
// Sync run status with db
|
||||||
|
job.Run = nil
|
||||||
|
if err := job.LoadAttributes(ctx); err != nil {
|
||||||
|
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
|
}
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +613,14 @@ func Approve(ctx *context_module.Context) {
|
||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, jobs...)
|
actions_service.CreateCommitStatus(ctx, jobs...)
|
||||||
|
|
||||||
|
if len(updatedjobs) > 0 {
|
||||||
|
job := updatedjobs[0]
|
||||||
|
// Sync run status with db
|
||||||
|
job.Run = nil
|
||||||
|
_ = job.LoadAttributes(ctx)
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
|
}
|
||||||
|
|
||||||
for _, job := range updatedjobs {
|
for _, job := range updatedjobs {
|
||||||
_ = job.LoadAttributes(ctx)
|
_ = job.LoadAttributes(ctx)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
|
|
@ -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.HookEventWorkflowRun: form.WorkflowRun,
|
||||||
webhook_module.HookEventWorkflowJob: form.WorkflowJob,
|
webhook_module.HookEventWorkflowJob: form.WorkflowJob,
|
||||||
},
|
},
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
|
|
|
@ -42,6 +42,10 @@ func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.Ac
|
||||||
_ = job.LoadAttributes(ctx)
|
_ = job.LoadAttributes(ctx)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
}
|
}
|
||||||
|
if len(jobs) > 0 {
|
||||||
|
job := jobs[0]
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +127,11 @@ func CancelAbandonedJobs(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, job)
|
CreateCommitStatus(ctx, job)
|
||||||
if updated {
|
if updated {
|
||||||
|
// Sync run status with db
|
||||||
|
job.Run = nil
|
||||||
_ = job.LoadAttributes(ctx)
|
_ = job.LoadAttributes(ctx)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,4 +33,8 @@ type API interface {
|
||||||
GetRunner(*context.APIContext)
|
GetRunner(*context.APIContext)
|
||||||
// DeleteRunner delete runner
|
// DeleteRunner delete runner
|
||||||
DeleteRunner(*context.APIContext)
|
DeleteRunner(*context.APIContext)
|
||||||
|
// ListWorkflowJobs list jobs
|
||||||
|
ListWorkflowJobs(*context.APIContext)
|
||||||
|
// ListWorkflowRuns list runs
|
||||||
|
ListWorkflowRuns(*context.APIContext)
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,24 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
||||||
_ = job.LoadAttributes(ctx)
|
_ = job.LoadAttributes(ctx)
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||||
}
|
}
|
||||||
|
if len(jobs) > 0 {
|
||||||
|
runUpdated := true
|
||||||
|
for _, job := range jobs {
|
||||||
|
if !job.Status.IsDone() {
|
||||||
|
runUpdated = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if runUpdated {
|
||||||
|
// Sync run status with db
|
||||||
|
jobs[0].Run = nil
|
||||||
|
if err := jobs[0].LoadAttributes(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
run := jobs[0].Run
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,16 @@ package actions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
perm_model "code.gitea.io/gitea/models/perm"
|
perm_model "code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/repository"
|
"code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -762,3 +765,41 @@ func (n *actionsNotifier) MigrateRepository(ctx context.Context, doer, u *user_m
|
||||||
Sender: convert.ToUser(ctx, doer, nil),
|
Sender: convert.ToUser(ctx, doer, nil),
|
||||||
}).Notify(ctx)
|
}).Notify(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
|
||||||
|
ctx = withMethod(ctx, "WorkflowRunStatusUpdate")
|
||||||
|
|
||||||
|
var org *api.Organization
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
|
||||||
|
}
|
||||||
|
|
||||||
|
status := convert.ToWorkflowRunAction(run.Status)
|
||||||
|
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(context.Background(), repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("OpenRepository: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetActionWorkflow: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("ToActionWorkflowRun: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newNotifyInput(repo, sender, webhook_module.HookEventWorkflowRun).WithPayload(&api.WorkflowRunPayload{
|
||||||
|
Action: status,
|
||||||
|
Workflow: convertedWorkflow,
|
||||||
|
WorkflowRun: convertedRun,
|
||||||
|
Organization: org,
|
||||||
|
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeOwner}),
|
||||||
|
Sender: convert.ToUser(ctx, sender, nil),
|
||||||
|
}).Notify(ctx)
|
||||||
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipWorkflows(input, commit) {
|
if skipWorkflows(ctx, input, commit) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
|
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
|
func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
|
||||||
// skip workflow runs with a configured skip-ci string in commit message or pr title if the event is push or pull_request(_sync)
|
// skip workflow runs with a configured skip-ci string in commit message or pr title if the event is push or pull_request(_sync)
|
||||||
// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
|
// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
|
||||||
skipWorkflowEvents := []webhook_module.HookEventType{
|
skipWorkflowEvents := []webhook_module.HookEventType{
|
||||||
|
@ -263,6 +263,27 @@ func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if input.Event == webhook_module.HookEventWorkflowRun {
|
||||||
|
wrun, ok := input.Payload.(*api.WorkflowRunPayload)
|
||||||
|
for i := 0; i < 5 && ok && wrun.WorkflowRun != nil; i++ {
|
||||||
|
if wrun.WorkflowRun.Event != "workflow_run" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r, err := actions_model.GetRunByRepoAndID(ctx, input.Repo.ID, wrun.WorkflowRun.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetRunByRepoAndID: %v", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
wrun, err = r.GetWorkflowRunEventPayload()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetWorkflowRunEventPayload: %v", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// skip workflow runs events exceeding the maxiumum of 5 recursive events
|
||||||
|
log.Debug("repo %s: skipped workflow_run because of recursive event of 5", input.Repo.RepoPath())
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,6 +393,15 @@ func handleWorkflows(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
CreateCommitStatus(ctx, alljobs...)
|
CreateCommitStatus(ctx, alljobs...)
|
||||||
|
if len(alljobs) > 0 {
|
||||||
|
job := alljobs[0]
|
||||||
|
err := job.LoadRun(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LoadRun: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
|
}
|
||||||
for _, job := range alljobs {
|
for _, job := range alljobs {
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, input.Repo, input.Doer, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, input.Repo, input.Doer, job, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("LoadAttributes: %v", err)
|
log.Error("LoadAttributes: %v", err)
|
||||||
}
|
}
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
|
||||||
for _, job := range allJobs {
|
for _, job := range allJobs {
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, run.Repo, run.TriggerUser, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, run.Repo, run.TriggerUser, job, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
@ -31,51 +28,8 @@ import (
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getActionWorkflowEntry(ctx *context.APIContext, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
|
|
||||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
|
||||||
cfg := cfgUnit.ActionsConfig()
|
|
||||||
|
|
||||||
defaultBranch, _ := commit.GetBranchName()
|
|
||||||
|
|
||||||
workflowURL := fmt.Sprintf("%s/actions/workflows/%s", ctx.Repo.Repository.APIURL(), url.PathEscape(entry.Name()))
|
|
||||||
workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", ctx.Repo.Repository.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), url.PathEscape(entry.Name()))
|
|
||||||
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", ctx.Repo.Repository.HTMLURL(ctx), url.PathEscape(entry.Name()), url.QueryEscape(ctx.Repo.Repository.DefaultBranch))
|
|
||||||
|
|
||||||
// See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
|
|
||||||
// State types:
|
|
||||||
// - active
|
|
||||||
// - deleted
|
|
||||||
// - disabled_fork
|
|
||||||
// - disabled_inactivity
|
|
||||||
// - disabled_manually
|
|
||||||
state := "active"
|
|
||||||
if cfg.IsWorkflowDisabled(entry.Name()) {
|
|
||||||
state = "disabled_manually"
|
|
||||||
}
|
|
||||||
|
|
||||||
// The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined
|
|
||||||
// by retrieving the first and last commits for the file history. The first commit would indicate the creation date,
|
|
||||||
// while the last commit would represent the modification date. The DeletedAt could be determined by identifying
|
|
||||||
// the last commit where the file existed. However, this implementation has not been done here yet, as it would likely
|
|
||||||
// cause a significant performance degradation.
|
|
||||||
createdAt := commit.Author.When
|
|
||||||
updatedAt := commit.Author.When
|
|
||||||
|
|
||||||
return &api.ActionWorkflow{
|
|
||||||
ID: entry.Name(),
|
|
||||||
Name: entry.Name(),
|
|
||||||
Path: path.Join(folder, entry.Name()),
|
|
||||||
State: state,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
URL: workflowURL,
|
|
||||||
HTMLURL: workflowRepoURL,
|
|
||||||
BadgeURL: badgeURL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnable bool) error {
|
func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnable bool) error {
|
||||||
workflow, err := GetActionWorkflow(ctx, workflowID)
|
workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -92,42 +46,6 @@ func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnabl
|
||||||
return repo_model.UpdateRepoUnit(ctx, cfgUnit)
|
return repo_model.UpdateRepoUnit(ctx, cfgUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListActionWorkflows(ctx *context.APIContext) ([]*api.ActionWorkflow, error) {
|
|
||||||
defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIError(http.StatusNotFound, err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
workflows := make([]*api.ActionWorkflow, len(entries))
|
|
||||||
for i, entry := range entries {
|
|
||||||
workflows[i] = getActionWorkflowEntry(ctx, defaultBranchCommit, folder, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return workflows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetActionWorkflow(ctx *context.APIContext, workflowID string) (*api.ActionWorkflow, error) {
|
|
||||||
entries, err := ListActionWorkflows(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.Name == workflowID {
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error {
|
func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error {
|
||||||
if workflowID == "" {
|
if workflowID == "" {
|
||||||
return util.ErrorWrapLocale(
|
return util.ErrorWrapLocale(
|
||||||
|
@ -285,6 +203,15 @@ 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...)
|
||||||
|
if len(allJobs) > 0 {
|
||||||
|
job := allJobs[0]
|
||||||
|
err := job.LoadRun(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LoadRun: %v", err)
|
||||||
|
} else {
|
||||||
|
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, job := range allJobs {
|
for _, job := range allJobs {
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
package convert
|
package convert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,6 +17,7 @@ import (
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
"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"
|
||||||
|
@ -22,6 +26,7 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/actions"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
@ -32,6 +37,7 @@ import (
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
|
"github.com/nektos/act/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToEmail convert models.EmailAddress to api.Email
|
// ToEmail convert models.EmailAddress to api.Email
|
||||||
|
@ -241,6 +247,242 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflowRun, error) {
|
||||||
|
err := run.LoadAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
status, conclusion := ToActionsStatus(run.Status)
|
||||||
|
return &api.ActionWorkflowRun{
|
||||||
|
ID: run.ID,
|
||||||
|
URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID),
|
||||||
|
HTMLURL: run.HTMLURL(),
|
||||||
|
RunNumber: run.Index,
|
||||||
|
StartedAt: run.Started.AsLocalTime(),
|
||||||
|
CompletedAt: run.Stopped.AsLocalTime(),
|
||||||
|
Event: string(run.Event),
|
||||||
|
DisplayTitle: run.Title,
|
||||||
|
HeadBranch: git.RefName(run.Ref).BranchName(),
|
||||||
|
HeadSha: run.CommitSHA,
|
||||||
|
Status: status,
|
||||||
|
Conclusion: conclusion,
|
||||||
|
Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref),
|
||||||
|
Repository: ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||||
|
TriggerActor: ToUser(ctx, run.TriggerUser, nil),
|
||||||
|
// We do not have a way to get a different User for the actor than the trigger user
|
||||||
|
Actor: ToUser(ctx, run.TriggerUser, nil),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToWorkflowRunAction(status actions_model.Status) string {
|
||||||
|
var action string
|
||||||
|
switch status {
|
||||||
|
case actions_model.StatusWaiting, actions_model.StatusBlocked:
|
||||||
|
action = "requested"
|
||||||
|
case actions_model.StatusRunning:
|
||||||
|
action = "in_progress"
|
||||||
|
}
|
||||||
|
if status.IsDone() {
|
||||||
|
action = "completed"
|
||||||
|
}
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToActionsStatus(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"
|
||||||
|
case actions_model.StatusSkipped:
|
||||||
|
conclusion = "skipped"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action, conclusion
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToActionWorkflowJob convert a actions_model.ActionRunJob to an api.ActionWorkflowJob
|
||||||
|
// task is optional and can be nil
|
||||||
|
func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask, job *actions_model.ActionRunJob) (*api.ActionWorkflowJob, error) {
|
||||||
|
err := job.LoadAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jobIndex := 0
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, j := range jobs {
|
||||||
|
if j.ID == job.ID {
|
||||||
|
jobIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status, conclusion := ToActionsStatus(job.Status)
|
||||||
|
var runnerID int64
|
||||||
|
var runnerName string
|
||||||
|
var steps []*api.ActionWorkflowStep
|
||||||
|
|
||||||
|
if job.TaskID != 0 {
|
||||||
|
if task == nil {
|
||||||
|
task, _, err = db.GetByID[actions_model.ActionTask](ctx, job.TaskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := ToActionsStatus(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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.ActionWorkflowJob{
|
||||||
|
ID: job.ID,
|
||||||
|
// missing api endpoint for this location
|
||||||
|
URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), 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(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
|
||||||
|
cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
|
||||||
|
cfg := cfgUnit.ActionsConfig()
|
||||||
|
|
||||||
|
defaultBranch, _ := commit.GetBranchName()
|
||||||
|
|
||||||
|
workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), url.PathEscape(entry.Name()))
|
||||||
|
workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), url.PathEscape(entry.Name()))
|
||||||
|
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), url.PathEscape(entry.Name()), url.QueryEscape(repo.DefaultBranch))
|
||||||
|
|
||||||
|
// See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
|
||||||
|
// State types:
|
||||||
|
// - active
|
||||||
|
// - deleted
|
||||||
|
// - disabled_fork
|
||||||
|
// - disabled_inactivity
|
||||||
|
// - disabled_manually
|
||||||
|
state := "active"
|
||||||
|
if cfg.IsWorkflowDisabled(entry.Name()) {
|
||||||
|
state = "disabled_manually"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined
|
||||||
|
// by retrieving the first and last commits for the file history. The first commit would indicate the creation date,
|
||||||
|
// while the last commit would represent the modification date. The DeletedAt could be determined by identifying
|
||||||
|
// the last commit where the file existed. However, this implementation has not been done here yet, as it would likely
|
||||||
|
// cause a significant performance degradation.
|
||||||
|
createdAt := commit.Author.When
|
||||||
|
updatedAt := commit.Author.When
|
||||||
|
|
||||||
|
content, err := actions.GetContentFromEntry(entry)
|
||||||
|
name := entry.Name()
|
||||||
|
if err == nil {
|
||||||
|
workflow, err := model.ReadWorkflow(bytes.NewReader(content))
|
||||||
|
if err == nil {
|
||||||
|
// Only use the name when specified in the workflow file
|
||||||
|
if workflow.Name != "" {
|
||||||
|
name = workflow.Name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("getActionWorkflowEntry: Failed to parse workflow: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("getActionWorkflowEntry: Failed to get content from entry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.ActionWorkflow{
|
||||||
|
ID: entry.Name(),
|
||||||
|
Name: name,
|
||||||
|
Path: path.Join(folder, entry.Name()),
|
||||||
|
State: state,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
URL: workflowURL,
|
||||||
|
HTMLURL: workflowRepoURL,
|
||||||
|
BadgeURL: badgeURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListActionWorkflows(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository) ([]*api.ActionWorkflow, error) {
|
||||||
|
defaultBranchCommit, err := gitrepo.GetBranchCommit(repo.DefaultBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
workflows := make([]*api.ActionWorkflow, len(entries))
|
||||||
|
for i, entry := range entries {
|
||||||
|
workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, folder, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActionWorkflow(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository, workflowID string) (*api.ActionWorkflow, error) {
|
||||||
|
entries, err := ListActionWorkflows(ctx, gitrepo, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.ID == workflowID {
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
// ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact
|
// ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact
|
||||||
func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) {
|
func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) {
|
||||||
url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID)
|
url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID)
|
||||||
|
|
|
@ -233,6 +233,7 @@ type WebhookForm struct {
|
||||||
Release bool
|
Release bool
|
||||||
Package bool
|
Package bool
|
||||||
Status bool
|
Status bool
|
||||||
|
WorkflowRun bool
|
||||||
WorkflowJob bool
|
WorkflowJob bool
|
||||||
Active bool
|
Active bool
|
||||||
BranchFilter string `binding:"GlobPattern"`
|
BranchFilter string `binding:"GlobPattern"`
|
||||||
|
|
|
@ -79,5 +79,7 @@ type Notifier interface {
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun)
|
||||||
|
|
||||||
WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask)
|
WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask)
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,6 +376,12 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
|
||||||
|
for _, notifier := range notifiers {
|
||||||
|
notifier.WorkflowRunStatusUpdate(ctx, repo, sender, run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
|
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 {
|
for _, notifier := range notifiers {
|
||||||
notifier.WorkflowJobStatusUpdate(ctx, repo, sender, job, task)
|
notifier.WorkflowJobStatusUpdate(ctx, repo, sender, job, task)
|
||||||
|
|
|
@ -214,5 +214,8 @@ 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) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
|
||||||
|
}
|
||||||
|
|
||||||
func (*NullNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
|
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) WorkflowRun(p *api.WorkflowRunPayload) (DingtalkPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return createDingtalkPayload(text, text, "Workflow Run", p.WorkflowRun.HTMLURL), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (dingtalkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DingtalkPayload, error) {
|
func (dingtalkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DingtalkPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,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) WorkflowRun(p *api.WorkflowRunPayload) (DiscordPayload, error) {
|
||||||
|
text, color := getWorkflowRunPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
return d.createPayload(p.Sender, text, "", p.WorkflowRun.HTMLURL, color), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d discordConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DiscordPayload, error) {
|
func (d discordConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DiscordPayload, error) {
|
||||||
text, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
text, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,12 @@ func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, err
|
||||||
return newFeishuTextPayload(text), nil
|
return newFeishuTextPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (feishuConvertor) WorkflowRun(p *api.WorkflowRunPayload) (FeishuPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return newFeishuTextPayload(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
|
func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -327,6 +327,37 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte
|
||||||
return text, color
|
return text, color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getWorkflowRunPayloadInfo(p *api.WorkflowRunPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
||||||
|
description := p.WorkflowRun.Conclusion
|
||||||
|
if description == "" {
|
||||||
|
description = p.WorkflowRun.Status
|
||||||
|
}
|
||||||
|
refLink := linkFormatter(p.WorkflowRun.HTMLURL, fmt.Sprintf("%s(#%d)", p.WorkflowRun.DisplayTitle, p.WorkflowRun.ID)+"["+base.ShortSha(p.WorkflowRun.HeadSha)+"]:"+description)
|
||||||
|
|
||||||
|
text = fmt.Sprintf("Workflow Run %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 += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return text, color
|
||||||
|
}
|
||||||
|
|
||||||
func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
||||||
description := p.WorkflowJob.Conclusion
|
description := p.WorkflowJob.Conclusion
|
||||||
if description == "" {
|
if description == "" {
|
||||||
|
|
|
@ -252,6 +252,12 @@ func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, erro
|
||||||
return m.newPayload(text)
|
return m.newPayload(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m matrixConvertor) WorkflowRun(p *api.WorkflowRunPayload) (MatrixPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
return m.newPayload(text)
|
||||||
|
}
|
||||||
|
|
||||||
func (m matrixConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MatrixPayload, error) {
|
func (m matrixConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MatrixPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,20 @@ func (m msteamsConvertor) Status(p *api.CommitStatusPayload) (MSTeamsPayload, er
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msteamsConvertor) WorkflowRun(p *api.WorkflowRunPayload) (MSTeamsPayload, error) {
|
||||||
|
title, color := getWorkflowRunPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
return createMSTeamsPayload(
|
||||||
|
p.Repo,
|
||||||
|
p.Sender,
|
||||||
|
title,
|
||||||
|
"",
|
||||||
|
p.WorkflowRun.HTMLURL,
|
||||||
|
color,
|
||||||
|
&MSTeamsFact{"WorkflowRun:", p.WorkflowRun.DisplayTitle},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload, error) {
|
func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload, error) {
|
||||||
title, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
title, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,8 @@ package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
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"
|
||||||
|
@ -18,6 +16,7 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/repository"
|
"code.gitea.io/gitea/modules/repository"
|
||||||
|
@ -956,72 +955,17 @@ func (*webhookNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_
|
||||||
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
|
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
|
||||||
}
|
}
|
||||||
|
|
||||||
err := job.LoadAttributes(ctx)
|
status, _ := convert.ToActionsStatus(job.Status)
|
||||||
|
|
||||||
|
convertedJob, err := convert.ToActionWorkflowJob(ctx, repo, task, job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error loading job attributes: %v", err)
|
log.Error("ToActionWorkflowJob: %v", err)
|
||||||
return
|
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{
|
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowJob, &api.WorkflowJobPayload{
|
||||||
Action: status,
|
Action: status,
|
||||||
WorkflowJob: &api.ActionWorkflowJob{
|
WorkflowJob: convertedJob,
|
||||||
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,
|
Organization: org,
|
||||||
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
|
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
|
||||||
Sender: convert.ToUser(ctx, sender, nil),
|
Sender: convert.ToUser(ctx, sender, nil),
|
||||||
|
@ -1030,28 +974,46 @@ func (*webhookNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toActionStatus(status actions_model.Status) (string, string) {
|
func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
|
||||||
var action string
|
source := EventSource{
|
||||||
var conclusion string
|
Repository: repo,
|
||||||
switch status {
|
Owner: repo.Owner,
|
||||||
// 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"
|
var org *api.Organization
|
||||||
switch status {
|
if repo.Owner.IsOrganization() {
|
||||||
case actions_model.StatusSuccess:
|
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
|
||||||
conclusion = "success"
|
}
|
||||||
case actions_model.StatusCancelled:
|
|
||||||
conclusion = "cancelled"
|
status := convert.ToWorkflowRunAction(run.Status)
|
||||||
case actions_model.StatusFailure:
|
|
||||||
conclusion = "failure"
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||||
}
|
if err != nil {
|
||||||
|
log.Error("OpenRepository: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetActionWorkflow: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("ToActionWorkflowRun: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowRun, &api.WorkflowRunPayload{
|
||||||
|
Action: status,
|
||||||
|
Workflow: convertedWorkflow,
|
||||||
|
WorkflowRun: convertedRun,
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
return action, conclusion
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,10 @@ func (pc packagistConvertor) Status(_ *api.CommitStatusPayload) (PackagistPayloa
|
||||||
return PackagistPayload{}, nil
|
return PackagistPayload{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc packagistConvertor) WorkflowRun(_ *api.WorkflowRunPayload) (PackagistPayload, error) {
|
||||||
|
return PackagistPayload{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (pc packagistConvertor) WorkflowJob(_ *api.WorkflowJobPayload) (PackagistPayload, error) {
|
func (pc packagistConvertor) WorkflowJob(_ *api.WorkflowJobPayload) (PackagistPayload, error) {
|
||||||
return PackagistPayload{}, nil
|
return PackagistPayload{}, 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)
|
||||||
|
WorkflowRun(*api.WorkflowRunPayload) (T, error)
|
||||||
WorkflowJob(*api.WorkflowJobPayload) (T, error)
|
WorkflowJob(*api.WorkflowJobPayload) (T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +82,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.HookEventWorkflowRun:
|
||||||
|
return convertUnmarshalledJSON(rc.WorkflowRun, data)
|
||||||
case webhook_module.HookEventWorkflowJob:
|
case webhook_module.HookEventWorkflowJob:
|
||||||
return convertUnmarshalledJSON(rc.WorkflowJob, data)
|
return convertUnmarshalledJSON(rc.WorkflowJob, data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) WorkflowRun(p *api.WorkflowRunPayload) (SlackPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, SlackLinkFormatter, true)
|
||||||
|
|
||||||
|
return s.createPayload(text, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s slackConvertor) WorkflowJob(p *api.WorkflowJobPayload) (SlackPayload, error) {
|
func (s slackConvertor) WorkflowJob(p *api.WorkflowJobPayload) (SlackPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, SlackLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, SlackLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,12 @@ func (t telegramConvertor) Status(p *api.CommitStatusPayload) (TelegramPayload,
|
||||||
return createTelegramPayloadHTML(text), nil
|
return createTelegramPayloadHTML(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (telegramConvertor) WorkflowRun(p *api.WorkflowRunPayload) (TelegramPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
return createTelegramPayloadHTML(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload, error) {
|
func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,12 @@ func (wc wechatworkConvertor) Status(p *api.CommitStatusPayload) (WechatworkPayl
|
||||||
return newWechatworkMarkdownPayload(text), nil
|
return newWechatworkMarkdownPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wc wechatworkConvertor) WorkflowRun(p *api.WorkflowRunPayload) (WechatworkPayload, error) {
|
||||||
|
text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
return newWechatworkMarkdownPayload(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (wc wechatworkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (WechatworkPayload, error) {
|
func (wc wechatworkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (WechatworkPayload, error) {
|
||||||
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,16 @@
|
||||||
<div class="fourteen wide column">
|
<div class="fourteen wide column">
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.event_header_workflow"}}</label>
|
<label>{{ctx.Locale.Tr "repo.settings.event_header_workflow"}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Workflow Run Event -->
|
||||||
|
<div class="seven wide column">
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="workflow_run" type="checkbox" {{if .Webhook.HookEvents.Get "workflow_run"}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.settings.event_workflow_run"}}</label>
|
||||||
|
<span class="help">{{ctx.Locale.Tr "repo.settings.event_workflow_run_desc"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Workflow Job Event -->
|
<!-- Workflow Job Event -->
|
||||||
<div class="seven wide column">
|
<div class="seven wide column">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -75,6 +75,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/actions/jobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Lists all jobs",
|
||||||
|
"operationId": "listAdminWorkflowJobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJobsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/actions/runners": {
|
"/admin/actions/runners": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -177,6 +220,73 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/actions/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Lists all runs",
|
||||||
|
"operationId": "listAdminWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow event name",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggered by user",
|
||||||
|
"name": "actor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowRunsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/cron": {
|
"/admin/cron": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -1799,6 +1909,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/actions/jobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get org-level workflow jobs",
|
||||||
|
"operationId": "getOrgWorkflowJobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJobsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/actions/runners": {
|
"/orgs/{org}/actions/runners": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -1957,6 +2117,80 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/actions/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get org-level workflow runs",
|
||||||
|
"operationId": "getOrgWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow event name",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggered by user",
|
||||||
|
"name": "actor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowRunsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/actions/secrets": {
|
"/orgs/{org}/actions/secrets": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -4519,6 +4753,109 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/jobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Lists all jobs for a repository",
|
||||||
|
"operationId": "listWorkflowJobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJobsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/jobs/{job_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Gets a specific workflow job for a workflow run",
|
||||||
|
"operationId": "getWorkflowJob",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the job",
|
||||||
|
"name": "job_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJob"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/jobs/{job_id}/logs": {
|
"/repos/{owner}/{repo}/actions/jobs/{job_id}/logs": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -4758,7 +5095,132 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Lists all runs for a repository run",
|
||||||
|
"operationId": "getWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow event name",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggered by user",
|
||||||
|
"name": "actor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ArtifactsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/runs/{run}": {
|
"/repos/{owner}/{repo}/actions/runs/{run}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Gets a specific workflow run",
|
||||||
|
"operationId": "GetWorkflowRun",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the run",
|
||||||
|
"name": "run",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowRun"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -4856,6 +5318,70 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runs/{run}/jobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Lists all jobs for a workflow run",
|
||||||
|
"operationId": "listWorkflowRunJobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "runid of the workflow run",
|
||||||
|
"name": "run",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJobsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/secrets": {
|
"/repos/{owner}/{repo}/actions/secrets": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -17584,6 +18110,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/actions/jobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get workflow jobs",
|
||||||
|
"operationId": "getUserWorkflowJobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowJobsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/actions/runners": {
|
"/user/actions/runners": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -17701,6 +18270,73 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/actions/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get workflow runs",
|
||||||
|
"operationId": "getUserWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow event name",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggered by user",
|
||||||
|
"name": "actor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowRunsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/actions/secrets/{secretname}": {
|
"/user/actions/secrets/{secretname}": {
|
||||||
"put": {
|
"put": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
@ -20440,23 +21076,251 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
"ActionWorkflowRun": {
|
"ActionWorkflowJob": {
|
||||||
"description": "ActionWorkflowRun represents a WorkflowRun",
|
"description": "ActionWorkflowJob represents a WorkflowJob",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"completed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "CompletedAt"
|
||||||
|
},
|
||||||
|
"conclusion": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Conclusion"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "CreatedAt"
|
||||||
|
},
|
||||||
|
"head_branch": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HeadBranch"
|
||||||
|
},
|
||||||
"head_sha": {
|
"head_sha": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "HeadSha"
|
"x-go-name": "HeadSha"
|
||||||
},
|
},
|
||||||
|
"html_url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HTMLURL"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "ID"
|
"x-go-name": "ID"
|
||||||
},
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Labels"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"run_attempt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunAttempt"
|
||||||
|
},
|
||||||
|
"run_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunID"
|
||||||
|
},
|
||||||
|
"run_url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "RunURL"
|
||||||
|
},
|
||||||
|
"runner_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunnerID"
|
||||||
|
},
|
||||||
|
"runner_name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "RunnerName"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "StartedAt"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Status"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowStep"
|
||||||
|
},
|
||||||
|
"x-go-name": "Steps"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionWorkflowJobsResponse": {
|
||||||
|
"description": "ActionWorkflowJobsResponse returns ActionWorkflowJobs",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"jobs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowJob"
|
||||||
|
},
|
||||||
|
"x-go-name": "Entries"
|
||||||
|
},
|
||||||
|
"total_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "TotalCount"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionWorkflowRun": {
|
||||||
|
"description": "ActionWorkflowRun represents a WorkflowRun",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"actor": {
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
},
|
||||||
|
"completed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "CompletedAt"
|
||||||
|
},
|
||||||
|
"conclusion": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Conclusion"
|
||||||
|
},
|
||||||
|
"display_title": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "DisplayTitle"
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Event"
|
||||||
|
},
|
||||||
|
"head_branch": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HeadBranch"
|
||||||
|
},
|
||||||
|
"head_repository": {
|
||||||
|
"$ref": "#/definitions/Repository"
|
||||||
|
},
|
||||||
|
"head_sha": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HeadSha"
|
||||||
|
},
|
||||||
|
"html_url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HTMLURL"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Path"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"$ref": "#/definitions/Repository"
|
||||||
|
},
|
||||||
"repository_id": {
|
"repository_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "RepositoryID"
|
"x-go-name": "RepositoryID"
|
||||||
|
},
|
||||||
|
"run_attempt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunAttempt"
|
||||||
|
},
|
||||||
|
"run_number": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunNumber"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "StartedAt"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Status"
|
||||||
|
},
|
||||||
|
"trigger_actor": {
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionWorkflowRunsResponse": {
|
||||||
|
"description": "ActionWorkflowRunsResponse returns ActionWorkflowRuns",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"total_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "TotalCount"
|
||||||
|
},
|
||||||
|
"workflow_runs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowRun"
|
||||||
|
},
|
||||||
|
"x-go-name": "Entries"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionWorkflowStep": {
|
||||||
|
"description": "ActionWorkflowStep represents a step of a WorkflowJob",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"completed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "CompletedAt"
|
||||||
|
},
|
||||||
|
"conclusion": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Conclusion"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Number"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "StartedAt"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Status"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -28607,6 +29471,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"WorkflowJob": {
|
||||||
|
"description": "WorkflowJob",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowJob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkflowJobsList": {
|
||||||
|
"description": "WorkflowJobsList",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowJobsResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkflowRun": {
|
||||||
|
"description": "WorkflowRun",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowRun"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkflowRunsList": {
|
||||||
|
"description": "WorkflowRunsList",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionWorkflowRunsResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"description": "APIConflict is a conflict empty response"
|
"description": "APIConflict is a conflict empty response"
|
||||||
},
|
},
|
||||||
|
|
|
@ -720,7 +720,7 @@ func TestWorkflowDispatchPublicApi(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch
|
workflow_dispatch
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -800,7 +800,7 @@ func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -891,7 +891,7 @@ func TestWorkflowDispatchPublicApiJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -977,7 +977,7 @@ func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1071,7 +1071,7 @@ func TestWorkflowDispatchPublicApiWithInputsNonDefaultBranchJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch
|
workflow_dispatch
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1107,7 +1107,7 @@ jobs:
|
||||||
{
|
{
|
||||||
Operation: "update",
|
Operation: "update",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1209,7 +1209,7 @@ func TestWorkflowApi(t *testing.T) {
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
TreePath: ".gitea/workflows/dispatch.yml",
|
TreePath: ".gitea/workflows/dispatch.yml",
|
||||||
ContentReader: strings.NewReader(`name: test
|
ContentReader: strings.NewReader(`
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
@ -910,8 +910,7 @@ jobs:
|
||||||
assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha)
|
assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha)
|
||||||
assert.Equal(t, "repo1", payloads[3].Repo.Name)
|
assert.Equal(t, "repo1", payloads[3].Repo.Name)
|
||||||
assert.Equal(t, "user2/repo1", payloads[3].Repo.FullName)
|
assert.Equal(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, fmt.Sprintf("/actions/jobs/%d", 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.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0))
|
||||||
assert.Len(t, payloads[3].WorkflowJob.Steps, 1)
|
assert.Len(t, payloads[3].WorkflowJob.Steps, 1)
|
||||||
|
|
||||||
|
@ -947,9 +946,207 @@ jobs:
|
||||||
assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha)
|
assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha)
|
||||||
assert.Equal(t, "repo1", payloads[6].Repo.Name)
|
assert.Equal(t, "repo1", payloads[6].Repo.Name)
|
||||||
assert.Equal(t, "user2/repo1", payloads[6].Repo.FullName)
|
assert.Equal(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, fmt.Sprintf("/actions/jobs/%d", 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.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1))
|
||||||
assert.Len(t, payloads[6].WorkflowJob.Steps, 2)
|
assert.Len(t, payloads[6].WorkflowJob.Steps, 2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type workflowRunWebhook struct {
|
||||||
|
URL string
|
||||||
|
payloads []api.WorkflowRunPayload
|
||||||
|
triggeredEvent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_WebhookWorkflowRun(t *testing.T) {
|
||||||
|
webhookData := &workflowRunWebhook{}
|
||||||
|
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||||
|
assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_run", "X-GitHub-Event-Type should contain workflow_run")
|
||||||
|
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_run", "X-Gitea-Event-Type should contain workflow_run")
|
||||||
|
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_run", "X-Gogs-Event-Type should contain workflow_run")
|
||||||
|
content, _ := io.ReadAll(r.Body)
|
||||||
|
var payload api.WorkflowRunPayload
|
||||||
|
err := json.Unmarshal(content, &payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
webhookData.payloads = append(webhookData.payloads, payload)
|
||||||
|
webhookData.triggeredEvent = "workflow_run"
|
||||||
|
}, http.StatusOK)
|
||||||
|
defer provider.Close()
|
||||||
|
webhookData.URL = provider.URL()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
callback func(t *testing.T, webhookData *workflowRunWebhook)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "WorkflowRun",
|
||||||
|
callback: testWebhookWorkflowRun,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "WorkflowRunDepthLimit",
|
||||||
|
callback: testWebhookWorkflowRunDepthLimit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
webhookData.payloads = nil
|
||||||
|
webhookData.triggeredEvent = ""
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
test.callback(t, webhookData)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) {
|
||||||
|
// 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", webhookData.URL, "workflow_run")
|
||||||
|
|
||||||
|
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"}, false)
|
||||||
|
|
||||||
|
// 2.1 add workflow_run workflow file to the repo
|
||||||
|
|
||||||
|
opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+"dispatch.yml", `
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Push"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
jobs:
|
||||||
|
dispatch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo 'test the webhook'
|
||||||
|
`)
|
||||||
|
createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", opts)
|
||||||
|
|
||||||
|
// 2.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, "create "+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.Equal(t, "workflow_run", webhookData.triggeredEvent)
|
||||||
|
assert.Len(t, webhookData.payloads, 1)
|
||||||
|
assert.Equal(t, "requested", webhookData.payloads[0].Action)
|
||||||
|
assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
|
||||||
|
assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
|
||||||
|
assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
|
||||||
|
assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName)
|
||||||
|
|
||||||
|
// 4. Execute two Jobs
|
||||||
|
task := runner.fetchTask(t)
|
||||||
|
outcome := &mockTaskOutcome{
|
||||||
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
|
}
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
|
||||||
|
task = runner.fetchTask(t)
|
||||||
|
outcome = &mockTaskOutcome{
|
||||||
|
result: runnerv1.Result_RESULT_FAILURE,
|
||||||
|
}
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
|
||||||
|
// 7. validate the webhook is triggered
|
||||||
|
assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
|
||||||
|
assert.Len(t, webhookData.payloads, 3)
|
||||||
|
assert.Equal(t, "completed", webhookData.payloads[1].Action)
|
||||||
|
assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event)
|
||||||
|
|
||||||
|
// 3. validate the webhook is triggered
|
||||||
|
assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
|
||||||
|
assert.Len(t, webhookData.payloads, 3)
|
||||||
|
assert.Equal(t, "requested", webhookData.payloads[2].Action)
|
||||||
|
assert.Equal(t, "queued", webhookData.payloads[2].WorkflowRun.Status)
|
||||||
|
assert.Equal(t, "workflow_run", webhookData.payloads[2].WorkflowRun.Event)
|
||||||
|
assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[2].WorkflowRun.HeadBranch)
|
||||||
|
assert.Equal(t, commitID, webhookData.payloads[2].WorkflowRun.HeadSha)
|
||||||
|
assert.Equal(t, "repo1", webhookData.payloads[2].Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", webhookData.payloads[2].Repo.FullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWebhookWorkflowRunDepthLimit(t *testing.T, webhookData *workflowRunWebhook) {
|
||||||
|
// 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", webhookData.URL, "workflow_run")
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||||
|
|
||||||
|
gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 2. trigger the webhooks
|
||||||
|
|
||||||
|
// add workflow file to the repo
|
||||||
|
// init the workflow
|
||||||
|
wfTreePath := ".gitea/workflows/push.yml"
|
||||||
|
wfFileContent := `name: Endless Loop
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_run:
|
||||||
|
types:
|
||||||
|
- requested
|
||||||
|
jobs:
|
||||||
|
dispatch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo 'test the webhook'
|
||||||
|
`
|
||||||
|
opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+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.Equal(t, "workflow_run", webhookData.triggeredEvent)
|
||||||
|
// 1x push + 5x workflow_run requested chain
|
||||||
|
assert.Len(t, webhookData.payloads, 6)
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
assert.Equal(t, "requested", webhookData.payloads[i].Action)
|
||||||
|
assert.Equal(t, "queued", webhookData.payloads[i].WorkflowRun.Status)
|
||||||
|
assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[i].WorkflowRun.HeadBranch)
|
||||||
|
assert.Equal(t, commitID, webhookData.payloads[i].WorkflowRun.HeadSha)
|
||||||
|
if i == 0 {
|
||||||
|
assert.Equal(t, "push", webhookData.payloads[i].WorkflowRun.Event)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, "workflow_run", webhookData.payloads[i].WorkflowRun.Event)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "repo1", webhookData.payloads[i].Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", webhookData.payloads[i].Repo.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIWorkflowRun(t *testing.T) {
|
||||||
|
t.Run("AdminRuns", func(t *testing.T) {
|
||||||
|
testAPIWorkflowRunBasic(t, "/api/v1/admin/actions", "User1", 802, auth_model.AccessTokenScopeReadAdmin, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
})
|
||||||
|
t.Run("UserRuns", func(t *testing.T) {
|
||||||
|
testAPIWorkflowRunBasic(t, "/api/v1/user/actions", "User2", 803, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
})
|
||||||
|
t.Run("OrgRuns", func(t *testing.T) {
|
||||||
|
testAPIWorkflowRunBasic(t, "/api/v1/orgs/org3/actions", "User1", 802, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
})
|
||||||
|
t.Run("RepoRuns", func(t *testing.T) {
|
||||||
|
testAPIWorkflowRunBasic(t, "/api/v1/repos/org3/repo5/actions", "User2", 802, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIWorkflowRunBasic(t *testing.T, apiRootURL, userUsername string, runID int64, scope ...auth_model.AccessTokenScope) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
token := getUserToken(t, userUsername, scope...)
|
||||||
|
|
||||||
|
apiRunsURL := fmt.Sprintf("%s/%s", apiRootURL, "runs")
|
||||||
|
req := NewRequest(t, "GET", apiRunsURL).AddTokenAuth(token)
|
||||||
|
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionWorkflowRunsResponse{}
|
||||||
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
|
foundRun := false
|
||||||
|
|
||||||
|
for _, run := range runnerList.Entries {
|
||||||
|
// Verify filtering works
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", run.Status, "", "", "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, run.Conclusion, "", "", "", "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", run.HeadBranch, "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", run.Event, "", "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, run.HeadSha)
|
||||||
|
|
||||||
|
// Verify run url works
|
||||||
|
req := NewRequest(t, "GET", run.URL).AddTokenAuth(token)
|
||||||
|
runResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
apiRun := api.ActionWorkflowRun{}
|
||||||
|
DecodeJSON(t, runResp, &apiRun)
|
||||||
|
assert.Equal(t, run.ID, apiRun.ID)
|
||||||
|
assert.Equal(t, run.Status, apiRun.Status)
|
||||||
|
assert.Equal(t, run.Conclusion, apiRun.Conclusion)
|
||||||
|
assert.Equal(t, run.Event, apiRun.Event)
|
||||||
|
|
||||||
|
// Verify jobs list works
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s", run.URL, "jobs")).AddTokenAuth(token)
|
||||||
|
jobsResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
jobList := api.ActionWorkflowJobsResponse{}
|
||||||
|
DecodeJSON(t, jobsResp, &jobList)
|
||||||
|
|
||||||
|
if run.ID == runID {
|
||||||
|
foundRun = true
|
||||||
|
assert.Len(t, jobList.Entries, 1)
|
||||||
|
for _, job := range jobList.Entries {
|
||||||
|
// Check the jobs list of the run
|
||||||
|
verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, "", job.Status)
|
||||||
|
verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, job.Conclusion, "")
|
||||||
|
// Check the run independent job list
|
||||||
|
verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, "", job.Status)
|
||||||
|
verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, job.Conclusion, "")
|
||||||
|
|
||||||
|
// Verify job url works
|
||||||
|
req := NewRequest(t, "GET", job.URL).AddTokenAuth(token)
|
||||||
|
jobsResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
apiJob := api.ActionWorkflowJob{}
|
||||||
|
DecodeJSON(t, jobsResp, &apiJob)
|
||||||
|
assert.Equal(t, job.ID, apiJob.ID)
|
||||||
|
assert.Equal(t, job.RunID, apiJob.RunID)
|
||||||
|
assert.Equal(t, job.Status, apiJob.Status)
|
||||||
|
assert.Equal(t, job.Conclusion, apiJob.Conclusion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, foundRun, "Expected to find run with ID %d", runID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyWorkflowRunCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status, event, branch, actor, headSHA string) {
|
||||||
|
filter := url.Values{}
|
||||||
|
if conclusion != "" {
|
||||||
|
filter.Add("status", conclusion)
|
||||||
|
}
|
||||||
|
if status != "" {
|
||||||
|
filter.Add("status", status)
|
||||||
|
}
|
||||||
|
if event != "" {
|
||||||
|
filter.Set("event", event)
|
||||||
|
}
|
||||||
|
if branch != "" {
|
||||||
|
filter.Set("branch", branch)
|
||||||
|
}
|
||||||
|
if actor != "" {
|
||||||
|
filter.Set("actor", actor)
|
||||||
|
}
|
||||||
|
if headSHA != "" {
|
||||||
|
filter.Set("head_sha", headSHA)
|
||||||
|
}
|
||||||
|
req := NewRequest(t, "GET", runAPIURL+"?"+filter.Encode()).AddTokenAuth(token)
|
||||||
|
runResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runList := api.ActionWorkflowRunsResponse{}
|
||||||
|
DecodeJSON(t, runResp, &runList)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, run := range runList.Entries {
|
||||||
|
if conclusion != "" {
|
||||||
|
assert.Equal(t, conclusion, run.Conclusion)
|
||||||
|
}
|
||||||
|
if status != "" {
|
||||||
|
assert.Equal(t, status, run.Status)
|
||||||
|
}
|
||||||
|
if event != "" {
|
||||||
|
assert.Equal(t, event, run.Event)
|
||||||
|
}
|
||||||
|
if branch != "" {
|
||||||
|
assert.Equal(t, branch, run.HeadBranch)
|
||||||
|
}
|
||||||
|
if actor != "" {
|
||||||
|
assert.Equal(t, actor, run.Actor.UserName)
|
||||||
|
}
|
||||||
|
found = found || run.ID == id
|
||||||
|
}
|
||||||
|
assert.True(t, found, "Expected to find run with ID %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyWorkflowJobCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status string) {
|
||||||
|
filter := conclusion
|
||||||
|
if filter == "" {
|
||||||
|
filter = status
|
||||||
|
}
|
||||||
|
if filter == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := NewRequest(t, "GET", runAPIURL+"?status="+filter).AddTokenAuth(token)
|
||||||
|
jobListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
jobList := api.ActionWorkflowJobsResponse{}
|
||||||
|
DecodeJSON(t, jobListResp, &jobList)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, job := range jobList.Entries {
|
||||||
|
if conclusion != "" {
|
||||||
|
assert.Equal(t, conclusion, job.Conclusion)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, status, job.Status)
|
||||||
|
}
|
||||||
|
found = found || job.ID == id
|
||||||
|
}
|
||||||
|
assert.True(t, found, "Expected to find job with ID %d", id)
|
||||||
|
}
|
Loading…
Reference in New Issue