Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 17 additions & 32 deletions models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,52 +422,37 @@ func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool {
return false
}

// MustGetUnit always returns a RepoUnit object
// MustGetUnit always returns a RepoUnit object even if the unit doesn't exist (not enabled)
func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit {
ru, err := repo.GetUnit(ctx, tp)
if err == nil {
return ru
}

if !errors.Is(err, util.ErrNotExist) {
setting.PanicInDevOrTesting("Failed to get unit %v for repository %d: %v", tp, repo.ID, err)
}
ru = &RepoUnit{RepoID: repo.ID, Type: tp}
switch tp {
case unit.TypeExternalWiki:
return &RepoUnit{
Type: tp,
Config: new(ExternalWikiConfig),
}
ru.Config = new(ExternalWikiConfig)
case unit.TypeExternalTracker:
return &RepoUnit{
Type: tp,
Config: new(ExternalTrackerConfig),
}
ru.Config = new(ExternalTrackerConfig)
case unit.TypePullRequests:
return &RepoUnit{
Type: tp,
Config: new(PullRequestsConfig),
}
ru.Config = new(PullRequestsConfig)
case unit.TypeIssues:
return &RepoUnit{
Type: tp,
Config: new(IssuesConfig),
}
ru.Config = new(IssuesConfig)
case unit.TypeActions:
return &RepoUnit{
Type: tp,
Config: new(ActionsConfig),
}
ru.Config = new(ActionsConfig)
case unit.TypeProjects:
cfg := new(ProjectsConfig)
cfg.ProjectsMode = ProjectsModeNone
return &RepoUnit{
Type: tp,
Config: cfg,
}
ru.Config = new(ProjectsConfig)
default: // other units don't have config
}

return &RepoUnit{
Type: tp,
Config: new(UnitConfig),
if ru.Config != nil {
if err = ru.Config.FromDB(nil); err != nil {
setting.PanicInDevOrTesting("Failed to load default config for unit %v of repository %d: %v", tp, repo.ID, err)
}
}
return ru
}

// GetUnit returns a RepoUnit object
Expand Down
2 changes: 2 additions & 0 deletions models/repo/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ type ProjectsConfig struct {

// FromDB fills up a ProjectsConfig from serialized format.
func (cfg *ProjectsConfig) FromDB(bs []byte) error {
// TODO: remove GetProjectsMode, only use ProjectsMode
cfg.ProjectsMode = ProjectsModeAll
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
}

Expand Down
2 changes: 2 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,9 @@ func Routes() *web.Router {
m.Group("/{run}", func() {
m.Get("", repo.GetWorkflowRun)
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
m.Post("/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowRun)
m.Get("/jobs", repo.ListWorkflowRunJobs)
m.Post("/jobs/{job_id}/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowJob)
m.Get("/artifacts", repo.GetArtifactsOfRun)
})
})
Expand Down
178 changes: 159 additions & 19 deletions routers/api/v1/repo/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"fmt"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -1103,6 +1104,33 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}

func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionRun {
runID := ctx.PathParamInt64("run")
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
if errors.Is(err, util.ErrNotExist) {
ctx.APIErrorNotFound(err)
return nil
} else if err != nil {
ctx.APIErrorInternal(err)
return nil
}
return run
}

func getCurrentRepoActionRunJobsByID(ctx *context.APIContext) (*actions_model.ActionRun, actions_model.ActionJobList) {
run := getCurrentRepoActionRunByID(ctx)
if ctx.Written() {
return nil, nil
}

jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
}
return run, jobs
}

// GetWorkflowRun Gets a specific workflow run.
func GetWorkflowRun(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
Expand Down Expand Up @@ -1134,24 +1162,144 @@ func GetWorkflowRun(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"

runID := ctx.PathParamInt64("run")
job, has, err := db.GetByID[actions_model.ActionRun](ctx, runID)
run := getCurrentRepoActionRunByID(ctx)
if ctx.Written() {
return
}

convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convertedRun)
}

if !has || job.RepoID != ctx.Repo.Repository.ID {
ctx.APIErrorNotFound(util.ErrNotExist)
// RerunWorkflowRun Reruns an entire workflow run.
func RerunWorkflowRun(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/rerun repository rerunWorkflowRun
// ---
// summary: Reruns an entire workflow run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// 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: integer
// required: true
// responses:
// "201":
// "$ref": "#/responses/WorkflowRun"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

run, jobs := getCurrentRepoActionRunJobsByID(ctx)
if ctx.Written() {
return
}

convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, nil); err != nil {
handleWorkflowRerunError(ctx, err)
return
}

convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convertedRun)
ctx.JSON(http.StatusCreated, convertedRun)
}

// RerunWorkflowJob Reruns a specific workflow job in a run.
func RerunWorkflowJob(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/jobs/{job_id}/rerun repository rerunWorkflowJob
// ---
// summary: Reruns a specific workflow job in a run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// 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: integer
// required: true
// - name: job_id
// in: path
// description: id of the job
// type: integer
// required: true
// responses:
// "201":
// "$ref": "#/responses/WorkflowJob"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"

run, jobs := getCurrentRepoActionRunJobsByID(ctx)
if ctx.Written() {
return
}

jobID := ctx.PathParamInt64("job_id")
jobIdx := slices.IndexFunc(jobs, func(job *actions_model.ActionRunJob) bool { return job.ID == jobID })
if jobIdx == -1 {
ctx.APIErrorNotFound(util.NewNotExistErrorf("workflow job with id %d", jobID))
return
}

targetJob := jobs[jobIdx]
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil {
handleWorkflowRerunError(ctx, err)
return
}

convertedJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, targetJob)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, convertedJob)
}

func handleWorkflowRerunError(ctx *context.APIContext, err error) {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
return
}
ctx.APIErrorInternal(err)
}

// ListWorkflowRunJobs Lists all jobs for a workflow run.
Expand Down Expand Up @@ -1198,9 +1346,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"

repoID := ctx.Repo.Repository.ID

runID := ctx.PathParamInt64("run")
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")

// Avoid the list all jobs functionality for this api route to be used with a runID == 0.
if runID <= 0 {
Expand Down Expand Up @@ -1300,10 +1446,8 @@ func GetArtifactsOfRun(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"

repoID := ctx.Repo.Repository.ID
artifactName := ctx.Req.URL.Query().Get("name")

runID := ctx.PathParamInt64("run")
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")

artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
RepoID: repoID,
Expand Down Expand Up @@ -1364,15 +1508,11 @@ func DeleteActionRun(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"

runID := ctx.PathParamInt64("run")
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
if errors.Is(err, util.ErrNotExist) {
ctx.APIErrorNotFound(err)
return
} else if err != nil {
ctx.APIErrorInternal(err)
run := getCurrentRepoActionRunByID(ctx)
if ctx.Written() {
return
}

if !run.Status.IsDone() {
ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
return
Expand Down
Loading