Skip to content

Commit 41bda0a

Browse files
committed
Add Actions API endpoints for workflow run and job management
This commit adds comprehensive REST API endpoints for GitHub Actions workflow run and job operations, addressing requirements for: - Workflow run operations: approve, cancel, rerun - Job-specific operations: rerun individual jobs - Log streaming: both archive download and incremental streaming - Enhanced job log access with proper error handling Key changes: - Add 6 new API endpoints in actions_run.go using consistent run ID approach - Add comprehensive integration tests with proper error scenarios - Update Swagger documentation for all new endpoints - Maintain backward compatibility with existing API patterns All endpoints follow established authentication and authorization patterns, with proper error handling and response formatting.
1 parent 9356be5 commit 41bda0a

File tree

4 files changed

+488
-64
lines changed

4 files changed

+488
-64
lines changed

routers/api/v1/repo/actions_run.go

Lines changed: 75 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
actions_service "code.gitea.io/gitea/services/actions"
2121
"code.gitea.io/gitea/services/context"
2222
notify_service "code.gitea.io/gitea/services/notify"
23+
2324
"xorm.io/builder"
2425
)
2526

@@ -93,7 +94,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
9394
// required: true
9495
// - name: run
9596
// in: path
96-
// description: run number or "latest"
97+
// description: run ID or "latest"
9798
// type: string
9899
// required: true
99100
// responses:
@@ -111,13 +112,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
111112
return
112113
}
113114

