mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-21 03:48:30 +02:00
Merge branch 'feat/add-oauth-management-to-api-for-iac-tooling' of github.com:uvulpos/gitea into feat/add-oauth-management-to-api-for-iac-tooling
This commit is contained in:
commit
c3fc57b42a
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/models/shared/types"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -123,8 +124,15 @@ func (r *ActionRunner) IsOnline() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Editable checks if the runner is editable by the user
|
||||
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
|
||||
// EditableInContext checks if the runner is editable by the "context" owner/repo
|
||||
// ownerID == 0 and repoID == 0 means "admin" context, any runner including global runners could be edited
|
||||
// ownerID == 0 and repoID != 0 means "repo" context, any runner belonging to the given repo could be edited
|
||||
// ownerID != 0 and repoID == 0 means "owner(org/user)" context, any runner belonging to the given user/org could be edited
|
||||
// ownerID != 0 and repoID != 0 means "owner" OR "repo" context, legacy behavior, but we should forbid using it
|
||||
func (r *ActionRunner) EditableInContext(ownerID, repoID int64) bool {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
if ownerID == 0 && repoID == 0 {
|
||||
return true
|
||||
}
|
||||
@ -168,6 +176,12 @@ func init() {
|
||||
db.RegisterModel(&ActionRunner{})
|
||||
}
|
||||
|
||||
// FindRunnerOptions
|
||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||
// repoID != 0 and WithAvailable == false means any runner for the given repo
|
||||
// repoID != 0 and WithAvailable == true means any runner for the given repo, parent user/org, and global runners
|
||||
// ownerID != 0 and repoID == 0 and WithAvailable == false means any runner for the given user/org
|
||||
// ownerID != 0 and repoID == 0 and WithAvailable == true means any runner for the given user/org and global runners
|
||||
type FindRunnerOptions struct {
|
||||
db.ListOptions
|
||||
IDs []int64
|
||||
|
40
models/fixtures/action_runner.yml
Normal file
40
models/fixtures/action_runner.yml
Normal file
@ -0,0 +1,40 @@
|
||||
-
|
||||
id: 34346
|
||||
name: runner_to_be_deleted-user
|
||||
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
|
||||
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
|
||||
version: "1.0.0"
|
||||
owner_id: 1
|
||||
repo_id: 0
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||
-
|
||||
id: 34347
|
||||
name: runner_to_be_deleted-org
|
||||
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
|
||||
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
|
||||
version: "1.0.0"
|
||||
owner_id: 3
|
||||
repo_id: 0
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||
-
|
||||
id: 34348
|
||||
name: runner_to_be_deleted-repo1
|
||||
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||
version: "1.0.0"
|
||||
owner_id: 0
|
||||
repo_id: 1
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||
-
|
||||
id: 34349
|
||||
name: runner_to_be_deleted
|
||||
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
|
||||
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
|
||||
version: "1.0.0"
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||
default:
|
||||
e.Desc("package_version.created_unix")
|
||||
}
|
||||
|
||||
// Sort by id for stable order with duplicates in the other field
|
||||
e.Asc("package_version.id")
|
||||
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||
}
|
||||
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
|
@ -133,3 +133,26 @@ type ActionWorkflowJob struct {
|
||||
// swagger:strfmt date-time
|
||||
CompletedAt time.Time `json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
// ActionRunnerLabel represents a Runner Label
|
||||
type ActionRunnerLabel struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ActionRunner represents a Runner
|
||||
type ActionRunner struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Busy bool `json:"busy"`
|
||||
Ephemeral bool `json:"ephemeral"`
|
||||
Labels []*ActionRunnerLabel `json:"labels"`
|
||||
}
|
||||
|
||||
// ActionRunnersResponse returns Runners
|
||||
type ActionRunnersResponse struct {
|
||||
Entries []*ActionRunner `json:"runners"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
||||
// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
|
||||
func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
|
||||
multipartFile, _, err := ctx.Req.FormFile(formKey)
|
||||
if err != nil && !errors.Is(err, http.ErrMissingFile) {
|
||||
return nil, err
|
||||
}
|
||||
if multipartFile != nil {
|
||||
return multipartFile, nil
|
||||
}
|
||||
|
||||
content := ctx.Req.FormValue(formKey)
|
||||
if content == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return io.NopCloser(strings.NewReader(content)), nil
|
||||
}
|
||||
|
||||
// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
||||
func UploadPackageFile(ctx *context.Context) {
|
||||
packageScope := ctx.PathParam("scope")
|
||||
packageName := ctx.PathParam("name")
|
||||
@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
|
||||
|
||||
packageVersion := v.Core().String()
|
||||
|
||||
file, _, err := ctx.Req.FormFile("source-archive")
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
file, err := formFileOptionalReadCloser(ctx, "source-archive")
|
||||
if file == nil || err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
|
||||
}
|
||||
defer buf.Close()
|
||||
|
||||
var mr io.Reader
|
||||
metadata := ctx.Req.FormValue("metadata")
|
||||
if metadata != "" {
|
||||
mr = strings.NewReader(metadata)
|
||||
mr, err := formFileOptionalReadCloser(ctx, "metadata")
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
|
||||
return
|
||||
}
|
||||
if mr != nil {
|
||||
defer mr.Close()
|
||||
}
|
||||
|
||||
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1697,6 +1705,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"))
|
||||
}
|
||||
|
@ -181,6 +181,7 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
|
||||
|
||||
// GetPullDiffStats get Pull Requests diff stats
|
||||
func GetPullDiffStats(ctx *context.Context) {
|
||||
// FIXME: this getPullInfo seems to be a duplicate call with other route handlers
|
||||
issue, ok := getPullInfo(ctx)
|
||||
if !ok {
|
||||
return
|
||||
@ -188,21 +189,19 @@ func GetPullDiffStats(ctx *context.Context) {
|
||||
pull := issue.PullRequest
|
||||
|
||||
mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue)
|
||||
|
||||
if mergeBaseCommitID == "" {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
return // no merge base, do nothing, do not stop the route handler, see below
|
||||
}
|
||||
|
||||
// do not report 500 server error to end users if error occurs, otherwise a PR missing ref won't be able to view.
|
||||
headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRefCommitID", err)
|
||||
log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||
return
|
||||
}
|
||||
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDiffShortStat", err)
|
||||
log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ func RunnersEdit(ctx *context.Context) {
|
||||
ctx.ServerError("LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
if !runner.Editable(ownerID, repoID) {
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
err = errors.New("no permission to edit this runner")
|
||||
ctx.NotFound(err)
|
||||
return
|
||||
@ -250,7 +250,7 @@ func RunnersEditPost(ctx *context.Context) {
|
||||
ctx.ServerError("RunnerDetailsEditPost.GetRunnerByID", err)
|
||||
return
|
||||
}
|
||||
if !runner.Editable(ownerID, repoID) {
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to edit this runner"))
|
||||
return
|
||||
}
|
||||
@ -304,7 +304,7 @@ func RunnerDeletePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !runner.Editable(rCtx.OwnerID, rCtx.RepoID) {
|
||||
if !runner.EditableInContext(rCtx.OwnerID, rCtx.RepoID) {
|
||||
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to delete this runner"))
|
||||
return
|
||||
}
|
||||
|
@ -25,4 +25,12 @@ type API interface {
|
||||
UpdateVariable(*context.APIContext)
|
||||
// GetRegistrationToken get registration token
|
||||
GetRegistrationToken(*context.APIContext)
|
||||
// CreateRegistrationToken get registration token
|
||||
CreateRegistrationToken(*context.APIContext)
|
||||
// ListRunners list runners
|
||||
ListRunners(*context.APIContext)
|
||||
// GetRunner get a runner
|
||||
GetRunner(*context.APIContext)
|
||||
// DeleteRunner delete runner
|
||||
DeleteRunner(*context.APIContext)
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
)
|
||||
|
||||
// ToEmail convert models.EmailAddress to api.Email
|
||||
@ -252,6 +254,30 @@ func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArti
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ToActionRunner(ctx context.Context, runner *actions_model.ActionRunner) *api.ActionRunner {
|
||||
status := runner.Status()
|
||||
apiStatus := "offline"
|
||||
if runner.IsOnline() {
|
||||
apiStatus = "online"
|
||||
}
|
||||
labels := make([]*api.ActionRunnerLabel, len(runner.AgentLabels))
|
||||
for i, label := range runner.AgentLabels {
|
||||
labels[i] = &api.ActionRunnerLabel{
|
||||
ID: int64(i),
|
||||
Name: label,
|
||||
Type: "custom",
|
||||
}
|
||||
}
|
||||
return &api.ActionRunner{
|
||||
ID: runner.ID,
|
||||
Name: runner.Name,
|
||||
Status: apiStatus,
|
||||
Busy: status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE,
|
||||
Ephemeral: runner.Ephemeral,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
||||
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
||||
verif := asymkey_service.ParseCommitWithSignature(ctx, c)
|
||||
|
@ -1,3 +1,3 @@
|
||||
<a class="ui label basic tiny button{{if .IsRelease}} primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
|
||||
<a class="ui basic label tw-p-1 {{if .IsRelease}}primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
|
||||
{{svg "octicon-tag"}} {{.TagName}}
|
||||
</a>
|
||||
|
582
templates/swagger/v1_json.tmpl
generated
582
templates/swagger/v1_json.tmpl
generated
@ -75,6 +75,108 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/actions/runners": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Get all runners",
|
||||
"operationId": "getAdminRunners",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunnersResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/actions/runners/registration-token": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Get an global actions runner registration token",
|
||||
"operationId": "adminCreateRunnerRegistrationToken",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/actions/runners/{runner_id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Get an global runner",
|
||||
"operationId": "getAdminRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Delete an global runner",
|
||||
"operationId": "deleteAdminRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "runner has been deleted"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/cron": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -1697,6 +1799,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/runners": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Get org-level runners",
|
||||
"operationId": "getOrgRunners",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunnersResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/runners/registration-token": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -1721,6 +1855,106 @@
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Get an organization's actions runner registration token",
|
||||
"operationId": "orgCreateRunnerRegistrationToken",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/runners/{runner_id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Get an org-level runner",
|
||||
"operationId": "getOrgRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Delete an org-level runner",
|
||||
"operationId": "deleteOrgRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "runner has been deleted"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/secrets": {
|
||||
@ -4331,6 +4565,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/runners": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get repo-level runners",
|
||||
"operationId": "getRepoRunners",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunnersResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/runners/registration-token": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -4362,6 +4635,127 @@
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get a repository's actions runner registration token",
|
||||
"operationId": "repoCreateRunnerRegistrationToken",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/runners/{runner_id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get an repo-level runner",
|
||||
"operationId": "getRepoRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Delete an repo-level runner",
|
||||
"operationId": "deleteRepoRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "runner has been deleted"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
|
||||
@ -4559,7 +4953,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "delete one secret of the organization"
|
||||
"description": "delete one secret of the repository"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
@ -16869,6 +17263,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/runners": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Get user-level runners",
|
||||
"operationId": "getUserRunners",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunnersResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/runners/registration-token": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -16884,6 +17301,83 @@
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Get an user's actions runner registration token",
|
||||
"operationId": "userCreateRunnerRegistrationToken",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/RegistrationToken"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/runners/{runner_id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Get an user-level runner",
|
||||
"operationId": "getUserRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete an user-level runner",
|
||||
"operationId": "deleteUserRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id of the runner",
|
||||
"name": "runner_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "runner has been deleted"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/secrets/{secretname}": {
|
||||
@ -19377,6 +19871,80 @@
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"ActionRunner": {
|
||||
"description": "ActionRunner represents a Runner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"busy": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "Busy"
|
||||
},
|
||||
"ephemeral": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "Ephemeral"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "ID"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ActionRunnerLabel"
|
||||
},
|
||||
"x-go-name": "Labels"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"x-go-name": "Status"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"ActionRunnerLabel": {
|
||||
"description": "ActionRunnerLabel represents a Runner Label",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"x-go-name": "Type"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"ActionRunnersResponse": {
|
||||
"description": "ActionRunnersResponse returns Runners",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"runners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
},
|
||||
"x-go-name": "Entries"
|
||||
},
|
||||
"total_count": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "TotalCount"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"ActionTask": {
|
||||
"description": "ActionTask represents a ActionTask",
|
||||
"type": "object",
|
||||
@ -27409,6 +27977,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Runner": {
|
||||
"description": "Runner",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ActionRunner"
|
||||
}
|
||||
},
|
||||
"RunnerList": {
|
||||
"description": "RunnerList",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ActionRunnersResponse"
|
||||
}
|
||||
},
|
||||
"SearchResults": {
|
||||
"description": "SearchResults",
|
||||
"schema": {
|
||||
|
332
tests/integration/api_actions_runner_test.go
Normal file
332
tests/integration/api_actions_runner_test.go
Normal file
@ -0,0 +1,332 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIActionsRunner(t *testing.T) {
|
||||
t.Run("AdminRunner", testActionsRunnerAdmin)
|
||||
t.Run("UserRunner", testActionsRunnerUser)
|
||||
t.Run("OwnerRunner", testActionsRunnerOwner)
|
||||
t.Run("RepoRunner", testActionsRunnerRepo)
|
||||
}
|
||||
|
||||
func testActionsRunnerAdmin(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
adminUsername := "user1"
|
||||
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
||||
req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token)
|
||||
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||
var registrationToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||
assert.NotEmpty(t, registrationToken.Token)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token)
|
||||
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerList := api.ActionRunnersResponse{}
|
||||
DecodeJSON(t, runnerListResp, &runnerList)
|
||||
|
||||
assert.Len(t, runnerList.Entries, 4)
|
||||
|
||||
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
|
||||
require.NotEqual(t, -1, idx)
|
||||
expectedRunner := runnerList.Entries[idx]
|
||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
|
||||
assert.False(t, expectedRunner.Ephemeral)
|
||||
assert.Len(t, expectedRunner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
||||
|
||||
// Verify all returned runners can be requested and deleted
|
||||
for _, runnerEntry := range runnerList.Entries {
|
||||
// Verify get the runner by id
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, runnerEntry.Name, runner.Name)
|
||||
assert.Equal(t, runnerEntry.ID, runner.ID)
|
||||
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
|
||||
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify runner deletion
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func testActionsRunnerUser(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
userUsername := "user1"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser)
|
||||
req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token)
|
||||
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||
var registrationToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||
assert.NotEmpty(t, registrationToken.Token)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token)
|
||||
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerList := api.ActionRunnersResponse{}
|
||||
DecodeJSON(t, runnerListResp, &runnerList)
|
||||
|
||||
assert.Len(t, runnerList.Entries, 1)
|
||||
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
|
||||
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
|
||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||
|
||||
// Verify get the runner by id
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
|
||||
assert.Equal(t, int64(34346), runner.ID)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify runner deletion
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func testActionsRunnerOwner(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("GetRunner", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
// Verify get the runner by id with read scope
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||
assert.Equal(t, int64(34347), runner.ID)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
})
|
||||
|
||||
t.Run("Access", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
|
||||
req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token)
|
||||
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||
var registrationToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||
assert.NotEmpty(t, registrationToken.Token)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token)
|
||||
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerList := api.ActionRunnersResponse{}
|
||||
DecodeJSON(t, runnerListResp, &runnerList)
|
||||
|
||||
assert.Len(t, runnerList.Entries, 1)
|
||||
assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name)
|
||||
assert.Equal(t, int64(34347), runnerList.Entries[0].ID)
|
||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||
|
||||
// Verify get the runner by id
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||
assert.Equal(t, int64(34347), runner.ID)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify runner deletion
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
// Verify delete the runner by id is forbidden with read scope
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
// Verify get the runner by id with read scope
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetAdminRunner", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
// Verify get a runner by id of different entity is not found
|
||||
// runner.EditableInContext(ownerID, repoID) false
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeleteAdminRunner", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
|
||||
// Verify delete a runner by id of different entity is not found
|
||||
// runner.EditableInContext(ownerID, repoID) false
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func testActionsRunnerRepo(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("GetRunner", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
// Verify get the runner by id with read scope
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||
assert.Equal(t, int64(34348), runner.ID)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
})
|
||||
|
||||
t.Run("Access", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token)
|
||||
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||
var registrationToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||
assert.NotEmpty(t, registrationToken.Token)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token)
|
||||
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||
runnerList := api.ActionRunnersResponse{}
|
||||
DecodeJSON(t, runnerListResp, &runnerList)
|
||||
|
||||
assert.Len(t, runnerList.Entries, 1)
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
|
||||
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
|
||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||
|
||||
// Verify get the runner by id
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
runner := api.ActionRunner{}
|
||||
DecodeJSON(t, runnerResp, &runner)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||
assert.Equal(t, int64(34348), runner.ID)
|
||||
assert.False(t, runner.Ephemeral)
|
||||
assert.Len(t, runner.Labels, 2)
|
||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||
|
||||
// Verify delete the runner by id
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify runner deletion
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
// Verify delete the runner by id is forbidden with read scope
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||
// Verify get the runner by id with read scope
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("GetAdminRunnerNotFound", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||
// Verify get a runner by id of different entity is not found
|
||||
// runner.EditableInContext(ownerID, repoID) false
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) {
|
||||
userUsername := "user2"
|
||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
|
||||
// Verify delete a runner by id of different entity is not found
|
||||
// runner.EditableInContext(ownerID, repoID) false
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
@ -23,6 +23,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageSwift(t *testing.T) {
|
||||
@ -34,6 +35,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
packageName := "test_package"
|
||||
packageID := packageScope + "." + packageName
|
||||
packageVersion := "1.0.3"
|
||||
packageVersion2 := "1.0.4"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Gitea Test Package"
|
||||
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
||||
@ -183,6 +185,94 @@ func TestPackageSwift(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("UploadMultipart", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) {
|
||||
var body bytes.Buffer
|
||||
mpw := multipart.NewWriter(&body)
|
||||
|
||||
// Read the source archive content
|
||||
sourceContent, err := io.ReadAll(sr)
|
||||
assert.NoError(t, err)
|
||||
mpw.WriteField("source-archive", string(sourceContent))
|
||||
|
||||
if metadata != "" {
|
||||
mpw.WriteField("metadata", metadata)
|
||||
}
|
||||
|
||||
mpw.Close()
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", url, &body).
|
||||
SetHeader("Content-Type", mpw.FormDataContentType()).
|
||||
SetHeader("Accept", swift_router.AcceptJSON).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
createArchive := func(files map[string]string) *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
for filename, content := range files {
|
||||
w, _ := zw.Create(filename)
|
||||
w.Write([]byte(content))
|
||||
}
|
||||
zw.Close()
|
||||
return &buf
|
||||
}
|
||||
|
||||
uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2)
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
|
||||
// Test with metadata as form field
|
||||
uploadPackage(
|
||||
t,
|
||||
uploadURL,
|
||||
http.StatusCreated,
|
||||
createArchive(map[string]string{
|
||||
"Package.swift": contentManifest1,
|
||||
"Package@swift-5.6.swift": contentManifest2,
|
||||
}),
|
||||
`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
|
||||
)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, pvs, 2) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests
|
||||
thisPackageVersion := pvs[0]
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, thisPackageVersion)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Equal(t, packageID, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion2, pd.Version.Version)
|
||||
assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
|
||||
metadata := pd.Metadata.(*swift_module.Metadata)
|
||||
assert.Equal(t, packageDescription, metadata.Description)
|
||||
assert.Len(t, metadata.Manifests, 2)
|
||||
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
|
||||
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
|
||||
assert.Len(t, pd.VersionProperties, 1)
|
||||
assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, thisPackageVersion.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pfs, 1)
|
||||
assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2), pfs[0].Name)
|
||||
assert.True(t, pfs[0].IsLead)
|
||||
|
||||
uploadPackage(
|
||||
t,
|
||||
uploadURL,
|
||||
http.StatusConflict,
|
||||
createArchive(map[string]string{
|
||||
"Package.swift": contentManifest1,
|
||||
}),
|
||||
"",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Download", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
@ -211,7 +301,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
SetHeader("Accept", swift_router.AcceptJSON)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion)
|
||||
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion2)
|
||||
|
||||
assert.Equal(t, "1", resp.Header().Get("Content-Version"))
|
||||
assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
|
||||
@ -221,9 +311,9 @@ func TestPackageSwift(t *testing.T) {
|
||||
var result *swift_router.EnumeratePackageVersionsResponse
|
||||
DecodeJSON(t, resp, &result)
|
||||
|
||||
assert.Len(t, result.Releases, 1)
|
||||
assert.Contains(t, result.Releases, packageVersion)
|
||||
assert.Equal(t, versionURL, result.Releases[packageVersion].URL)
|
||||
assert.Len(t, result.Releases, 2)
|
||||
assert.Contains(t, result.Releases, packageVersion2)
|
||||
assert.Equal(t, versionURL, result.Releases[packageVersion2].URL)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
|
||||
AddBasicAuth(user.Name)
|
||||
|
Loading…
x
Reference in New Issue
Block a user