1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Feature: Support workflow event dispatch via API (#33545)

Fix: https://github.com/go-gitea/gitea/issues/31765 (Re-open #32059)

---------

Co-authored-by: Bence Santha <git@santha.eu>
Co-authored-by: Bence Sántha <7604637+bencurio@users.noreply.github.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
This commit is contained in:
wxiaoguang
2025-02-11 03:05:42 +08:00
committed by GitHub
parent 085f273d19
commit 30993e9508
13 changed files with 1748 additions and 148 deletions

View File

@@ -1155,11 +1155,17 @@ func Routes() *web.Router {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
addActionsRoutes(
m,
reqOwner(),
repo.NewAction(),
)
addActionsRoutes(m, reqOwner(), repo.NewAction()) // it adds the routes for secrets/variables and runner management
m.Group("/actions/workflows", func() {
m.Get("", repo.ActionsListRepositoryWorkflows)
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
}, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
m.Group("/{id}", func() {

View File

@@ -6,6 +6,7 @@ package repo
import (
"errors"
"net/http"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
@@ -19,6 +20,8 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
secret_service "code.gitea.io/gitea/services/secrets"
"github.com/nektos/act/pkg/model"
)
// ListActionsSecrets list an repo's actions secrets
@@ -581,3 +584,270 @@ func ListActionTasks(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, &res)
}
func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows repository ActionsListRepositoryWorkflows
// ---
// summary: List repository workflows
// 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 repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/ActionWorkflowList"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
// "500":
// "$ref": "#/responses/error"
workflows, err := actions_service.ListActionWorkflows(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListActionWorkflows", err)
return
}
ctx.JSON(http.StatusOK, &api.ActionWorkflowResponse{Workflows: workflows, TotalCount: int64(len(workflows))})
}
func ActionsGetWorkflow(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id} repository ActionsGetWorkflow
// ---
// summary: Get a workflow
// 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 repo
// type: string
// required: true
// - name: workflow_id
// in: path
// description: id of the workflow
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/ActionWorkflow"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
// "500":
// "$ref": "#/responses/error"
workflowID := ctx.PathParam("workflow_id")
workflow, err := actions_service.GetActionWorkflow(ctx, workflowID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "GetActionWorkflow", err)
} else {
ctx.Error(http.StatusInternalServerError, "GetActionWorkflow", err)
}
return
}
ctx.JSON(http.StatusOK, workflow)
}
func ActionsDisableWorkflow(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
// ---
// summary: Disable a workflow
// 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 repo
// type: string
// required: true
// - name: workflow_id
// in: path
// description: id of the workflow
// type: string
// required: true
// responses:
// "204":
// description: No Content
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
workflowID := ctx.PathParam("workflow_id")
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "DisableActionWorkflow", err)
} else {
ctx.Error(http.StatusInternalServerError, "DisableActionWorkflow", err)
}
return
}
ctx.Status(http.StatusNoContent)
}
func ActionsDispatchWorkflow(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches repository ActionsDispatchWorkflow
// ---
// summary: Create a workflow dispatch event
// 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 repo
// type: string
// required: true
// - name: workflow_id
// in: path
// description: id of the workflow
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateActionWorkflowDispatch"
// responses:
// "204":
// description: No Content
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
workflowID := ctx.PathParam("workflow_id")
opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch)
if opt.Ref == "" {
ctx.Error(http.StatusUnprocessableEntity, "MissingWorkflowParameter", util.NewInvalidArgumentErrorf("ref is required parameter"))
return
}
err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") {
// The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string
// So we have to manually read the `inputs[key]` from the form
for name, config := range workflowDispatch.Inputs {
value := ctx.FormString("inputs["+name+"]", config.Default)
inputs[name] = value
}
} else {
for name, config := range workflowDispatch.Inputs {
value, ok := opt.Inputs[name]
if ok {
inputs[name] = value
} else {
inputs[name] = config.Default
}
}
}
return nil
})
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "DispatchActionWorkflow", err)
} else if errors.Is(err, util.ErrPermissionDenied) {
ctx.Error(http.StatusForbidden, "DispatchActionWorkflow", err)
} else {
ctx.Error(http.StatusInternalServerError, "DispatchActionWorkflow", err)
}
return
}
ctx.Status(http.StatusNoContent)
}
func ActionsEnableWorkflow(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable repository ActionsEnableWorkflow
// ---
// summary: Enable a workflow
// 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 repo
// type: string
// required: true
// - name: workflow_id
// in: path
// description: id of the workflow
// type: string
// required: true
// responses:
// "204":
// description: No Content
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/conflict"
// "422":
// "$ref": "#/responses/validationError"
workflowID := ctx.PathParam("workflow_id")
err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "EnableActionWorkflow", err)
} else {
ctx.Error(http.StatusInternalServerError, "EnableActionWorkflow", err)
}
return
}
ctx.Status(http.StatusNoContent)
}

View File

@@ -32,3 +32,17 @@ type swaggerResponseVariableList struct {
// in:body
Body []api.ActionVariable `json:"body"`
}
// ActionWorkflow
// swagger:response ActionWorkflow
type swaggerResponseActionWorkflow struct {
// in:body
Body api.ActionWorkflow `json:"body"`
}
// ActionWorkflowList
// swagger:response ActionWorkflowList
type swaggerResponseActionWorkflowList struct {
// in:body
Body []api.ActionWorkflow `json:"body"`
}

View File

@@ -211,6 +211,9 @@ type swaggerParameterBodies struct {
// in:body
RenameOrgOption api.RenameOrgOption
// in:body
CreateActionWorkflowDispatch api.CreateActionWorkflowDispatch
// in:body
UpdateVariableOption api.UpdateVariableOption
}