mirror of
https://github.com/go-gitea/gitea.git
synced 2026-03-17 14:24:07 +00:00
Feature: Add per-runner “Disable/Pause” (#36776)
This PR adds per-runner disable/enable support for Gitea Actions so a registered runner can be paused from picking up new jobs without unregistering. Disabled runners stay registered and online but are excluded from new task assignment; running tasks are allowed to finish. Re-enabling restores pickup, and runner list/get responses now expose disabled state. Also added an endpoint for testing http://localhost:3000/devtest/runner-edit/enable <img width="1509" height="701" alt="Bildschirmfoto 2026-02-27 um 22 13 24" src="https://github.com/user-attachments/assets/5328eda9-e59c-46b6-b398-f436e50ee3da" /> Fixes: https://github.com/go-gitea/gitea/issues/36767
This commit is contained in:
@@ -62,6 +62,8 @@ type ActionRunner struct {
|
|||||||
AgentLabels []string `xorm:"TEXT"`
|
AgentLabels []string `xorm:"TEXT"`
|
||||||
// Store if this is a runner that only ever get one single job assigned
|
// Store if this is a runner that only ever get one single job assigned
|
||||||
Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"`
|
Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"`
|
||||||
|
// Store if this runner is disabled and should not pick up new jobs
|
||||||
|
IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"`
|
||||||
|
|
||||||
Created timeutil.TimeStamp `xorm:"created"`
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||||
@@ -199,6 +201,7 @@ type FindRunnerOptions struct {
|
|||||||
Sort string
|
Sort string
|
||||||
Filter string
|
Filter string
|
||||||
IsOnline optional.Option[bool]
|
IsOnline optional.Option[bool]
|
||||||
|
IsDisabled optional.Option[bool]
|
||||||
WithAvailable bool // not only runners belong to, but also runners can be used
|
WithAvailable bool // not only runners belong to, but also runners can be used
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +242,10 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
|||||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.IsDisabled.Has() {
|
||||||
|
cond = cond.And(builder.Eq{"is_disabled": opts.IsDisabled.Value()})
|
||||||
|
}
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +304,20 @@ func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetRunnerDisabled(ctx context.Context, runner *ActionRunner, isDisabled bool) error {
|
||||||
|
if runner.IsDisabled == isDisabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
runner.IsDisabled = isDisabled
|
||||||
|
if err := UpdateRunner(ctx, runner, "is_disabled"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return IncreaseTaskVersion(ctx, runner.OwnerID, runner.RepoID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRunner deletes a runner by given ID.
|
// DeleteRunner deletes a runner by given ID.
|
||||||
func DeleteRunner(ctx context.Context, id int64) error {
|
func DeleteRunner(ctx context.Context, id int64) error {
|
||||||
if _, err := GetRunnerByID(ctx, id); err != nil {
|
if _, err := GetRunnerByID(ctx, id); err != nil {
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness),
|
newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness),
|
||||||
newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments),
|
newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments),
|
||||||
newMigration(326, "Migrate commit status target URL to use run ID and job ID", v1_26.FixCommitStatusTargetURLToUseRunAndJobID),
|
newMigration(326, "Migrate commit status target URL to use run ID and job ID", v1_26.FixCommitStatusTargetURLToUseRunAndJobID),
|
||||||
|
newMigration(327, "Add disabled state to action runners", v1_26.AddDisabledToActionRunner),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
17
models/migrations/v1_26/v327.go
Normal file
17
models/migrations/v1_26/v327.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_26
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddDisabledToActionRunner(x *xorm.Engine) error {
|
||||||
|
type ActionRunner struct {
|
||||||
|
IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(ActionRunner))
|
||||||
|
return err
|
||||||
|
}
|
||||||
33
models/migrations/v1_26/v327_test.go
Normal file
33
models/migrations/v1_26/v327_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_26
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddDisabledToActionRunner(t *testing.T) {
|
||||||
|
type ActionRunner struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(ActionRunner))
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
_, err := x.Insert(&ActionRunner{Name: "runner"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, AddDisabledToActionRunner(x))
|
||||||
|
|
||||||
|
var isDisabled bool
|
||||||
|
has, err := x.SQL("SELECT is_disabled FROM action_runner WHERE id = ?", 1).Get(&isDisabled)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, has)
|
||||||
|
require.False(t, isDisabled)
|
||||||
|
}
|
||||||
@@ -196,10 +196,18 @@ type ActionRunner struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Busy bool `json:"busy"`
|
Busy bool `json:"busy"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
Ephemeral bool `json:"ephemeral"`
|
Ephemeral bool `json:"ephemeral"`
|
||||||
Labels []*ActionRunnerLabel `json:"labels"`
|
Labels []*ActionRunnerLabel `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EditActionRunnerOption represents the editable fields for a runner.
|
||||||
|
// swagger:model
|
||||||
|
type EditActionRunnerOption struct {
|
||||||
|
// required: true
|
||||||
|
Disabled *bool `json:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
// ActionRunnersResponse returns Runners
|
// ActionRunnersResponse returns Runners
|
||||||
type ActionRunnersResponse struct {
|
type ActionRunnersResponse struct {
|
||||||
Entries []*ActionRunner `json:"runners"`
|
Entries []*ActionRunner `json:"runners"`
|
||||||
|
|||||||
@@ -3644,6 +3644,7 @@
|
|||||||
"actions.runners.id": "ID",
|
"actions.runners.id": "ID",
|
||||||
"actions.runners.name": "Name",
|
"actions.runners.name": "Name",
|
||||||
"actions.runners.owner_type": "Type",
|
"actions.runners.owner_type": "Type",
|
||||||
|
"actions.runners.availability": "Availability",
|
||||||
"actions.runners.description": "Description",
|
"actions.runners.description": "Description",
|
||||||
"actions.runners.labels": "Labels",
|
"actions.runners.labels": "Labels",
|
||||||
"actions.runners.last_online": "Last Online Time",
|
"actions.runners.last_online": "Last Online Time",
|
||||||
@@ -3659,6 +3660,12 @@
|
|||||||
"actions.runners.update_runner": "Update Changes",
|
"actions.runners.update_runner": "Update Changes",
|
||||||
"actions.runners.update_runner_success": "Runner updated successfully",
|
"actions.runners.update_runner_success": "Runner updated successfully",
|
||||||
"actions.runners.update_runner_failed": "Failed to update runner",
|
"actions.runners.update_runner_failed": "Failed to update runner",
|
||||||
|
"actions.runners.enable_runner": "Enable this runner",
|
||||||
|
"actions.runners.enable_runner_success": "Runner enabled successfully",
|
||||||
|
"actions.runners.enable_runner_failed": "Failed to enable runner",
|
||||||
|
"actions.runners.disable_runner": "Disable this runner",
|
||||||
|
"actions.runners.disable_runner_success": "Runner disabled successfully",
|
||||||
|
"actions.runners.disable_runner_failed": "Failed to disable runner",
|
||||||
"actions.runners.delete_runner": "Delete this runner",
|
"actions.runners.delete_runner": "Delete this runner",
|
||||||
"actions.runners.delete_runner_success": "Runner deleted successfully",
|
"actions.runners.delete_runner_success": "Runner deleted successfully",
|
||||||
"actions.runners.delete_runner_failed": "Failed to delete runner",
|
"actions.runners.delete_runner_failed": "Failed to delete runner",
|
||||||
|
|||||||
@@ -156,10 +156,16 @@ func (s *Service) FetchTask(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tasksVersion != latestVersion {
|
if tasksVersion != latestVersion {
|
||||||
|
// Re-load runner from DB so task assignment uses current IsDisabled state
|
||||||
|
// (avoids race where disable commits while this request still has stale runner).
|
||||||
|
freshRunner, err := actions_model.GetRunnerByUUID(ctx, runner.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "get runner: %v", err)
|
||||||
|
}
|
||||||
// if the task version in request is not equal to the version in db,
|
// if the task version in request is not equal to the version in db,
|
||||||
// it means there may still be some tasks that haven't been assigned.
|
// it means there may still be some tasks that haven't been assigned.
|
||||||
// try to pick a task for the runner that send the request.
|
// try to pick a task for the runner that send the request.
|
||||||
if t, ok, err := actions_service.PickTask(ctx, runner); err != nil {
|
if t, ok, err := actions_service.PickTask(ctx, freshRunner); err != nil {
|
||||||
log.Error("pick task failed: %v", err)
|
log.Error("pick task failed: %v", err)
|
||||||
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
|
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
|
||||||
} else if ok {
|
} else if ok {
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ func ListRunners(ctx *context.APIContext) {
|
|||||||
// summary: Get all runners
|
// summary: Get all runners
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: disabled
|
||||||
|
// in: query
|
||||||
|
// description: filter by disabled status (true or false)
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -87,3 +93,34 @@ func DeleteRunner(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRunner update a global runner
|
||||||
|
func UpdateRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /admin/actions/runners/{runner_id} admin updateAdminRunner
|
||||||
|
// ---
|
||||||
|
// summary: Update a global runner
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
shared.UpdateRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -916,6 +916,7 @@ func Routes() *web.Router {
|
|||||||
m.Post("/registration-token", reqToken(), reqOwnerCheck, act.CreateRegistrationToken)
|
m.Post("/registration-token", reqToken(), reqOwnerCheck, act.CreateRegistrationToken)
|
||||||
m.Get("/{runner_id}", reqToken(), reqOwnerCheck, act.GetRunner)
|
m.Get("/{runner_id}", reqToken(), reqOwnerCheck, act.GetRunner)
|
||||||
m.Delete("/{runner_id}", reqToken(), reqOwnerCheck, act.DeleteRunner)
|
m.Delete("/{runner_id}", reqToken(), reqOwnerCheck, act.DeleteRunner)
|
||||||
|
m.Patch("/{runner_id}", reqToken(), reqOwnerCheck, bind(api.EditActionRunnerOption{}), act.UpdateRunner)
|
||||||
})
|
})
|
||||||
m.Get("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns)
|
m.Get("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns)
|
||||||
m.Get("/jobs", reqToken(), reqReaderCheck, act.ListWorkflowJobs)
|
m.Get("/jobs", reqToken(), reqReaderCheck, act.ListWorkflowJobs)
|
||||||
@@ -1043,6 +1044,7 @@ func Routes() *web.Router {
|
|||||||
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
|
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
|
||||||
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||||
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||||
|
m.Patch("/{runner_id}", reqToken(), bind(api.EditActionRunnerOption{}), user.UpdateRunner)
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
||||||
@@ -1728,6 +1730,7 @@ func Routes() *web.Router {
|
|||||||
m.Post("/registration-token", admin.CreateRegistrationToken)
|
m.Post("/registration-token", admin.CreateRegistrationToken)
|
||||||
m.Get("/{runner_id}", admin.GetRunner)
|
m.Get("/{runner_id}", admin.GetRunner)
|
||||||
m.Delete("/{runner_id}", admin.DeleteRunner)
|
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||||
|
m.Patch("/{runner_id}", bind(api.EditActionRunnerOption{}), admin.UpdateRunner)
|
||||||
})
|
})
|
||||||
m.Get("/runs", admin.ListWorkflowRuns)
|
m.Get("/runs", admin.ListWorkflowRuns)
|
||||||
m.Get("/jobs", admin.ListWorkflowJobs)
|
m.Get("/jobs", admin.ListWorkflowJobs)
|
||||||
|
|||||||
@@ -485,6 +485,11 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
|||||||
// description: name of the organization
|
// description: name of the organization
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: disabled
|
||||||
|
// in: query
|
||||||
|
// description: filter by disabled status (true or false)
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -551,6 +556,42 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
|||||||
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRunner update an org-level runner
|
||||||
|
func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /orgs/{org}/actions/runners/{runner_id} organization updateOrgRunner
|
||||||
|
// ---
|
||||||
|
// summary: Update an org-level runner
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// 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
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
shared.UpdateRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
|
// swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
|
||||||
// ---
|
// ---
|
||||||
|
|||||||
@@ -554,6 +554,11 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
|||||||
// description: name of the repo
|
// description: name of the repo
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
|
// - name: disabled
|
||||||
|
// in: query
|
||||||
|
// description: filter by disabled status (true or false)
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -564,11 +569,11 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
|||||||
shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
|
shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRunner get an repo-level runner
|
// GetRunner get a repo-level runner
|
||||||
func (Action) GetRunner(ctx *context.APIContext) {
|
func (Action) GetRunner(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an repo-level runner
|
// summary: Get a repo-level runner
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
@@ -597,11 +602,11 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
|||||||
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRunner delete an repo-level runner
|
// DeleteRunner delete a repo-level runner
|
||||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete an repo-level runner
|
// summary: Delete a repo-level runner
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
@@ -630,6 +635,47 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
|||||||
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRunner update a repo-level runner
|
||||||
|
func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /repos/{owner}/{repo}/actions/runners/{runner_id} repository updateRepoRunner
|
||||||
|
// ---
|
||||||
|
// summary: Update a repo-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: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
shared.UpdateRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
// GetWorkflowRunJobs Lists all jobs for a workflow run.
|
// GetWorkflowRunJobs Lists all jobs for a workflow run.
|
||||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
|
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
@@ -46,11 +47,13 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
}
|
}
|
||||||
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
|
opts := &actions_model.FindRunnerOptions{
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
})
|
}
|
||||||
|
opts.IsDisabled = ctx.FormOptionalBool("disabled")
|
||||||
|
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -125,3 +128,23 @@ func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
|||||||
}
|
}
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
|
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
form := web.GetForm(ctx).(*api.EditActionRunnerOption)
|
||||||
|
if form.Disabled == nil {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "[Disabled]: Required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_model.SetRunnerDisabled(ctx, runner, *form.Disabled); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
GetRunner(ctx, ownerID, repoID, runnerID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -225,6 +225,9 @@ type swaggerParameterBodies struct {
|
|||||||
// in:body
|
// in:body
|
||||||
UpdateVariableOption api.UpdateVariableOption
|
UpdateVariableOption api.UpdateVariableOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
EditActionRunnerOption api.EditActionRunnerOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
LockIssueOption api.LockIssueOption
|
LockIssueOption api.LockIssueOption
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ func ListRunners(ctx *context.APIContext) {
|
|||||||
// summary: Get user-level runners
|
// summary: Get user-level runners
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: disabled
|
||||||
|
// in: query
|
||||||
|
// description: filter by disabled status (true or false)
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -42,11 +48,11 @@ func ListRunners(ctx *context.APIContext) {
|
|||||||
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRunner get an user-level runner
|
// GetRunner get a user-level runner
|
||||||
func GetRunner(ctx *context.APIContext) {
|
func GetRunner(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an user-level runner
|
// summary: Get a user-level runner
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
@@ -65,11 +71,11 @@ func GetRunner(ctx *context.APIContext) {
|
|||||||
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRunner delete an user-level runner
|
// DeleteRunner delete a user-level runner
|
||||||
func DeleteRunner(ctx *context.APIContext) {
|
func DeleteRunner(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete an user-level runner
|
// summary: Delete a user-level runner
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
@@ -87,3 +93,34 @@ func DeleteRunner(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRunner update a user-level runner
|
||||||
|
func UpdateRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /user/actions/runners/{runner_id} user updateUserRunner
|
||||||
|
// ---
|
||||||
|
// summary: Update a user-level runner
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
shared.UpdateRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) {
|
|||||||
}
|
}
|
||||||
hasOnlineRunner := false
|
hasOnlineRunner := false
|
||||||
for _, runner := range runners {
|
for _, runner := range runners {
|
||||||
if runner.CanMatchLabels(job.RunsOn) {
|
if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) {
|
||||||
hasOnlineRunner = true
|
hasOnlineRunner = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,47 @@ func RunnerDeletePost(ctx *context.Context) {
|
|||||||
ctx.JSONRedirect(successRedirectTo)
|
ctx.JSONRedirect(successRedirectTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunnerUpdatePost(ctx *context.Context) {
|
||||||
|
rCtx, err := getRunnersCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("getRunnersCtx", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runner := findActionsRunner(ctx, rCtx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runner.EditableInContext(rCtx.OwnerID, rCtx.RepoID) {
|
||||||
|
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to edit this runner"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled := ctx.FormOptionalBool("disabled")
|
||||||
|
if !isDisabled.Has() {
|
||||||
|
ctx.HTTPError(http.StatusBadRequest, "missing 'disabled' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successKey := "actions.runners.enable_runner_success"
|
||||||
|
failedKey := "actions.runners.enable_runner_failed"
|
||||||
|
if isDisabled.Value() {
|
||||||
|
successKey = "actions.runners.disable_runner_success"
|
||||||
|
failedKey = "actions.runners.disable_runner_failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_model.SetRunnerDisabled(ctx, runner, isDisabled.Value()); err != nil {
|
||||||
|
log.Warn("RunnerUpdatePost.SetRunnerDisabled failed: %v, url: %s", err, ctx.Req.URL)
|
||||||
|
ctx.Flash.Error(ctx.Tr(failedKey))
|
||||||
|
ctx.JSONRedirect("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr(successKey))
|
||||||
|
ctx.JSONRedirect("")
|
||||||
|
}
|
||||||
|
|
||||||
func RedirectToDefaultSetting(ctx *context.Context) {
|
func RedirectToDefaultSetting(ctx *context.Context) {
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners")
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,6 +493,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Get("", shared_actions.Runners)
|
m.Get("", shared_actions.Runners)
|
||||||
m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit).
|
m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit).
|
||||||
Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost)
|
Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost)
|
||||||
|
m.Post("/{runnerid}/update-runner", shared_actions.RunnerUpdatePost)
|
||||||
m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost)
|
m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost)
|
||||||
m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken)
|
m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ type API interface {
|
|||||||
GetRunner(*context.APIContext)
|
GetRunner(*context.APIContext)
|
||||||
// DeleteRunner delete runner
|
// DeleteRunner delete runner
|
||||||
DeleteRunner(*context.APIContext)
|
DeleteRunner(*context.APIContext)
|
||||||
|
// UpdateRunner update runner
|
||||||
|
UpdateRunner(*context.APIContext)
|
||||||
// ListWorkflowJobs list jobs
|
// ListWorkflowJobs list jobs
|
||||||
ListWorkflowJobs(*context.APIContext)
|
ListWorkflowJobs(*context.APIContext)
|
||||||
// ListWorkflowRuns list runs
|
// ListWorkflowRuns list runs
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||||||
actionTask *actions_model.ActionTask
|
actionTask *actions_model.ActionTask
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if runner.IsDisabled {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
if runner.Ephemeral {
|
if runner.Ephemeral {
|
||||||
var task actions_model.ActionTask
|
var task actions_model.ActionTask
|
||||||
has, err := db.GetEngine(ctx).Where("runner_id = ?", runner.ID).Get(&task)
|
has, err := db.GetEngine(ctx).Where("runner_id = ?", runner.ID).Get(&task)
|
||||||
|
|||||||
@@ -521,6 +521,7 @@ func ToActionRunner(ctx context.Context, runner *actions_model.ActionRunner) *ap
|
|||||||
Name: runner.Name,
|
Name: runner.Name,
|
||||||
Status: apiStatus,
|
Status: apiStatus,
|
||||||
Busy: status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE,
|
Busy: status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE,
|
||||||
|
Disabled: runner.IsDisabled,
|
||||||
Ephemeral: runner.Ephemeral,
|
Ephemeral: runner.Ephemeral,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,16 @@
|
|||||||
<label>{{ctx.Locale.Tr "actions.runners.status"}}</label>
|
<label>{{ctx.Locale.Tr "actions.runners.status"}}</label>
|
||||||
<span class="ui {{if .Runner.IsOnline}}green{{else}}basic{{end}} label">{{.Runner.StatusLocaleName ctx.Locale}}</span>
|
<span class="ui {{if .Runner.IsOnline}}green{{else}}basic{{end}} label">{{.Runner.StatusLocaleName ctx.Locale}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field tw-inline-block tw-mr-4">
|
||||||
|
<label>{{ctx.Locale.Tr "actions.runners.availability"}}</label>
|
||||||
|
<span class="ui {{if .Runner.IsDisabled}}grey{{else}}green{{end}} label">
|
||||||
|
{{if .Runner.IsDisabled}}
|
||||||
|
{{ctx.Locale.Tr "disabled"}}
|
||||||
|
{{else}}
|
||||||
|
{{ctx.Locale.Tr "enabled"}}
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="field tw-inline-block tw-mr-4">
|
<div class="field tw-inline-block tw-mr-4">
|
||||||
<label>{{ctx.Locale.Tr "actions.runners.last_online"}}</label>
|
<label>{{ctx.Locale.Tr "actions.runners.last_online"}}</label>
|
||||||
<span>{{if .Runner.LastOnline}}{{DateUtils.TimeSince .Runner.LastOnline}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</span>
|
<span>{{if .Runner.LastOnline}}{{DateUtils.TimeSince .Runner.LastOnline}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</span>
|
||||||
@@ -39,6 +49,9 @@
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<button class="ui primary button" data-url="{{.Link}}">{{ctx.Locale.Tr "actions.runners.update_runner"}}</button>
|
<button class="ui primary button" data-url="{{.Link}}">{{ctx.Locale.Tr "actions.runners.update_runner"}}</button>
|
||||||
|
<button type="button" class="ui button link-action" data-url="{{.Link}}/update-runner?disabled={{not .Runner.IsDisabled}}">
|
||||||
|
{{if .Runner.IsDisabled}}{{ctx.Locale.Tr "actions.runners.enable_runner"}}{{else}}{{ctx.Locale.Tr "actions.runners.disable_runner"}}{{end}}
|
||||||
|
</button>
|
||||||
<button class="ui red button delete-button" data-url="{{.Link}}/delete" data-modal="#runner-delete-modal">
|
<button class="ui red button delete-button" data-url="{{.Link}}/delete" data-modal="#runner-delete-modal">
|
||||||
{{ctx.Locale.Tr "actions.runners.delete_runner"}}</button>
|
{{ctx.Locale.Tr "actions.runners.delete_runner"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,10 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{range .Runners}}
|
{{range .Runners}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="ui label {{if .IsOnline}}green{{end}}">{{.StatusLocaleName ctx.Locale}}</span></td>
|
<td>
|
||||||
|
<span class="ui label {{if .IsOnline}}green{{end}}">{{.StatusLocaleName ctx.Locale}}</span>
|
||||||
|
{{if .IsDisabled}}<span class="ui grey label">{{ctx.Locale.Tr "actions.runners.disabled"}}</span>{{end}}
|
||||||
|
</td>
|
||||||
<td>{{.ID}}</td>
|
<td>{{.ID}}</td>
|
||||||
<td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td>
|
<td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td>
|
||||||
<td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td>
|
<td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td>
|
||||||
|
|||||||
247
templates/swagger/v1_json.tmpl
generated
247
templates/swagger/v1_json.tmpl
generated
@@ -76,6 +76,14 @@
|
|||||||
],
|
],
|
||||||
"summary": "Get all runners",
|
"summary": "Get all runners",
|
||||||
"operationId": "getAdminRunners",
|
"operationId": "getAdminRunners",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "filter by disabled status (true or false)",
|
||||||
|
"name": "disabled",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"$ref": "#/definitions/ActionRunnersResponse"
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -166,6 +174,49 @@
|
|||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Update a global runner",
|
||||||
|
"operationId": "updateAdminRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/actions/runs": {
|
"/admin/actions/runs": {
|
||||||
@@ -1947,6 +1998,12 @@
|
|||||||
"name": "org",
|
"name": "org",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "filter by disabled status (true or false)",
|
||||||
|
"name": "disabled",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -2062,6 +2119,56 @@
|
|||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Update an org-level runner",
|
||||||
|
"operationId": "updateOrgRunner",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/orgs/{org}/actions/runs": {
|
"/orgs/{org}/actions/runs": {
|
||||||
@@ -4872,6 +4979,12 @@
|
|||||||
"name": "repo",
|
"name": "repo",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "filter by disabled status (true or false)",
|
||||||
|
"name": "disabled",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -4928,7 +5041,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"repository"
|
"repository"
|
||||||
],
|
],
|
||||||
"summary": "Get an repo-level runner",
|
"summary": "Get a repo-level runner",
|
||||||
"operationId": "getRepoRunner",
|
"operationId": "getRepoRunner",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -4972,7 +5085,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"repository"
|
"repository"
|
||||||
],
|
],
|
||||||
"summary": "Delete an repo-level runner",
|
"summary": "Delete a repo-level runner",
|
||||||
"operationId": "deleteRepoRunner",
|
"operationId": "deleteRepoRunner",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -5008,6 +5121,63 @@
|
|||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Update a repo-level runner",
|
||||||
|
"operationId": "updateRepoRunner",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/actions/runs": {
|
"/repos/{owner}/{repo}/actions/runs": {
|
||||||
@@ -18441,6 +18611,14 @@
|
|||||||
],
|
],
|
||||||
"summary": "Get user-level runners",
|
"summary": "Get user-level runners",
|
||||||
"operationId": "getUserRunners",
|
"operationId": "getUserRunners",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "filter by disabled status (true or false)",
|
||||||
|
"name": "disabled",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"$ref": "#/definitions/ActionRunnersResponse"
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
@@ -18479,7 +18657,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"summary": "Get an user-level runner",
|
"summary": "Get a user-level runner",
|
||||||
"operationId": "getUserRunner",
|
"operationId": "getUserRunner",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -18509,7 +18687,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"summary": "Delete an user-level runner",
|
"summary": "Delete a user-level runner",
|
||||||
"operationId": "deleteUserRunner",
|
"operationId": "deleteUserRunner",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -18531,6 +18709,49 @@
|
|||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Update a user-level runner",
|
||||||
|
"operationId": "updateUserRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditActionRunnerOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/user/actions/runs": {
|
"/user/actions/runs": {
|
||||||
@@ -21115,6 +21336,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"x-go-name": "Busy"
|
"x-go-name": "Busy"
|
||||||
},
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Disabled"
|
||||||
|
},
|
||||||
"ephemeral": {
|
"ephemeral": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"x-go-name": "Ephemeral"
|
"x-go-name": "Ephemeral"
|
||||||
@@ -24205,6 +24430,20 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"EditActionRunnerOption": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "EditActionRunnerOption represents the editable fields for a runner.",
|
||||||
|
"required": [
|
||||||
|
"disabled"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Disabled"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"EditAttachmentOptions": {
|
"EditAttachmentOptions": {
|
||||||
"description": "EditAttachmentOptions options for editing attachments",
|
"description": "EditAttachmentOptions options for editing attachments",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJobWithNeeds(t *testing.T) {
|
func TestJobWithNeeds(t *testing.T) {
|
||||||
@@ -349,6 +350,122 @@ jobs:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunnerDisableEnable(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
session := loginUser(t, user2.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
t.Run("BasicDisableEnable", func(t *testing.T) {
|
||||||
|
testData := prepareRunnerDisableEnableTest(t, user2, token, "actions-runner-disable-enable", "mock-runner", "runner-disable-enable")
|
||||||
|
|
||||||
|
task1 := testData.runner.fetchTask(t)
|
||||||
|
require.NotNil(t, task1)
|
||||||
|
|
||||||
|
triggerRunnerDisableEnableRun(t, user2, token, testData.repo, "second-push.txt")
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
testData.runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||||
|
testData.runner.fetchNoTask(t, 2*time.Second)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
task2 := testData.runner.fetchTask(t, 5*time.Second)
|
||||||
|
require.NotNil(t, task2)
|
||||||
|
testData.runner.execTask(t, task2, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TasksVersionPath", func(t *testing.T) {
|
||||||
|
testData := prepareRunnerDisableEnableTest(t, user2, token, "actions-runner-version-path", "mock-runner-version-path", "runner-version-path")
|
||||||
|
|
||||||
|
var firstVersion int64
|
||||||
|
var task1 *runnerv1.Task
|
||||||
|
ddl := time.Now().Add(5 * time.Second)
|
||||||
|
for time.Now().Before(ddl) {
|
||||||
|
task1, firstVersion = testData.runner.fetchTaskOnce(t, 0)
|
||||||
|
if task1 != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
require.NotNil(t, task1, "expected to receive first task")
|
||||||
|
require.NotZero(t, firstVersion, "response TasksVersion should be set")
|
||||||
|
|
||||||
|
// Trigger a second run so there is a pending job after we re-enable the runner
|
||||||
|
triggerRunnerDisableEnableRun(t, user2, token, testData.repo, "second-push.txt")
|
||||||
|
time.Sleep(500 * time.Millisecond) // allow workflow run to be created
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
testData.runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||||
|
|
||||||
|
// Fetch with the version we had before disable. Server has bumped version on disable,
|
||||||
|
// so we enter PickTask with a re-loaded runner (disabled) and get no task.
|
||||||
|
taskAfterDisable, _ := testData.runner.fetchTaskOnce(t, firstVersion)
|
||||||
|
assert.Nil(t, taskAfterDisable, "disabled runner must not receive a task when sending previous TasksVersion")
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/%d", user2.Name, testData.repo.Name, testData.runnerID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
task2 := testData.runner.fetchTask(t, 5*time.Second)
|
||||||
|
require.NotNil(t, task2, "after re-enable runner should receive tasks again")
|
||||||
|
testData.runner.execTask(t, task2, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type runnerDisableEnableTestData struct {
|
||||||
|
repo *api.Repository
|
||||||
|
runner *mockRunner
|
||||||
|
runnerID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRunnerDisableEnableTest(t *testing.T, user *user_model.User, authToken, repoName, runnerName, workflowName string) *runnerDisableEnableTestData {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
apiRepo := createActionsTestRepo(t, authToken, repoName, false)
|
||||||
|
runner := newMockRunner()
|
||||||
|
runner.registerAsRepoRunner(t, user.Name, apiRepo.Name, runnerName, []string{"ubuntu-latest"}, false)
|
||||||
|
|
||||||
|
wfTreePath := fmt.Sprintf(".gitea/workflows/%s.yml", workflowName)
|
||||||
|
wfContent := fmt.Sprintf(`name: %s
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo %s
|
||||||
|
`, workflowName, workflowName)
|
||||||
|
opts := getWorkflowCreateFileOptions(user, apiRepo.DefaultBranch, "create workflow", wfContent)
|
||||||
|
createWorkflowFile(t, authToken, user.Name, apiRepo.Name, wfTreePath, opts)
|
||||||
|
|
||||||
|
return &runnerDisableEnableTestData{
|
||||||
|
repo: apiRepo,
|
||||||
|
runner: runner,
|
||||||
|
runnerID: getRepoRunnerID(t, authToken, user.Name, apiRepo.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func triggerRunnerDisableEnableRun(t *testing.T, user *user_model.User, authToken string, repo *api.Repository, treePath string) {
|
||||||
|
t.Helper()
|
||||||
|
opts := getWorkflowCreateFileOptions(user, repo.DefaultBranch, "second push", "second run")
|
||||||
|
createWorkflowFile(t, authToken, user.Name, repo.Name, treePath, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRepoRunnerID(t *testing.T, authToken, ownerName, repoName string) int64 {
|
||||||
|
t.Helper()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners", ownerName, repoName)).AddTokenAuth(authToken)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionRunnersResponse{}
|
||||||
|
DecodeJSON(t, resp, &runnerList)
|
||||||
|
require.Len(t, runnerList.Entries, 1)
|
||||||
|
return runnerList.Entries[0].ID
|
||||||
|
}
|
||||||
|
|
||||||
func TestActionsGiteaContext(t *testing.T) {
|
func TestActionsGiteaContext(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|||||||
@@ -60,17 +60,36 @@ func TestActionsRunnerModify(t *testing.T) {
|
|||||||
sess.MakeRequest(t, req, expectedStatus)
|
sess.MakeRequest(t, req, expectedStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doDisable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||||
|
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=true", baseURL, id))
|
||||||
|
sess.MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
doEnable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
|
||||||
|
req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=false", baseURL, id))
|
||||||
|
sess.MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
||||||
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound)
|
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound)
|
||||||
|
doDisable(t, sess, baseURL, id, http.StatusNotFound)
|
||||||
|
doEnable(t, sess, baseURL, id, http.StatusNotFound)
|
||||||
doDelete(t, sess, baseURL, id, http.StatusNotFound)
|
doDelete(t, sess, baseURL, id, http.StatusNotFound)
|
||||||
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||||
assert.Empty(t, v.Description)
|
assert.Empty(t, v.Description)
|
||||||
|
assert.False(t, v.IsDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
|
||||||
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther)
|
doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther)
|
||||||
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||||
assert.Equal(t, "ChangedDescription", v.Description)
|
assert.Equal(t, "ChangedDescription", v.Description)
|
||||||
|
doDisable(t, sess, baseURL, id, http.StatusOK)
|
||||||
|
v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||||
|
assert.True(t, v.IsDisabled)
|
||||||
|
doEnable(t, sess, baseURL, id, http.StatusOK)
|
||||||
|
v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
|
||||||
|
assert.False(t, v.IsDisabled)
|
||||||
doDelete(t, sess, baseURL, id, http.StatusOK)
|
doDelete(t, sess, baseURL, id, http.StatusOK)
|
||||||
unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id})
|
unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,12 +114,8 @@ func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runne
|
|||||||
ddl := time.Now().Add(fetchTimeout)
|
ddl := time.Now().Add(fetchTimeout)
|
||||||
var task *runnerv1.Task
|
var task *runnerv1.Task
|
||||||
for time.Now().Before(ddl) {
|
for time.Now().Before(ddl) {
|
||||||
resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{
|
task, _ = r.fetchTaskOnce(t, 0)
|
||||||
TasksVersion: 0,
|
if task != nil {
|
||||||
}))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if resp.Msg.Task != nil {
|
|
||||||
task = resp.Msg.Task
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
@@ -127,6 +124,17 @@ func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runne
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchTaskOnce performs a single FetchTask request with the given TasksVersion
|
||||||
|
// and returns the task (if any) and the TasksVersion from the response.
|
||||||
|
// Used to verify the production path where the runner sends the current version.
|
||||||
|
func (r *mockRunner) fetchTaskOnce(t *testing.T, tasksVersion int64) (*runnerv1.Task, int64) {
|
||||||
|
resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{
|
||||||
|
TasksVersion: tasksVersion,
|
||||||
|
}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
return resp.Msg.Task, resp.Msg.TasksVersion
|
||||||
|
}
|
||||||
|
|
||||||
type mockTaskOutcome struct {
|
type mockTaskOutcome struct {
|
||||||
result runnerv1.Result
|
result runnerv1.Result
|
||||||
outputs map[string]string
|
outputs map[string]string
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ func TestAPIActionsRunner(t *testing.T) {
|
|||||||
t.Run("RepoRunner", testActionsRunnerRepo)
|
t.Run("RepoRunner", testActionsRunnerRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRunnerUpdateRequest(t *testing.T, url string, disabled bool) *RequestWrapper {
|
||||||
|
return NewRequestWithJSON(t, http.MethodPatch, url, api.EditActionRunnerOption{
|
||||||
|
Disabled: &disabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testActionsRunnerAdmin(t *testing.T) {
|
func testActionsRunnerAdmin(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
adminUsername := "user1"
|
adminUsername := "user1"
|
||||||
@@ -45,22 +51,39 @@ func testActionsRunnerAdmin(t *testing.T) {
|
|||||||
require.NotEqual(t, -1, idx)
|
require.NotEqual(t, -1, idx)
|
||||||
expectedRunner := runnerList.Entries[idx]
|
expectedRunner := runnerList.Entries[idx]
|
||||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
|
||||||
|
assert.False(t, expectedRunner.Disabled)
|
||||||
assert.False(t, expectedRunner.Ephemeral)
|
assert.False(t, expectedRunner.Ephemeral)
|
||||||
assert.Len(t, expectedRunner.Labels, 2)
|
assert.Len(t, expectedRunner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||||
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
disabledRunner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &disabledRunner)
|
||||||
|
assert.True(t, disabledRunner.Disabled)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/admin/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
// Verify all returned runners can be requested and deleted
|
// Verify all returned runners can be requested and deleted
|
||||||
for _, runnerEntry := range runnerList.Entries {
|
for _, runnerEntry := range runnerList.Entries {
|
||||||
// Verify get the runner by id
|
// Verify get the runner by id
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
runner := api.ActionRunner{}
|
runner := api.ActionRunner{}
|
||||||
DecodeJSON(t, runnerResp, &runner)
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
assert.Equal(t, runnerEntry.Name, runner.Name)
|
assert.Equal(t, runnerEntry.Name, runner.Name)
|
||||||
assert.Equal(t, runnerEntry.ID, runner.ID)
|
assert.Equal(t, runnerEntry.ID, runner.ID)
|
||||||
|
assert.Equal(t, runnerEntry.Disabled, runner.Disabled)
|
||||||
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
|
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
|
||||||
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
|
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
|
||||||
|
|
||||||
@@ -93,6 +116,7 @@ func testActionsRunnerUser(t *testing.T) {
|
|||||||
assert.Len(t, runnerList.Entries, 1)
|
assert.Len(t, runnerList.Entries, 1)
|
||||||
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
|
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
|
||||||
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
|
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
|
||||||
|
assert.False(t, runnerList.Entries[0].Disabled)
|
||||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||||
@@ -107,11 +131,26 @@ func testActionsRunnerUser(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
|
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
|
||||||
assert.Equal(t, int64(34346), runner.ID)
|
assert.Equal(t, int64(34346), runner.ID)
|
||||||
|
assert.False(t, runner.Disabled)
|
||||||
assert.False(t, runner.Ephemeral)
|
assert.False(t, runner.Ephemeral)
|
||||||
assert.Len(t, runner.Labels, 2)
|
assert.Len(t, runner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
assert.True(t, runner.Disabled)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
// Verify delete the runner by id
|
// Verify delete the runner by id
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusNoContent)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
@@ -136,6 +175,7 @@ func testActionsRunnerOwner(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||||
assert.Equal(t, int64(34347), runner.ID)
|
assert.Equal(t, int64(34347), runner.ID)
|
||||||
|
assert.False(t, runner.Disabled)
|
||||||
assert.False(t, runner.Ephemeral)
|
assert.False(t, runner.Ephemeral)
|
||||||
assert.Len(t, runner.Labels, 2)
|
assert.Len(t, runner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
@@ -165,6 +205,7 @@ func testActionsRunnerOwner(t *testing.T) {
|
|||||||
require.NotNil(t, expectedRunner)
|
require.NotNil(t, expectedRunner)
|
||||||
assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
|
assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
|
||||||
assert.Equal(t, int64(34347), expectedRunner.ID)
|
assert.Equal(t, int64(34347), expectedRunner.ID)
|
||||||
|
assert.False(t, expectedRunner.Disabled)
|
||||||
assert.False(t, expectedRunner.Ephemeral)
|
assert.False(t, expectedRunner.Ephemeral)
|
||||||
assert.Len(t, expectedRunner.Labels, 2)
|
assert.Len(t, expectedRunner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||||
@@ -179,11 +220,26 @@ func testActionsRunnerOwner(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||||
assert.Equal(t, int64(34347), runner.ID)
|
assert.Equal(t, int64(34347), runner.ID)
|
||||||
|
assert.False(t, runner.Disabled)
|
||||||
assert.False(t, runner.Ephemeral)
|
assert.False(t, runner.Ephemeral)
|
||||||
assert.Len(t, runner.Labels, 2)
|
assert.Len(t, runner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
|
runnerResp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
assert.True(t, runner.Disabled)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
// Verify delete the runner by id
|
// Verify delete the runner by id
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusNoContent)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
@@ -202,6 +258,22 @@ func testActionsRunnerOwner(t *testing.T) {
|
|||||||
MakeRequest(t, req, http.StatusForbidden)
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("DisableReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
|
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
|
||||||
userUsername := "user2"
|
userUsername := "user2"
|
||||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
@@ -244,6 +316,7 @@ func testActionsRunnerRepo(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||||
assert.Equal(t, int64(34348), runner.ID)
|
assert.Equal(t, int64(34348), runner.ID)
|
||||||
|
assert.False(t, runner.Disabled)
|
||||||
assert.False(t, runner.Ephemeral)
|
assert.False(t, runner.Ephemeral)
|
||||||
assert.Len(t, runner.Labels, 2)
|
assert.Len(t, runner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
@@ -269,6 +342,7 @@ func testActionsRunnerRepo(t *testing.T) {
|
|||||||
assert.Len(t, runnerList.Entries, 1)
|
assert.Len(t, runnerList.Entries, 1)
|
||||||
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
|
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
|
||||||
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
|
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
|
||||||
|
assert.False(t, runnerList.Entries[0].Disabled)
|
||||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||||
@@ -283,11 +357,26 @@ func testActionsRunnerRepo(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||||
assert.Equal(t, int64(34348), runner.ID)
|
assert.Equal(t, int64(34348), runner.ID)
|
||||||
|
assert.False(t, runner.Disabled)
|
||||||
assert.False(t, runner.Ephemeral)
|
assert.False(t, runner.Ephemeral)
|
||||||
assert.Len(t, runner.Labels, 2)
|
assert.Len(t, runner.Labels, 2)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
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)
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
assert.True(t, runner.Disabled)
|
||||||
|
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
// Verify delete the runner by id
|
// 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)
|
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)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
@@ -306,6 +395,22 @@ func testActionsRunnerRepo(t *testing.T) {
|
|||||||
MakeRequest(t, req, http.StatusForbidden)
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("DisableReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348), true).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
req := newRunnerUpdateRequest(t, fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348), false).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
|
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
|
||||||
userUsername := "user2"
|
userUsername := "user2"
|
||||||
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
|||||||
Reference in New Issue
Block a user