114-
runIndex := getRunIndex(ctx)
115-
if runIndex == 0 {
116-
ctx.APIError(404, "Invalid run number")
117-
return
118-
}
119-
120-
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
115+
_, run, err := getRunID(ctx)
121116
if err != nil {
122117
if errors.Is(err, util.ErrNotExist) {
123118
ctx.APIError(404, "Run not found")
@@ -184,7 +179,7 @@ func CancelWorkflowRun(ctx *context.APIContext) {
184179
// required: true
185180
// - name: run
186181
// in: path
187-
// description: run number or "latest"
182+
// description: run ID or "latest"
188183
// type: string
189184
// required: true
190185
// responses:
@@ -202,13 +197,17 @@ func CancelWorkflowRun(ctx *context.APIContext) {
202197
return
203198
}
204199

205-
runIndex := getRunIndex(ctx)
206-
if runIndex == 0 {
207-
ctx.APIError(404, "Invalid run number")
200+
runID, _, err := getRunID(ctx)
201+
if err != nil {
202+
if errors.Is(err, util.ErrNotExist) {
203+
ctx.APIError(404, "Run not found")
204+
} else {
205+
ctx.APIErrorInternal(err)
206+
}
208207
return
209208
}
210209

211-
jobs, err := getRunJobsByIndex(ctx, runIndex)
210+
jobs, err := getRunJobsByRunID(ctx, runID)
212211
if err != nil {
213212
ctx.APIErrorInternal(err)
214213
return
@@ -281,7 +280,7 @@ func ApproveWorkflowRun(ctx *context.APIContext) {
281280
// required: true
282281
// - name: run
283282
// in: path
284-
// description: run number or "latest"
283+
// description: run ID or "latest"
285284
// type: string
286285
// required: true
287286
// responses:
@@ -299,13 +298,17 @@ func ApproveWorkflowRun(ctx *context.APIContext) {
299298
return
300299
}
301300

302-
runIndex := getRunIndex(ctx)
303-
if runIndex == 0 {
304-
ctx.APIError(404, "Invalid run number")
301+
runID, _, err := getRunID(ctx)
302+
if err != nil {
303+
if errors.Is(err, util.ErrNotExist) {
304+
ctx.APIError(404, "Run not found")
305+
} else {
306+
ctx.APIErrorInternal(err)
307+
}
305308
return
306309
}
307310

308-
current, jobs, err := getRunJobsAndCurrent(ctx, runIndex, -1)
311+
current, jobs, err := getRunJobsAndCurrent(ctx, runID, -1)
309312
if err != nil {
310313
ctx.APIErrorInternal(err)
311314
return
@@ -375,7 +378,7 @@ func RerunWorkflowJob(ctx *context.APIContext) {
375378
// required: true
376379
// - name: run
377380
// in: path
378-
// description: run number or "latest"
381+
// description: run ID or "latest"
379382
// type: string
380383
// required: true
381384
// - name: job_id
@@ -398,16 +401,20 @@ func RerunWorkflowJob(ctx *context.APIContext) {
398401
return
399402
}
400403

401-
runIndex := getRunIndex(ctx)
402-
if runIndex == 0 {
403-
ctx.APIError(404, "Invalid run number")
404+
runID, _, err := getRunID(ctx)
405+
if err != nil {
406+
if errors.Is(err, util.ErrNotExist) {
407+
ctx.APIError(404, "Run not found")
408+
} else {
409+
ctx.APIErrorInternal(err)
410+
}
404411
return
405412
}
406413

407414
jobID := ctx.PathParamInt64("job_id")
408415

409416
// Get all jobs for the run to handle dependencies
410-
allJobs, err := getRunJobsByIndex(ctx, runIndex)
417+
allJobs, err := getRunJobsByRunID(ctx, runID)
411418
if err != nil {
412419
ctx.APIErrorInternal(err)
413420
return
@@ -427,12 +434,8 @@ func RerunWorkflowJob(ctx *context.APIContext) {
427434
return
428435
}
429436

430-
// Get run and check if workflow is disabled
431-
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
432-
if err != nil {
433-
ctx.APIErrorInternal(err)
434-
return
435-
}
437+
// Get run from the job and check if workflow is disabled
438+
run := allJobs[0].Run
436439

437440
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
438441
cfg := cfgUnit.ActionsConfig()
@@ -468,21 +471,39 @@ func RerunWorkflowJob(ctx *context.APIContext) {
468471
}
469472

470473
// Helper functions
471-
func getRunIndex(ctx *context.APIContext) int64 {
472-
// if run param is "latest", get the latest run index
474+
func getRunID(ctx *context.APIContext) (int64, *actions_model.ActionRun, error) {
475+
// if run param is "latest", get the latest run
473476
if ctx.PathParam("run") == "latest" {
474-
if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil {
475-
return run.Index
477+
run, err := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID)
478+
if err != nil {
479+
return 0, nil, err
480+
}
481+
if run == nil {
482+
return 0, nil, util.ErrNotExist
476483
}
484+
return run.ID, run, nil
485+
}
486+
487+
// Otherwise get run by ID
488+
runID := ctx.PathParamInt64("run")
489+
run, has, err := db.GetByID[actions_model.ActionRun](ctx, runID)
490+
if err != nil {
491+
return 0, nil, err
477492
}
478-
return ctx.PathParamInt64("run")
493+
if !has || run.RepoID != ctx.Repo.Repository.ID {
494+
return 0, nil, util.ErrNotExist
495+
}
496+
return runID, run, nil
479497
}
480498

481-
func getRunJobsByIndex(ctx *context.APIContext, runIndex int64) ([]*actions_model.ActionRunJob, error) {
482-
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
499+
func getRunJobsByRunID(ctx *context.APIContext, runID int64) ([]*actions_model.ActionRunJob, error) {
500+
run, has, err := db.GetByID[actions_model.ActionRun](ctx, runID)
483501
if err != nil {
484502
return nil, err
485503
}
504+
if !has || run.RepoID != ctx.Repo.Repository.ID {
505+
return nil, util.ErrNotExist
506+
}
486507
run.Repo = ctx.Repo.Repository
487508
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
488509
if err != nil {
@@ -494,8 +515,8 @@ func getRunJobsByIndex(ctx *context.APIContext, runIndex int64) ([]*actions_mode
494515
return jobs, nil
495516
}
496517

497-
func getRunJobsAndCurrent(ctx *context.APIContext, runIndex, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob, error) {
498-
jobs, err := getRunJobsByIndex(ctx, runIndex)
518+
func getRunJobsAndCurrent(ctx *context.APIContext, runID, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob, error) {
519+
jobs, err := getRunJobsByRunID(ctx, runID)
499520
if err != nil {
500521
return nil, nil, err
501522
}
@@ -588,7 +609,7 @@ func GetWorkflowRunLogs(ctx *context.APIContext) {
588609
// required: true
589610
// - name: run
590611
// in: path
591-
// description: run number or "latest"
612+
// description: run ID or "latest"
592613
// type: string
593614
// required: true
594615
// responses:
@@ -597,13 +618,7 @@ func GetWorkflowRunLogs(ctx *context.APIContext) {
597618
// "404":
598619
// "$ref": "#/responses/notFound"
599620

600-
runIndex := getRunIndex(ctx)
601-
if runIndex == 0 {
602-
ctx.APIError(404, "Invalid run number")
603-
return
604-
}
605-
606-
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
621+
_, run, err := getRunID(ctx)
607622
if err != nil {
608623
if errors.Is(err, util.ErrNotExist) {
609624
ctx.APIError(404, "Run not found")
@@ -641,7 +656,7 @@ func GetWorkflowJobLogs(ctx *context.APIContext) {
641656
// required: true
642657
// - name: run
643658
// in: path
644-
// description: run number or "latest"
659+
// description: run ID or "latest"
645660
// type: string
646661
// required: true
647662
// - name: job_id
@@ -655,15 +670,7 @@ func GetWorkflowJobLogs(ctx *context.APIContext) {
655670
// "404":
656671
// "$ref": "#/responses/notFound"
657672

658-
runIndex := getRunIndex(ctx)
659-
if runIndex == 0 {
660-
ctx.APIError(404, "Invalid run number")
661-
return
662-
}
663-
664-
jobID := ctx.PathParamInt64("job_id")
665-
666-
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
673+
runID, _, err := getRunID(ctx)
667674
if err != nil {
668675
if errors.Is(err, util.ErrNotExist) {
669676
ctx.APIError(404, "Run not found")
@@ -673,7 +680,9 @@ func GetWorkflowJobLogs(ctx *context.APIContext) {
673680
return
674681
}
675682

676-
if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, run.ID, jobID); err != nil {
683+
jobID := ctx.PathParamInt64("job_id")
684+
685+
if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, runID, jobID); err != nil {
677686
if errors.Is(err, util.ErrNotExist) {
678687
ctx.APIError(404, "Job logs not found")
679688
} else {
@@ -703,7 +712,7 @@ func GetWorkflowRunLogsStream(ctx *context.APIContext) {
703712
// required: true
704713
// - name: run
705714
// in: path
706-
// description: run number or "latest"
715+
// description: run ID or "latest"
707716
// type: string
708717
// required: true
709718
// - name: job
@@ -758,9 +767,13 @@ func GetWorkflowRunLogsStream(ctx *context.APIContext) {
758767
// "404":
759768
// "$ref": "#/responses/notFound"
760769

761-
runIndex := getRunIndex(ctx)
762-
if runIndex == 0 {
763-
ctx.APIError(404, "Invalid run number")
770+
runID, _, err := getRunID(ctx)
771+
if err != nil {
772+
if errors.Is(err, util.ErrNotExist) {
773+
ctx.APIError(404, "Run not found")
774+
} else {
775+
ctx.APIErrorInternal(err)
776+
}
764777
return
765778
}
766779

@@ -776,7 +789,7 @@ func GetWorkflowRunLogsStream(ctx *context.APIContext) {
776789
req = LogRequest{LogCursors: []LogCursor{}}
777790
}
778791

779-
current, _, err := getRunJobsAndCurrent(ctx, runIndex, jobIndex)
792+
current, _, err := getRunJobsAndCurrent(ctx, runID, jobIndex)
780793
if err != nil {
781794
if errors.Is(err, util.ErrNotExist) {
782795
ctx.APIError(404, "Run or job not found")

routers/api/v1/swagger/action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,5 @@ type swaggerWorkflowRunRerunRequest struct {
5858
// swagger:model WorkflowRunLogsRequest
5959
type swaggerWorkflowRunLogsRequest struct {
6060
// Log cursors for incremental log streaming
61-
LogCursors []map[string]interface{} `json:"logCursors"`
61+
LogCursors []map[string]any `json:"logCursors"`
6262
}

0 commit comments

Comments
 (0)