mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-02-03 20:51:07 -05:00
feat: add HTTP API endpoint for runner registration (#10677)
Add an HTTP API endpoint for runner registration. It enables managing the entire runner lifecycle using Forgejo's HTTP API. See https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/78 for background, design considerations, and usage. Example usage: ``` $ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: token 3fc3ef39805b0f811a5d7789cb7b448348d6bfbb" --data '{"name":"api-runner","description":"Lorem ipsum"}' http://localhost:3000/api/v1/user/actions/runners ``` ```json {"id":30,"uuid":"a5e33697-9f58-437d-83c3-551b6c6a6334","token":"cac45fa6726fe4e28f42598773671af28a3be121"} ``` ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10677 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch> Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
This commit is contained in:
parent
28e0af23fa
commit
0837c8d8be
15 changed files with 714 additions and 22 deletions
26
modules/structs/runner.go
Normal file
26
modules/structs/runner.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package structs
|
||||
|
||||
// RegisterRunnerOptions declares the accepted options for registering runners.
|
||||
// swagger:model
|
||||
type RegisterRunnerOptions struct {
|
||||
// Name of the runner to register. The name of the runner does not have to be unique.
|
||||
//
|
||||
// required: true
|
||||
Name string `json:"name" binding:"Required"`
|
||||
|
||||
// Description of the runner to register.
|
||||
//
|
||||
// required: false
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// RegisterRunnerResponse contains the details of the just registered runner.
|
||||
// swagger:model
|
||||
type RegisterRunnerResponse struct {
|
||||
ID int64 `json:"id" binding:"Required"`
|
||||
UUID string `json:"uuid" binding:"Required"`
|
||||
Token string `json:"token" binding:"Required"`
|
||||
}
|
||||
|
|
@ -139,6 +139,33 @@ func GetRunner(ctx *context.APIContext) {
|
|||
shared.GetRunner(ctx, 0, 0, ctx.ParamsInt64("runner_id"))
|
||||
}
|
||||
|
||||
// RegisterRunner registers a new global runner
|
||||
func RegisterRunner(ctx *context.APIContext) {
|
||||
// swagger:operation POST /admin/actions/runners admin registerAdminRunner
|
||||
// ---
|
||||
// summary: Register a new global runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/RegisterRunnerOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/RegisterRunnerResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "401":
|
||||
// "$ref": "#/responses/unauthorized"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.RegisterRunner(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// DeleteRunner removes a particular runner, no matter whether it is a global runner or scoped to an organization, user, or repository
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
|
||||
|
|
|
|||
|
|
@ -856,7 +856,9 @@ func Routes() *web.Route {
|
|||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), reqChecker, act.ListRunners)
|
||||
m.Combo("").
|
||||
Get(reqToken(), reqChecker, act.ListRunners).
|
||||
Post(reqToken(), reqChecker, bind(api.RegisterRunnerOptions{}), act.RegisterRunner)
|
||||
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
||||
|
|
@ -1019,7 +1021,9 @@ func Routes() *web.Route {
|
|||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), user.ListRunners)
|
||||
m.Combo("").
|
||||
Get(reqToken(), user.ListRunners).
|
||||
Post(bind(api.RegisterRunnerOptions{}), user.RegisterRunner)
|
||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||
|
|
@ -1701,7 +1705,9 @@ func Routes() *web.Route {
|
|||
Delete(admin.DeleteHook)
|
||||
})
|
||||
m.Group("/actions/runners", func() {
|
||||
m.Get("", admin.ListRunners)
|
||||
m.Combo("").
|
||||
Get(admin.ListRunners).
|
||||
Post(bind(api.RegisterRunnerOptions{}), admin.RegisterRunner)
|
||||
m.Get("/registration-token", admin.GetRunnerRegistrationToken)
|
||||
m.Get("/{runner_id}", admin.GetRunner)
|
||||
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||
|
|
|
|||
|
|
@ -324,6 +324,38 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
|||
shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.ParamsInt64("runner_id"))
|
||||
}
|
||||
|
||||
// RegisterRunner registers a new organization-level runner
|
||||
func (Action) RegisterRunner(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/actions/runners organization registerOrgRunner
|
||||
// ---
|
||||
// summary: Register a new organization-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/RegisterRunnerOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/RegisterRunnerResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "401":
|
||||
// "$ref": "#/responses/unauthorized"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.RegisterRunner(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// DeleteRunner removes a particular runner that belongs to the organization
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
|
||||
|
|
|
|||
|
|
@ -574,6 +574,43 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
|||
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.ParamsInt64("runner_id"))
|
||||
}
|
||||
|
||||
// RegisterRunner registers a new repository-level runner
|
||||
func (Action) RegisterRunner(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runners repository registerRepoRunner
|
||||
// ---
|
||||
// summary: Register a new repository-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// 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: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/RegisterRunnerOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/RegisterRunnerResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "401":
|
||||
// "$ref": "#/responses/unauthorized"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.RegisterRunner(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// DeleteRunner removes a particular runner that belongs to a repository
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ import (
|
|||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/structs"
|
||||
"forgejo.org/modules/util"
|
||||
"forgejo.org/modules/web"
|
||||
"forgejo.org/routers/api/v1/utils"
|
||||
"forgejo.org/services/context"
|
||||
"forgejo.org/services/convert"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
// RegistrationToken is a string used to register a runner with a server
|
||||
|
|
@ -146,6 +149,33 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
|||
ctx.JSON(http.StatusOK, actionRunner)
|
||||
}
|
||||
|
||||
func RegisterRunner(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "RegisterRunner", fmt.Errorf("ownerID '%d' and repoID '%d' cannot be set simultaneously", ownerID, repoID))
|
||||
return
|
||||
}
|
||||
|
||||
options := web.GetForm(ctx).(*structs.RegisterRunnerOptions)
|
||||
runner := &actions_model.ActionRunner{
|
||||
UUID: gouuid.NewString(),
|
||||
Name: options.Name,
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Description: options.Description,
|
||||
}
|
||||
runner.GenerateToken()
|
||||
if err := actions_model.CreateRunner(ctx, runner); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateRunner", err)
|
||||
}
|
||||
|
||||
response := &structs.RegisterRunnerResponse{
|
||||
ID: runner.ID,
|
||||
UUID: runner.UUID,
|
||||
Token: runner.Token,
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -76,3 +76,10 @@ type swaggerActionRunnerListResponse struct {
|
|||
// Links to other pages, if any
|
||||
Link string `json:"Link"`
|
||||
}
|
||||
|
||||
// RegisterRunnerResponse contains the details of the just registered runner.
|
||||
// swagger:response RegisterRunnerResponse
|
||||
type swaggerRegisterRunnerResponse struct {
|
||||
// in: body
|
||||
Body api.RegisterRunnerResponse `json:"body"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,4 +239,7 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
NoteOptions api.NoteOptions
|
||||
|
||||
// in:body
|
||||
RegisterRunnerOptions api.RegisterRunnerOptions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,33 @@ func GetRunner(ctx *context.APIContext) {
|
|||
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.ParamsInt64("runner_id"))
|
||||
}
|
||||
|
||||
// RegisterRunner registers a new user-level runner
|
||||
func RegisterRunner(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/runners user registerUserRunner
|
||||
// ---
|
||||
// summary: Register a new user-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/RegisterRunnerOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/RegisterRunnerResponse"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "401":
|
||||
// "$ref": "#/responses/unauthorized"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.RegisterRunner(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// DeleteRunner deletes a particular user-level runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ type API interface {
|
|||
ListRunners(*context.APIContext)
|
||||
// GetRunner get a runner
|
||||
GetRunner(*context.APIContext)
|
||||
// RegisterRunner registers a new runner
|
||||
RegisterRunner(*context.APIContext)
|
||||
// DeleteRunner delete runner
|
||||
DeleteRunner(*context.APIContext)
|
||||
}
|
||||
|
|
|
|||
213
templates/swagger/v1_json.tmpl
generated
213
templates/swagger/v1_json.tmpl
generated
|
|
@ -346,6 +346,42 @@
|
|||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Register a new global runner",
|
||||
"operationId": "registerAdminRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RegisterRunnerOptions"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/RegisterRunnerResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/actions/runners/jobs": {
|
||||
|
|
@ -2715,6 +2751,49 @@
|
|||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"organization"
|
||||
],
|
||||
"summary": "Register a new organization-level runner",
|
||||
"operationId": "registerOrgRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the organization",
|
||||
"name": "org",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RegisterRunnerOptions"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/RegisterRunnerResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org}/actions/runners/jobs": {
|
||||
|
|
@ -5408,6 +5487,56 @@
|
|||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Register a new repository-level runner",
|
||||
"operationId": "registerRepoRunner",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RegisterRunnerOptions"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/RegisterRunnerResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/actions/runners/jobs": {
|
||||
|
|
@ -18852,6 +18981,42 @@
|
|||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Register a new user-level runner",
|
||||
"operationId": "registerUserRunner",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RegisterRunnerOptions"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/responses/RegisterRunnerResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/actions/runners/jobs": {
|
||||
|
|
@ -28320,6 +28485,46 @@
|
|||
},
|
||||
"x-go-package": "forgejo.org/modules/structs"
|
||||
},
|
||||
"RegisterRunnerOptions": {
|
||||
"type": "object",
|
||||
"title": "RegisterRunnerOptions declares the accepted options for registering runners.",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Description of the runner to register.",
|
||||
"type": "string",
|
||||
"x-go-name": "Description"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the runner to register. The name of the runner does not have to be unique.",
|
||||
"type": "string",
|
||||
"x-go-name": "Name"
|
||||
}
|
||||
},
|
||||
"x-go-package": "forgejo.org/modules/structs"
|
||||
},
|
||||
"RegisterRunnerResponse": {
|
||||
"type": "object",
|
||||
"title": "RegisterRunnerResponse contains the details of the just registered runner.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "ID"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"x-go-name": "Token"
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"x-go-name": "UUID"
|
||||
}
|
||||
},
|
||||
"x-go-package": "forgejo.org/modules/structs"
|
||||
},
|
||||
"RegistrationToken": {
|
||||
"description": "RegistrationToken is a string used to register a runner with a server",
|
||||
"type": "object",
|
||||
|
|
@ -30715,6 +30920,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"RegisterRunnerResponse": {
|
||||
"description": "RegisterRunnerResponse contains the details of the just registered runner.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RegisterRunnerResponse"
|
||||
}
|
||||
},
|
||||
"RegistrationToken": {
|
||||
"description": "RegistrationToken is a string used to register a runner with a server",
|
||||
"schema": {
|
||||
|
|
@ -31039,7 +31250,7 @@
|
|||
"parameterBodies": {
|
||||
"description": "parameterBodies",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NoteOptions"
|
||||
"$ref": "#/definitions/RegisterRunnerOptions"
|
||||
}
|
||||
},
|
||||
"quotaExceeded": {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"forgejo.org/routers/api/v1/shared"
|
||||
"forgejo.org/tests"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -182,7 +183,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
|
||||
|
||||
t.Run("GetRunners", func(t *testing.T) {
|
||||
t.Run("Get runners", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -233,7 +234,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
assert.Contains(t, runners, runnerThree)
|
||||
})
|
||||
|
||||
t.Run("GetRunnersPaginated", func(t *testing.T) {
|
||||
t.Run("Get runners paginated", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners?page=1&limit=5")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -246,7 +247,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
assert.Len(t, runners, 5)
|
||||
})
|
||||
|
||||
t.Run("GetGlobalRunner", func(t *testing.T) {
|
||||
t.Run("Get global runner", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners/130793")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -269,7 +270,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
assert.Equal(t, runnerOne, runner)
|
||||
})
|
||||
|
||||
t.Run("GetRepositoryScopedRunner", func(t *testing.T) {
|
||||
t.Run("Get repository-scoped runner", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners/130794")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -292,7 +293,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
assert.Equal(t, runnerFour, runner)
|
||||
})
|
||||
|
||||
t.Run("DeleteGlobalRunner", func(t *testing.T) {
|
||||
t.Run("Delete global runner", func(t *testing.T) {
|
||||
url := "/api/v1/admin/actions/runners/130791"
|
||||
|
||||
request := NewRequest(t, "GET", url)
|
||||
|
|
@ -308,7 +309,7 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
MakeRequest(t, request, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeleteRepositoryScopedRunner", func(t *testing.T) {
|
||||
t.Run("Delete repository-scoped runner", func(t *testing.T) {
|
||||
url := "/api/v1/admin/actions/runners/130794"
|
||||
|
||||
request := NewRequest(t, "GET", url)
|
||||
|
|
@ -323,4 +324,73 @@ func TestAPIAdminActionsRunnerOperations(t *testing.T) {
|
|||
request.AddTokenAuth(readToken)
|
||||
MakeRequest(t, request, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("Register runner", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner", Description: "Some description"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/admin/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
assert.NotNil(t, registerRunnerResponse)
|
||||
assert.Positive(t, registerRunnerResponse.ID)
|
||||
assert.Equal(t, gouuid.Version(4), gouuid.MustParse(registerRunnerResponse.UUID).Version())
|
||||
assert.Regexp(t, "(?i)^[0-9a-f]{40}$", registerRunnerResponse.Token)
|
||||
|
||||
registeredRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
assert.Equal(t, registerRunnerResponse.ID, registeredRunner.ID)
|
||||
assert.Equal(t, registerRunnerResponse.UUID, registeredRunner.UUID)
|
||||
assert.Zero(t, registeredRunner.OwnerID)
|
||||
assert.Zero(t, registeredRunner.RepoID)
|
||||
assert.Equal(t, "api-runner", registeredRunner.Name)
|
||||
assert.Equal(t, "Some description", registeredRunner.Description)
|
||||
assert.Empty(t, registeredRunner.AgentLabels)
|
||||
assert.Empty(t, registeredRunner.Version)
|
||||
assert.NotEmpty(t, registeredRunner.TokenHash)
|
||||
assert.NotEmpty(t, registeredRunner.TokenSalt)
|
||||
})
|
||||
|
||||
t.Run("Runner registration does not update runner with identical name", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/admin/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
secondRequest := NewRequestWithJSON(t, "POST", "/api/v1/admin/actions/runners", options)
|
||||
secondRequest.AddTokenAuth(writeToken)
|
||||
secondResponse := MakeRequest(t, secondRequest, http.StatusCreated)
|
||||
|
||||
var secondRegisterRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, secondResponse, &secondRegisterRunnerResponse)
|
||||
|
||||
firstRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
secondRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: secondRegisterRunnerResponse.UUID})
|
||||
|
||||
assert.NotEqual(t, firstRunner.ID, secondRunner.ID)
|
||||
assert.NotEqual(t, firstRunner.UUID, secondRunner.UUID)
|
||||
})
|
||||
|
||||
t.Run("Runner registration requires write token for admin scope", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/admin/actions/runners", options)
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var errorMessage *errorResponse
|
||||
DecodeJSON(t, response, &errorMessage)
|
||||
|
||||
assert.Equal(t, "token does not have at least one of required scope(s): [write:admin]", errorMessage.Message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"forgejo.org/routers/api/v1/shared"
|
||||
"forgejo.org/tests"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -111,7 +112,7 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
|
|||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
|
||||
|
||||
t.Run("GetRunners", func(t *testing.T) {
|
||||
t.Run("Get runners", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -147,7 +148,7 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
|
|||
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
|
||||
})
|
||||
|
||||
t.Run("GetRunnersPaginated", func(t *testing.T) {
|
||||
t.Run("Get runners paginated", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners?page=1&limit=1")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -160,7 +161,7 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
|
|||
assert.Len(t, runners, 1)
|
||||
})
|
||||
|
||||
t.Run("GetRunner", func(t *testing.T) {
|
||||
t.Run("Get runner", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners/655691")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -183,7 +184,7 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
|
|||
assert.Equal(t, runnerOne, runner)
|
||||
})
|
||||
|
||||
t.Run("DeleteRunner", func(t *testing.T) {
|
||||
t.Run("Delete runner", func(t *testing.T) {
|
||||
url := "/api/v1/orgs/org3/actions/runners/655691"
|
||||
|
||||
request := NewRequest(t, "GET", url)
|
||||
|
|
@ -198,4 +199,73 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
|
|||
request.AddTokenAuth(readToken)
|
||||
MakeRequest(t, request, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("Register runner", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner", Description: "Some description"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
assert.NotNil(t, registerRunnerResponse)
|
||||
assert.Positive(t, registerRunnerResponse.ID)
|
||||
assert.Equal(t, gouuid.Version(4), gouuid.MustParse(registerRunnerResponse.UUID).Version())
|
||||
assert.Regexp(t, "(?i)^[0-9a-f]{40}$", registerRunnerResponse.Token)
|
||||
|
||||
registeredRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
assert.Equal(t, registerRunnerResponse.ID, registeredRunner.ID)
|
||||
assert.Equal(t, registerRunnerResponse.UUID, registeredRunner.UUID)
|
||||
assert.Equal(t, int64(3), registeredRunner.OwnerID)
|
||||
assert.Zero(t, registeredRunner.RepoID)
|
||||
assert.Equal(t, "api-runner", registeredRunner.Name)
|
||||
assert.Equal(t, "Some description", registeredRunner.Description)
|
||||
assert.Empty(t, registeredRunner.AgentLabels)
|
||||
assert.Empty(t, registeredRunner.Version)
|
||||
assert.NotEmpty(t, registeredRunner.TokenHash)
|
||||
assert.NotEmpty(t, registeredRunner.TokenSalt)
|
||||
})
|
||||
|
||||
t.Run("Runner registration does not update runner with identical name", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
secondRequest := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/actions/runners", options)
|
||||
secondRequest.AddTokenAuth(writeToken)
|
||||
secondResponse := MakeRequest(t, secondRequest, http.StatusCreated)
|
||||
|
||||
var secondRegisterRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, secondResponse, &secondRegisterRunnerResponse)
|
||||
|
||||
firstRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
secondRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: secondRegisterRunnerResponse.UUID})
|
||||
|
||||
assert.NotEqual(t, firstRunner.ID, secondRunner.ID)
|
||||
assert.NotEqual(t, firstRunner.UUID, secondRunner.UUID)
|
||||
})
|
||||
|
||||
t.Run("Runner registration requires write token for organization scope", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/actions/runners", options)
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var errorMessage *errorResponse
|
||||
DecodeJSON(t, response, &errorMessage)
|
||||
|
||||
assert.Equal(t, "token does not have at least one of required scope(s): [write:organization]", errorMessage.Message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
files_service "forgejo.org/services/repository/files"
|
||||
"forgejo.org/tests"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -380,11 +381,12 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
|
|||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
session := loginUser(t, user2.Name)
|
||||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
t.Run("GetRunners", func(t *testing.T) {
|
||||
t.Run("Get runners", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/repos/user2/test_workflows/actions/runners")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -420,7 +422,7 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
|
|||
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
|
||||
})
|
||||
|
||||
t.Run("GetRunnersPaginated", func(t *testing.T) {
|
||||
t.Run("Get runners paginated", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/repos/user2/test_workflows/actions/runners?page=1&limit=1")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -433,7 +435,7 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
|
|||
assert.Len(t, runners, 1)
|
||||
})
|
||||
|
||||
t.Run("GetRunner", func(t *testing.T) {
|
||||
t.Run("Get runner", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/repos/user2/test_workflows/actions/runners/899251")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -456,7 +458,7 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
|
|||
assert.Equal(t, runnerOne, runner)
|
||||
})
|
||||
|
||||
t.Run("DeleteRunner", func(t *testing.T) {
|
||||
t.Run("Delete runner", func(t *testing.T) {
|
||||
url := "/api/v1/repos/user2/test_workflows/actions/runners/899253"
|
||||
|
||||
request := NewRequest(t, "GET", url)
|
||||
|
|
@ -471,4 +473,76 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
|
|||
request.AddTokenAuth(readToken)
|
||||
MakeRequest(t, request, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("Register runner", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner", Description: "Some description"}
|
||||
|
||||
requestURL := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners", repo1.OwnerName, repo1.Name)
|
||||
request := NewRequestWithJSON(t, "POST", requestURL, options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
assert.NotNil(t, registerRunnerResponse)
|
||||
assert.Positive(t, registerRunnerResponse.ID)
|
||||
assert.Equal(t, gouuid.Version(4), gouuid.MustParse(registerRunnerResponse.UUID).Version())
|
||||
assert.Regexp(t, "(?i)^[0-9a-f]{40}$", registerRunnerResponse.Token)
|
||||
|
||||
registeredRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
assert.Equal(t, registerRunnerResponse.ID, registeredRunner.ID)
|
||||
assert.Equal(t, registerRunnerResponse.UUID, registeredRunner.UUID)
|
||||
assert.Zero(t, registeredRunner.OwnerID)
|
||||
assert.Equal(t, repo1.ID, registeredRunner.RepoID)
|
||||
assert.Equal(t, "api-runner", registeredRunner.Name)
|
||||
assert.Equal(t, "Some description", registeredRunner.Description)
|
||||
assert.Empty(t, registeredRunner.AgentLabels)
|
||||
assert.Empty(t, registeredRunner.Version)
|
||||
assert.NotEmpty(t, registeredRunner.TokenHash)
|
||||
assert.NotEmpty(t, registeredRunner.TokenSalt)
|
||||
})
|
||||
|
||||
t.Run("Runner registration does not update runner with identical name", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
requestURL := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners", repo1.OwnerName, repo1.Name)
|
||||
request := NewRequestWithJSON(t, "POST", requestURL, options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
secondRequest := NewRequestWithJSON(t, "POST", requestURL, options)
|
||||
secondRequest.AddTokenAuth(writeToken)
|
||||
secondResponse := MakeRequest(t, secondRequest, http.StatusCreated)
|
||||
|
||||
var secondRegisterRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, secondResponse, &secondRegisterRunnerResponse)
|
||||
|
||||
firstRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
secondRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: secondRegisterRunnerResponse.UUID})
|
||||
|
||||
assert.NotEqual(t, firstRunner.ID, secondRunner.ID)
|
||||
assert.NotEqual(t, firstRunner.UUID, secondRunner.UUID)
|
||||
})
|
||||
|
||||
t.Run("Runner registration requires write token for repository scope", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
requestURL := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners", repo1.OwnerName, repo1.Name)
|
||||
request := NewRequestWithJSON(t, "POST", requestURL, options)
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var errorMessage *errorResponse
|
||||
DecodeJSON(t, response, &errorMessage)
|
||||
|
||||
assert.Equal(t, "token does not have at least one of required scope(s): [write:repository]", errorMessage.Message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"forgejo.org/routers/api/v1/shared"
|
||||
"forgejo.org/tests"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -109,7 +110,7 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
|
|||
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser)
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
t.Run("GetRunners", func(t *testing.T) {
|
||||
t.Run("Get runners", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/user/actions/runners")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -145,7 +146,7 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
|
|||
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
|
||||
})
|
||||
|
||||
t.Run("GetRunnersPaginated", func(t *testing.T) {
|
||||
t.Run("Get runners paginated", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/user/actions/runners?page=1&limit=1")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -158,7 +159,7 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
|
|||
assert.Len(t, runners, 1)
|
||||
})
|
||||
|
||||
t.Run("GetRunner", func(t *testing.T) {
|
||||
t.Run("Get runner", func(t *testing.T) {
|
||||
request := NewRequest(t, "GET", "/api/v1/user/actions/runners/71303")
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusOK)
|
||||
|
|
@ -181,7 +182,7 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
|
|||
assert.Equal(t, runnerThree, runner)
|
||||
})
|
||||
|
||||
t.Run("DeleteRunner", func(t *testing.T) {
|
||||
t.Run("Delete runner", func(t *testing.T) {
|
||||
url := "/api/v1/user/actions/runners/71303"
|
||||
|
||||
request := NewRequest(t, "GET", url)
|
||||
|
|
@ -196,4 +197,73 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
|
|||
request.AddTokenAuth(readToken)
|
||||
MakeRequest(t, request, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("Register runner", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner", Description: "Some description"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
assert.NotNil(t, registerRunnerResponse)
|
||||
assert.Positive(t, registerRunnerResponse.ID)
|
||||
assert.Equal(t, gouuid.Version(4), gouuid.MustParse(registerRunnerResponse.UUID).Version())
|
||||
assert.Regexp(t, "(?i)^[0-9a-f]{40}$", registerRunnerResponse.Token)
|
||||
|
||||
registeredRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
assert.Equal(t, registerRunnerResponse.ID, registeredRunner.ID)
|
||||
assert.Equal(t, registerRunnerResponse.UUID, registeredRunner.UUID)
|
||||
assert.Equal(t, user2.ID, registeredRunner.OwnerID)
|
||||
assert.Zero(t, registeredRunner.RepoID)
|
||||
assert.Equal(t, "api-runner", registeredRunner.Name)
|
||||
assert.Equal(t, "Some description", registeredRunner.Description)
|
||||
assert.Empty(t, registeredRunner.AgentLabels)
|
||||
assert.Empty(t, registeredRunner.Version)
|
||||
assert.NotEmpty(t, registeredRunner.TokenHash)
|
||||
assert.NotEmpty(t, registeredRunner.TokenSalt)
|
||||
})
|
||||
|
||||
t.Run("Runner registration does not update runner with identical name", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/runners", options)
|
||||
request.AddTokenAuth(writeToken)
|
||||
response := MakeRequest(t, request, http.StatusCreated)
|
||||
|
||||
var registerRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, response, ®isterRunnerResponse)
|
||||
|
||||
secondRequest := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/runners", options)
|
||||
secondRequest.AddTokenAuth(writeToken)
|
||||
secondResponse := MakeRequest(t, secondRequest, http.StatusCreated)
|
||||
|
||||
var secondRegisterRunnerResponse *api.RegisterRunnerResponse
|
||||
DecodeJSON(t, secondResponse, &secondRegisterRunnerResponse)
|
||||
|
||||
firstRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: registerRunnerResponse.UUID})
|
||||
secondRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{UUID: secondRegisterRunnerResponse.UUID})
|
||||
|
||||
assert.NotEqual(t, firstRunner.ID, secondRunner.ID)
|
||||
assert.NotEqual(t, firstRunner.UUID, secondRunner.UUID)
|
||||
})
|
||||
|
||||
t.Run("Runner registration requires write token for user scope", func(t *testing.T) {
|
||||
options := api.RegisterRunnerOptions{Name: "api-runner"}
|
||||
|
||||
request := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/runners", options)
|
||||
request.AddTokenAuth(readToken)
|
||||
response := MakeRequest(t, request, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var errorMessage *errorResponse
|
||||
DecodeJSON(t, response, &errorMessage)
|
||||
|
||||
assert.Equal(t, "token does not have at least one of required scope(s): [write:user]", errorMessage.Message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue