Add runner controller token API
What does this MR do and why?
Add runner controller token API for instance admins to list, view, create, and delete token via REST APIs.
This is a part of the epic: Phase 1: admission control (&19660).
Changelog: added
API Endpoints added in this MR:
- List all AC: GET
/runner_controllers/:id/tokens - Get detail: GET
/runner_controllers/:id/tokens/:id - Create new token: POST
/runner_controllers/:id/tokens
Revoke endpoint will be added as a follow-up: #582812, and rotate in #578798.
References
- Part of Runner controller management API (Part 2) Runne... (#578797 - closed).
- The corresponding table was added in Add runner_controller_tokens table and model (!212206 - merged).
- Token Authenticatable module: https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/token_authenticatable.md.
How to set up and validate locally
- Set up GDK and prepare instance admin's personal access token.
- Verify the endpoints using
curl.
export PAT="your-personal-access-token"
Create runner controller if not exists:
curl --request POST \
--header "Content-Type: application/json" \
--header "PRIVATE-TOKEN: $PAT" \
"https://gdk.test:3443/api/v4/runner_controllers"
Get tokens
curl --request GET \
--header "Content-Type: application/json" \
--header "PRIVATE-TOKEN: $PAT" \
"https://gdk.test:3443/api/v4/runner_controllers/1/tokens"
Create new token:
curl --request POST \
--header "Content-Type: application/json" \
--header "PRIVATE-TOKEN: $PAT" \
--data '{
"description": "Validates runner security settings before registration"
}' \
"https://gdk.test:3443/api/v4/runner_controllers/1/tokens"
returns:
{
"id": 15,
"runner_controller_id": 1,
"description": "Validates runner security settings before registration",
"created_at": "2025-11-28T05:36:26.168Z",
"updated_at": "2025-11-28T05:36:26.168Z",
"token": "glrct-abcde******************"
}
Database query plans
List runner controller tokens
https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140780
SELECT "ci_runner_controller_tokens".*
FROM "ci_runner_controller_tokens"
WHERE "ci_runner_controller_tokens"."runner_controller_id" = 1
ORDER BY "ci_runner_controller_tokens"."id" ASC
LIMIT 20 offset 0
Limit (cost=0.01..0.02 rows=1 width=96) (actual time=0.026..0.027 rows=0 loops=1)
Buffers: shared hit=3
-> Sort (cost=0.01..0.02 rows=1 width=96) (actual time=0.025..0.026 rows=0 loops=1)
Sort Key: ci_runner_controller_tokens.id
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=3
-> Seq Scan on public.ci_runner_controller_tokens (cost=0.00..0.00 rows=1 width=96) (actual time=0.005..0.006 rows=0 loops=1)
Filter: (ci_runner_controller_tokens.runner_controller_id = 1)
Rows Removed by Filter: 0
Settings: seq_page_cost = '4', work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off'
Select runner controller token
https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140781
SELECT "ci_runner_controller_tokens".*
FROM "ci_runner_controller_tokens"
WHERE "ci_runner_controller_tokens"."runner_controller_id" = 1
AND "ci_runner_controller_tokens"."id" = 16
LIMIT 1
Limit (cost=0.00..0.00 rows=1 width=96) (actual time=0.006..0.006 rows=0 loops=1)
-> Seq Scan on public.ci_runner_controller_tokens (cost=0.00..0.00 rows=1 width=96) (actual time=0.005..0.005 rows=0 loops=1)
Filter: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.id = 16))
Rows Removed by Filter: 0
Settings: work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4'
Create new runner controller token
INSERT INTO "ci_runner_controller_tokens"
(
"description",
"token_digest",
"runner_controller_id",
"created_at",
"updated_at"
)
VALUES
(
'Validates runner security settings before registration',
'M+ZZR5zepDNkZpkpZ2jKqMeThaZL/nYI7M1VoGDpVcM=',
1,
'2025-12-02 05:04:12.588131',
'2025-12-02 05:04:12.588131'
)
returning "id"
plan:
Insert on ci_runner_controller_tokens (cost=0.00..0.01 rows=1 width=96)
-> Result (cost=0.00..0.01 rows=1 width=96)
Delete a runner controller token
https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140783
DELETE FROM "ci_runner_controller_tokens"
WHERE "ci_runner_controller_tokens"."id" = 1
plan:
ModifyTable on public.ci_runner_controller_tokens (cost=0.14..3.16 rows=0 width=0) (actual time=0.039..0.040 rows=0 loops=1)
Buffers: shared hit=6
-> Index Scan using ci_runner_controller_tokens_pkey on public.ci_runner_controller_tokens (cost=0.14..3.16 rows=1 width=6) (actual time=0.038..0.039 rows=0 loops=1)
Index Cond: (ci_runner_controller_tokens.id = 1)
Buffers: shared hit=6
Settings: work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4'
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.