From 49c3b3f70e3f591452104ed4bc3d1e39cebb6453 Mon Sep 17 00:00:00 2001 From: Andreas Ahlenstorf Date: Thu, 25 Dec 2025 05:09:10 +0100 Subject: [PATCH] refactor: update Actions Runner admin API endpoint URLs to be consistent w/ other levels (#10573) Align the URLs of admin API endpoints for runner management with other levels like organizations. It enables using the same URL schema (`/actions/runners`) for managing all kinds of runners. The old API endpoints that use `/admin/runners` have been deprecated but are retained for compatibility reasons for the foreseeable future. ## 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. - [x] 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/.md` to be be used for the release notes instead of the title. ## Release notes - Other changes without a feature or bug label - [PR](https://codeberg.org/forgejo/forgejo/pulls/10573): refactor: update Actions Runner admin API endpoint URLs to be consistent w/ other levels Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10573 Reviewed-by: Mathieu Fenniak Co-authored-by: Andreas Ahlenstorf Co-committed-by: Andreas Ahlenstorf --- routers/api/v1/admin/runners.go | 55 +++++- routers/api/v1/api.go | 7 +- templates/swagger/v1_json.tmpl | 51 +++++- tests/integration/api_admin_actions_test.go | 176 +++++++++++++------- 4 files changed, 221 insertions(+), 68 deletions(-) diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go index d978c86eea..b663eb57a3 100644 --- a/routers/api/v1/admin/runners.go +++ b/routers/api/v1/admin/runners.go @@ -8,9 +8,9 @@ import ( "forgejo.org/services/context" ) -// GetRegistrationToken returns the token to register global runners -func GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken +// GetRunnerRegistrationToken returns a token to register global runners +func GetRunnerRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /admin/actions/runners/registration-token admin adminGetRunnerRegistrationToken // --- // summary: Get a runner registration token for registering global runners // produces: @@ -23,11 +23,58 @@ func GetRegistrationToken(ctx *context.APIContext) { shared.GetRegistrationToken(ctx, 0, 0) } -// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters +// GetRegistrationToken returns the token to register global runners +// +// Deprecated: This operation has been deprecated in Forgejo 15. Use GetRunnerRegistrationToken instead. +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /admin/runners/registration-token admin adminGetRegistrationToken + // --- + // summary: Get a runner registration token for registering global runners + // description: > + // This operation has been deprecated in Forgejo 15. + // Use [`/admin/actions/runners/registration-token`](#/admin/adminGetRunnerRegistrationToken) instead. + // deprecated: true + // produces: + // - application/json + // parameters: + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, 0, 0) +} + +// GetActionRunJobs returns a list of action run jobs +func GetActionRunJobs(ctx *context.APIContext) { + // swagger:operation GET /admin/actions/runners/jobs admin adminGetActionRunJobs + // --- + // summary: Get action run jobs + // produces: + // - application/json + // parameters: + // - name: labels + // in: query + // description: a comma separated list of labels to search for + // type: string + // responses: + // "200": + // "$ref": "#/responses/RunJobList" + // "403": + // "$ref": "#/responses/forbidden" + shared.GetActionRunJobs(ctx, 0, 0) +} + +// SearchActionRunJobs returns a list of actions jobs filtered by the provided parameters +// +// Deprecated: This operation has been deprecated in Forgejo 15. Use GetActionRunJobs instead. func SearchActionRunJobs(ctx *context.APIContext) { // swagger:operation GET /admin/runners/jobs admin adminSearchRunJobs // --- // summary: Search action jobs according to filter conditions + // description: > + // This operation has been deprecated in Forgejo 15. + // Use [`/admin/actions/runners/jobs`](#/admin/adminGetActionRunJobs) instead. + // deprecated: true // produces: // - application/json // parameters: diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f3589094b2..9029ba834b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1702,13 +1702,14 @@ func Routes() *web.Route { }) m.Group("/actions/runners", func() { m.Get("", admin.ListRunners) - m.Get("/registration-token", admin.GetRegistrationToken) + m.Get("/registration-token", admin.GetRunnerRegistrationToken) m.Get("/{runner_id}", admin.GetRunner) m.Delete("/{runner_id}", admin.DeleteRunner) + m.Get("/jobs", admin.GetActionRunJobs) }) m.Group("/runners", func() { - m.Get("/registration-token", admin.GetRegistrationToken) - m.Get("/jobs", admin.SearchActionRunJobs) + m.Get("/registration-token", admin.GetRegistrationToken) //nolint:staticcheck + m.Get("/jobs", admin.SearchActionRunJobs) //nolint:staticcheck }) if setting.Quota.Enabled { m.Group("/quota", func() { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index d4015cd675..4a69a39292 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -348,6 +348,51 @@ } } }, + "/admin/actions/runners/jobs": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get action run jobs", + "operationId": "adminGetActionRunJobs", + "parameters": [ + { + "type": "string", + "description": "a comma separated list of labels to search for", + "name": "labels", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/RunJobList" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + } + }, + "/admin/actions/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get a runner registration token for registering global runners", + "operationId": "adminGetRunnerRegistrationToken", + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/admin/actions/runners/{runner_id}": { "get": { "produces": [ @@ -1236,6 +1281,7 @@ }, "/admin/runners/jobs": { "get": { + "description": "This operation has been deprecated in Forgejo 15. Use [`/admin/actions/runners/jobs`](#/admin/adminGetActionRunJobs) instead.\n", "produces": [ "application/json" ], @@ -1244,6 +1290,7 @@ ], "summary": "Search action jobs according to filter conditions", "operationId": "adminSearchRunJobs", + "deprecated": true, "parameters": [ { "type": "string", @@ -1264,6 +1311,7 @@ }, "/admin/runners/registration-token": { "get": { + "description": "This operation has been deprecated in Forgejo 15. Use [`/admin/actions/runners/registration-token`](#/admin/adminGetRunnerRegistrationToken) instead.\n", "produces": [ "application/json" ], @@ -1271,7 +1319,8 @@ "admin" ], "summary": "Get a runner registration token for registering global runners", - "operationId": "adminGetRunnerRegistrationToken", + "operationId": "adminGetRegistrationToken", + "deprecated": true, "responses": { "200": { "$ref": "#/responses/RegistrationToken" diff --git a/tests/integration/api_admin_actions_test.go b/tests/integration/api_admin_actions_test.go index 90db9c4836..d9a6a5d6e1 100644 --- a/tests/integration/api_admin_actions_test.go +++ b/tests/integration/api_admin_actions_test.go @@ -20,48 +20,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestActionsAPISearchActionJobs_GlobalRunner(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393}) - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - - req := NewRequest( - t, - "GET", - fmt.Sprintf("/api/v1/admin/runners/jobs?labels=%s", "ubuntu-latest"), - ).AddTokenAuth(token) - res := MakeRequest(t, req, http.StatusOK) - - var jobs []*api.ActionRunJob - DecodeJSON(t, res, &jobs) - - assert.Len(t, jobs, 1) - assert.Equal(t, job.ID, jobs[0].ID) -} - -func TestActionsAPISearchActionJobs_GlobalRunnerAllPendingJobsWithoutLabels(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - job196 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 196}) - job397 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 397}) - - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - - req := NewRequest(t, "GET", "/api/v1/admin/runners/jobs?labels=").AddTokenAuth(token) - res := MakeRequest(t, req, http.StatusOK) - - var jobs []*api.ActionRunJob - DecodeJSON(t, res, &jobs) - - assert.Len(t, jobs, 2) - assert.Equal(t, job397.ID, jobs[0].ID) - assert.Equal(t, job196.ID, jobs[1].ID) -} - -func TestActionsAPISearchActionJobs_GlobalRunnerAllPendingJobs(t *testing.T) { +func TestAPIAdminActionsGetJobs(t *testing.T) { defer tests.PrepareTestEnv(t)() job196 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 196}) @@ -75,27 +34,111 @@ func TestActionsAPISearchActionJobs_GlobalRunnerAllPendingJobs(t *testing.T) { adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - req := NewRequest( - t, - "GET", - "/api/v1/admin/runners/jobs", - ).AddTokenAuth(token) - res := MakeRequest(t, req, http.StatusOK) + t.Run("jobs-with-label", func(t *testing.T) { + url := fmt.Sprintf("/api/v1/admin/actions/runners/jobs?labels=%s", "ubuntu-latest") + req := NewRequest(t, "GET", url) + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) - var jobs []*api.ActionRunJob - DecodeJSON(t, res, &jobs) + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) - assert.Len(t, jobs, 7) - assert.Equal(t, job397.ID, jobs[0].ID) - assert.Equal(t, job396.ID, jobs[1].ID) - assert.Equal(t, job395.ID, jobs[2].ID) - assert.Equal(t, job394.ID, jobs[3].ID) - assert.Equal(t, job393.ID, jobs[4].ID) - assert.Equal(t, job198.ID, jobs[5].ID) - assert.Equal(t, job196.ID, jobs[6].ID) + assert.Len(t, jobs, 1) + assert.Equal(t, job393.ID, jobs[0].ID) + }) + + t.Run("jobs-without-labels", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/actions/runners/jobs?labels=") + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) + + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) + + assert.Len(t, jobs, 2) + assert.Equal(t, job397.ID, jobs[0].ID) + assert.Equal(t, job196.ID, jobs[1].ID) + }) + + t.Run("all-jobs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/actions/runners/jobs") + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) + + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) + + assert.Len(t, jobs, 7) + assert.Equal(t, job397.ID, jobs[0].ID) + assert.Equal(t, job396.ID, jobs[1].ID) + assert.Equal(t, job395.ID, jobs[2].ID) + assert.Equal(t, job394.ID, jobs[3].ID) + assert.Equal(t, job393.ID, jobs[4].ID) + assert.Equal(t, job198.ID, jobs[5].ID) + assert.Equal(t, job196.ID, jobs[6].ID) + }) } -func TestAPIGlobalActionsRunnerRegistrationTokenOperations(t *testing.T) { +func TestAPIAdminActionsSearchJobs(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + job196 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 196}) + job198 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 198}) + job393 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393}) + job394 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 394}) + job395 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 395}) + job396 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 396}) + job397 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 397}) + + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + + t.Run("jobs-with-label", func(t *testing.T) { + url := fmt.Sprintf("/api/v1/admin/runners/jobs?labels=%s", "ubuntu-latest") + req := NewRequest(t, "GET", url) + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) + + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) + + assert.Len(t, jobs, 1) + assert.Equal(t, job393.ID, jobs[0].ID) + }) + + t.Run("jobs-without-labels", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/runners/jobs?labels=") + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) + + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) + + assert.Len(t, jobs, 2) + assert.Equal(t, job397.ID, jobs[0].ID) + assert.Equal(t, job196.ID, jobs[1].ID) + }) + + t.Run("all-jobs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/runners/jobs") + req.AddTokenAuth(token) + res := MakeRequest(t, req, http.StatusOK) + + var jobs []*api.ActionRunJob + DecodeJSON(t, res, &jobs) + + assert.Len(t, jobs, 7) + assert.Equal(t, job397.ID, jobs[0].ID) + assert.Equal(t, job396.ID, jobs[1].ID) + assert.Equal(t, job395.ID, jobs[2].ID) + assert.Equal(t, job394.ID, jobs[3].ID) + assert.Equal(t, job393.ID, jobs[4].ID) + assert.Equal(t, job198.ID, jobs[5].ID) + assert.Equal(t, job196.ID, jobs[6].ID) + }) +} + +func TestAPIAdminActionsRegistrationTokenOperations(t *testing.T) { defer unittest.OverrideFixtures("tests/integration/fixtures/TestAPIGlobalActionsRunnerRegistrationTokenOperations")() require.NoError(t, unittest.PrepareTestDatabase()) @@ -115,9 +158,22 @@ func TestAPIGlobalActionsRunnerRegistrationTokenOperations(t *testing.T) { assert.Equal(t, expected, registrationToken) }) + + t.Run("DeprecatedGetRegistrationToken", func(t *testing.T) { + request := NewRequest(t, "GET", "/api/v1/admin/runners/registration-token") + request.AddTokenAuth(readToken) + response := MakeRequest(t, request, http.StatusOK) + + var registrationToken shared.RegistrationToken + DecodeJSON(t, response, ®istrationToken) + + expected := shared.RegistrationToken{Token: "BzcgyhjWhLeKGA4ihJIigeRDrcxrFESd0yizEpb7xZJ"} + + assert.Equal(t, expected, registrationToken) + }) } -func TestAPIGlobalActionsRunnerOperations(t *testing.T) { +func TestAPIAdminActionsRunnerOperations(t *testing.T) { defer unittest.OverrideFixtures("tests/integration/fixtures/TestAPIGlobalActionsRunnerOperations")() require.NoError(t, unittest.PrepareTestDatabase())