diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go new file mode 100644 index 0000000000..c0d9364435 --- /dev/null +++ b/routers/api/v1/admin/runners.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register global runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken + // --- + // summary: Get an global actions runner registration token + // produces: + // - application/json + // parameters: + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, 0, 0) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index a4c3d6f444..4fe4e20e79 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -948,11 +948,17 @@ func Routes() *web.Route { Post(bind(api.CreateEmailOption{}), user.AddEmail). Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail) - // create or update a user's actions secrets - m.Group("/actions/secrets", func() { - m.Combo("/{secretname}"). - Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret). - Delete(user.DeleteSecret) + // manage user-level actions features + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Combo("/{secretname}"). + Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret). + Delete(user.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), user.GetRegistrationToken) + }) }) m.Get("/followers", user.ListMyFollowers) @@ -1052,10 +1058,16 @@ func Routes() *web.Route { m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) }, reqToken()) - m.Group("/actions/secrets", func() { - m.Combo("/{secretname}"). - Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). - Delete(reqToken(), reqOwner(), repo.DeleteSecret) + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Combo("/{secretname}"). + Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). + Delete(reqToken(), reqOwner(), repo.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken) + }) }) m.Group("/hooks/git", func() { m.Combo("").Get(repo.ListGitHooks) @@ -1422,11 +1434,17 @@ func Routes() *web.Route { m.Combo("/{username}").Get(reqToken(), org.IsMember). Delete(reqToken(), reqOrgOwnership(), org.DeleteMember) }) - m.Group("/actions/secrets", func() { - m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) - m.Combo("/{secretname}"). - Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). - Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) + m.Combo("/{secretname}"). + Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). + Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken) + }) }) m.Group("/public_members", func() { m.Get("", org.ListPublicMembers) @@ -1518,6 +1536,9 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), admin.EditHook). Delete(admin.DeleteHook) }) + m.Group("/runners", func() { + m.Get("/registration-token", admin.GetRegistrationToken) + }) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go new file mode 100644 index 0000000000..05bce8daef --- /dev/null +++ b/routers/api/v1/org/runners.go @@ -0,0 +1,31 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register org runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken + // --- + // 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) +} diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/secrets.go similarity index 100% rename from routers/api/v1/org/action.go rename to routers/api/v1/org/secrets.go diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go new file mode 100644 index 0000000000..0a2bbf8117 --- /dev/null +++ b/routers/api/v1/repo/runners.go @@ -0,0 +1,34 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// GetRegistrationToken returns the token to register repo runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken + // --- + // 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, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) +} diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go new file mode 100644 index 0000000000..a342bd4b63 --- /dev/null +++ b/routers/api/v1/shared/runners.go @@ -0,0 +1,32 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package shared + +import ( + "errors" + "net/http" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/util" +) + +// RegistrationToken is response related to registeration token +// swagger:response RegistrationToken +type RegistrationToken struct { + Token string `json:"token"` +} + +func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) { + token, err := actions_model.GetLatestRunnerToken(ctx, ownerID, repoID) + if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) { + token, err = actions_model.NewRunnerToken(ctx, ownerID, repoID) + } + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token}) +} diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go new file mode 100644 index 0000000000..51556ae0fb --- /dev/null +++ b/routers/api/v1/user/runners.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register user runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken + // --- + // 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) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 215c1692f6..de3bc331f1 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -392,6 +392,23 @@ } } }, + "/admin/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get an global actions runner registration token", + "operationId": "adminGetRunnerRegistrationToken", + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/admin/unadopted": { "get": { "produces": [ @@ -1562,6 +1579,32 @@ } } }, + "/orgs/{org}/actions/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Get an organization's actions runner registration token", + "operationId": "orgGetRunnerRegistrationToken", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/orgs/{org}/actions/secrets": { "get": { "produces": [ @@ -12359,6 +12402,39 @@ } } }, + "/repos/{owner}/{repo}/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get a repository's actions runner registration token", + "operationId": "repoGetRunnerRegistrationToken", + "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}/signing-key.gpg": { "get": { "produces": [ @@ -14517,6 +14593,23 @@ } } }, + "/user/actions/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get an user's actions runner registration token", + "operationId": "userGetRunnerRegistrationToken", + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/user/actions/secrets/{secretname}": { "put": { "consumes": [ @@ -23726,6 +23819,14 @@ } } }, + "RegistrationToken": { + "description": "RegistrationToken is response related to registeration token", + "headers": { + "token": { + "type": "string" + } + } + }, "Release": { "description": "Release", "schema": {