mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Actions Runner rest api (#33873)
Implements runner apis based on https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-an-organization - Add Post endpoints for registration-token, google/go-github revealed this as problem - We should deprecate Get Endpoints, leaving them for compatibility - Get endpoint of admin has api path /admin/runners/registration-token that feels wrong, /admin/actions/runners/registration-token seems more consistent with user/org/repo api - Get Runner Api - List Runner Api - Delete Runner Api - Tests admin / user / org / repo level endpoints Related to #33750 (implements point 1 and 2) Via needs discovered in #32461, this runner api is needed to allow cleanup of runners that are deallocated without user interaction. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
|
||||
|
||||
shared.GetRegistrationToken(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register global runners
|
||||
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an global actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// ListRunners get all runners
|
||||
func ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/actions/runners admin getAdminRunners
|
||||
// ---
|
||||
// summary: Get all runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an global runner
|
||||
func GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
|
||||
// ---
|
||||
// summary: Get an global runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an global runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
|
||||
// ---
|
||||
// summary: Delete an global runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
@@ -912,7 +912,11 @@ func Routes() *web.Router {
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), reqChecker, act.ListRunners)
|
||||
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||
m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1043,7 +1047,11 @@ func Routes() *web.Router {
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), user.ListRunners)
|
||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1689,6 +1697,12 @@ func Routes() *web.Router {
|
||||
Patch(bind(api.EditHookOption{}), admin.EditHook).
|
||||
Delete(admin.DeleteHook)
|
||||
})
|
||||
m.Group("/actions/runners", func() {
|
||||
m.Get("", admin.ListRunners)
|
||||
m.Post("/registration-token", admin.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", admin.GetRunner)
|
||||
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||
})
|
||||
m.Group("/runners", func() {
|
||||
m.Get("/registration-token", admin.GetRegistrationToken)
|
||||
})
|
||||
|
@@ -190,6 +190,27 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
// CreateRegistrationToken returns the token to register org runners
|
||||
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/actions/runners/registration-token organization orgCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an organization's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// ListVariables list org-level variables
|
||||
func (Action) ListVariables(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||
@@ -470,6 +491,85 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListRunners get org-level runners
|
||||
func (Action) ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runners organization getOrgRunners
|
||||
// ---
|
||||
// summary: Get org-level runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an org-level runner
|
||||
func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runners/{runner_id} organization getOrgRunner
|
||||
// ---
|
||||
// summary: Get an org-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an org-level runner
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
|
||||
// ---
|
||||
// summary: Delete an org-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
|
||||
// Action implements actions_service.API
|
||||
|
@@ -183,7 +183,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: delete one secret of the organization
|
||||
// description: delete one secret of the repository
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -531,6 +531,125 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register repo runners
|
||||
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get a repository's actions runner registration token
|
||||
// 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/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// ListRunners get repo-level runners
|
||||
func (Action) ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runners repository getRepoRunners
|
||||
// ---
|
||||
// summary: Get repo-level runners
|
||||
// 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": "#/definitions/ActionRunnersResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// GetRunner get an repo-level runner
|
||||
func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
|
||||
// ---
|
||||
// summary: Get an repo-level runner
|
||||
// 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: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an repo-level runner
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
||||
// ---
|
||||
// summary: Delete an repo-level runner
|
||||
// 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: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
|
||||
// Action implements actions_service.API
|
||||
|
@@ -8,8 +8,13 @@ import (
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// RegistrationToken is response related to registration token
|
||||
@@ -30,3 +35,84 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
|
||||
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
|
||||
}
|
||||
|
||||
// ListRunners lists runners for api route validated ownerID and repoID
|
||||
// ownerID == 0 and repoID == 0 means all runners including global runners, does not appear in sql where clause
|
||||
// ownerID == 0 and repoID != 0 means all runners for the given repo
|
||||
// ownerID != 0 and repoID == 0 means all runners for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// Access rights are checked at the API route level
|
||||
func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
res := new(api.ActionRunnersResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
res.Entries = make([]*api.ActionRunner, len(runners))
|
||||
for i, runner := range runners {
|
||||
res.Entries[i] = convert.ToActionRunner(ctx, runner)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
||||
// GetRunner get the runner for api route validated ownerID and repoID
|
||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||
// ownerID != 0 and repoID == 0 means any runner for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// Access rights are checked at the API route level
|
||||
func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return
|
||||
}
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
ctx.APIErrorNotFound("No permission to get this runner")
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
||||
}
|
||||
|
||||
// DeleteRunner deletes the runner for api route validated ownerID and repoID
|
||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||
// ownerID != 0 and repoID == 0 means any runner for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// Access rights are checked at the API route level
|
||||
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
ctx.APIErrorNotFound("No permission to delete this runner")
|
||||
return
|
||||
}
|
||||
|
||||
err = actions_model.DeleteRunner(ctx, runner.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
@@ -457,6 +457,20 @@ type swaggerRepoArtifact struct {
|
||||
Body api.ActionArtifact `json:"body"`
|
||||
}
|
||||
|
||||
// RunnerList
|
||||
// swagger:response RunnerList
|
||||
type swaggerRunnerList struct {
|
||||
// in:body
|
||||
Body api.ActionRunnersResponse `json:"body"`
|
||||
}
|
||||
|
||||
// Runner
|
||||
// swagger:response Runner
|
||||
type swaggerRunner struct {
|
||||
// in:body
|
||||
Body api.ActionRunner `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:response Compare
|
||||
type swaggerCompare struct {
|
||||
// in:body
|
||||
|
@@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register user runners
|
||||
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an user's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// ListRunners get user-level runners
|
||||
func ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners user getUserRunners
|
||||
// ---
|
||||
// summary: Get user-level runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an user-level runner
|
||||
func GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
||||
// ---
|
||||
// summary: Get an user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an user-level runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||
// ---
|
||||
// summary: Delete an user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
Reference in New Issue
Block a user