mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 11:21:02 +00:00
Add server config to disable user registered agents (#4206)
This commit is contained in:
parent
07baae28af
commit
04e8309e60
12 changed files with 587 additions and 12 deletions
|
@ -213,6 +213,11 @@ var flags = append([]cli.Flag{
|
|||
Name: "agent-secret",
|
||||
Usage: "server-agent shared password",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_DISABLE_USER_AGENT_REGISTRATION"),
|
||||
Name: "disable-user-agent-registration",
|
||||
Usage: "Disable user registered agents",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_MIN_TIME"),
|
||||
Name: "keepalive-min-time",
|
||||
|
|
|
@ -167,6 +167,9 @@ func setupEvilGlobals(ctx context.Context, c *cli.Command, s store.Store) (err e
|
|||
return fmt.Errorf("could not setup log store: %w", err)
|
||||
}
|
||||
|
||||
// agents
|
||||
server.Config.Agent.DisableUserRegisteredAgentRegistration = c.Bool("disable-user-agent-registration")
|
||||
|
||||
// authentication
|
||||
server.Config.Pipeline.AuthenticatePublicRepos = c.Bool("authenticate-public-repos")
|
||||
|
||||
|
|
|
@ -54,6 +54,20 @@ Use the `WOODPECKER_REPO_OWNERS` variable to filter which GitHub user's repos sh
|
|||
WOODPECKER_REPO_OWNERS=my_company,my_company_oss_github_user
|
||||
```
|
||||
|
||||
## Disallow normal users to create agents
|
||||
|
||||
By default, users can create new agents for their repos they have admin access to.
|
||||
If an instance admin doesn't want this feature enabled, they can disable the API and hide the Web UI elements.
|
||||
|
||||
:::note
|
||||
You should set this option if you have, for example,
|
||||
global secrets and don't trust your users to create a rogue agent and pipeline for secret extraction.
|
||||
:::
|
||||
|
||||
```ini
|
||||
WOODPECKER_DISABLE_USER_AGENT_REGISTRATION=true
|
||||
```
|
||||
|
||||
## Global registry setting
|
||||
|
||||
If you want to make available a specific private registry to all pipelines, use the `WOODPECKER_DOCKER_CONFIG` server configuration.
|
||||
|
@ -422,6 +436,12 @@ A shared secret used by server and agents to authenticate communication. A secre
|
|||
|
||||
Read the value for `WOODPECKER_AGENT_SECRET` from the specified filepath
|
||||
|
||||
### `WOODPECKER_DISABLE_USER_AGENT_REGISTRATION`
|
||||
|
||||
> Default: false
|
||||
|
||||
[Read about "Disallow normal users to create agents"](./10-server-config.md#disallow-normal-users-to-create-agents)
|
||||
|
||||
### `WOODPECKER_KEEPALIVE_MIN_TIME`
|
||||
|
||||
> Default: empty
|
||||
|
|
275
server/api/agent_test.go
Normal file
275
server/api/agent_test.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
|
||||
queue_mocks "go.woodpecker-ci.org/woodpecker/v2/server/queue/mocks"
|
||||
mocks_manager "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks"
|
||||
store_mocks "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
)
|
||||
|
||||
var fakeAgent = &model.Agent{
|
||||
ID: 1,
|
||||
Name: "test-agent",
|
||||
OwnerID: 1,
|
||||
NoSchedule: false,
|
||||
}
|
||||
|
||||
func TestGetAgents(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("should get agents", func(t *testing.T) {
|
||||
agents := []*model.Agent{fakeAgent}
|
||||
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentList", mock.Anything).Return(agents, nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
|
||||
GetAgents(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentList", mock.Anything)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response []*model.Agent
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, agents, response)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("should get agent", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
|
||||
|
||||
GetAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentFind", int64(1))
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response model.Agent
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, fakeAgent, &response)
|
||||
})
|
||||
|
||||
t.Run("should return bad request for invalid agent id", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "invalid"}}
|
||||
|
||||
GetAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
|
||||
t.Run("should return not found for non-existent agent", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentFind", int64(2)).Return((*model.Agent)(nil), types.RecordNotExist)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "2"}}
|
||||
|
||||
GetAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("should update agent", func(t *testing.T) {
|
||||
updatedAgent := *fakeAgent
|
||||
updatedAgent.Name = "updated-agent"
|
||||
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
|
||||
mockStore.On("AgentUpdate", mock.AnythingOfType("*model.Agent")).Return(nil)
|
||||
|
||||
mockManager := mocks_manager.NewManager(t)
|
||||
server.Config.Services.Manager = mockManager
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
|
||||
c.Request, _ = http.NewRequest(http.MethodPatch, "/", strings.NewReader(`{"name":"updated-agent"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
PatchAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentFind", int64(1))
|
||||
mockStore.AssertCalled(t, "AgentUpdate", mock.AnythingOfType("*model.Agent"))
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response model.Agent
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "updated-agent", response.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("should create agent", func(t *testing.T) {
|
||||
newAgent := &model.Agent{
|
||||
Name: "new-agent",
|
||||
NoSchedule: false,
|
||||
}
|
||||
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Set("user", &model.User{ID: 1})
|
||||
c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
PostAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent"))
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response model.Agent
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, newAgent.Name, response.Name)
|
||||
assert.NotEmpty(t, response.Token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("should delete agent", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
|
||||
mockStore.On("AgentDelete", mock.AnythingOfType("*model.Agent")).Return(nil)
|
||||
|
||||
mockManager := mocks_manager.NewManager(t)
|
||||
server.Config.Services.Manager = mockManager
|
||||
|
||||
mockQueue := queue_mocks.NewQueue(t)
|
||||
mockQueue.On("Info", mock.Anything).Return(queue.InfoT{})
|
||||
mockQueue.On("KickAgentWorkers", int64(1)).Return()
|
||||
server.Config.Services.Queue = mockQueue
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
|
||||
|
||||
DeleteAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentFind", int64(1))
|
||||
mockStore.AssertCalled(t, "AgentDelete", mock.AnythingOfType("*model.Agent"))
|
||||
mockQueue.AssertCalled(t, "KickAgentWorkers", int64(1))
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
})
|
||||
|
||||
t.Run("should not delete agent with running tasks", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
|
||||
|
||||
mockManager := mocks_manager.NewManager(t)
|
||||
server.Config.Services.Manager = mockManager
|
||||
|
||||
mockQueue := queue_mocks.NewQueue(t)
|
||||
mockQueue.On("Info", mock.Anything).Return(queue.InfoT{
|
||||
Running: []*model.Task{{AgentID: 1}},
|
||||
})
|
||||
server.Config.Services.Queue = mockQueue
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
|
||||
|
||||
DeleteAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
mockStore.AssertCalled(t, "AgentFind", int64(1))
|
||||
mockStore.AssertNotCalled(t, "AgentDelete", mock.Anything)
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostOrgAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("create org agent should succeed", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
|
||||
// Set up a non-admin user
|
||||
c.Set("user", &model.User{
|
||||
ID: 1,
|
||||
Admin: false,
|
||||
})
|
||||
|
||||
c.Params = gin.Params{{Key: "org_id", Value: "1"}}
|
||||
c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
PostOrgAgent(c)
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Ensure an agent was created
|
||||
mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent"))
|
||||
})
|
||||
}
|
|
@ -54,6 +54,9 @@ var Config = struct {
|
|||
CustomCSSFile string
|
||||
CustomJsFile string
|
||||
}
|
||||
Agent struct {
|
||||
DisableUserRegisteredAgentRegistration bool
|
||||
}
|
||||
WebUI struct {
|
||||
EnableSwagger bool
|
||||
SkipVersionCheck bool
|
||||
|
|
259
server/queue/mocks/queue.go
Normal file
259
server/queue/mocks/queue.go
Normal file
|
@ -0,0 +1,259 @@
|
|||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
//go:build test
|
||||
// +build test
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
model "go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
||||
queue "go.woodpecker-ci.org/woodpecker/v2/server/queue"
|
||||
)
|
||||
|
||||
// Queue is an autogenerated mock type for the Queue type
|
||||
type Queue struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Done provides a mock function with given fields: c, id, exitStatus
|
||||
func (_m *Queue) Done(c context.Context, id string, exitStatus model.StatusValue) error {
|
||||
ret := _m.Called(c, id, exitStatus)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Done")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, model.StatusValue) error); ok {
|
||||
r0 = rf(c, id, exitStatus)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Error provides a mock function with given fields: c, id, err
|
||||
func (_m *Queue) Error(c context.Context, id string, err error) error {
|
||||
ret := _m.Called(c, id, err)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Error")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, error) error); ok {
|
||||
r0 = rf(c, id, err)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ErrorAtOnce provides a mock function with given fields: c, ids, err
|
||||
func (_m *Queue) ErrorAtOnce(c context.Context, ids []string, err error) error {
|
||||
ret := _m.Called(c, ids, err)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ErrorAtOnce")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string, error) error); ok {
|
||||
r0 = rf(c, ids, err)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Evict provides a mock function with given fields: c, id
|
||||
func (_m *Queue) Evict(c context.Context, id string) error {
|
||||
ret := _m.Called(c, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Evict")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(c, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// EvictAtOnce provides a mock function with given fields: c, ids
|
||||
func (_m *Queue) EvictAtOnce(c context.Context, ids []string) error {
|
||||
ret := _m.Called(c, ids)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for EvictAtOnce")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok {
|
||||
r0 = rf(c, ids)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Extend provides a mock function with given fields: c, agentID, workflowID
|
||||
func (_m *Queue) Extend(c context.Context, agentID int64, workflowID string) error {
|
||||
ret := _m.Called(c, agentID, workflowID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Extend")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok {
|
||||
r0 = rf(c, agentID, workflowID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Info provides a mock function with given fields: c
|
||||
func (_m *Queue) Info(c context.Context) queue.InfoT {
|
||||
ret := _m.Called(c)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Info")
|
||||
}
|
||||
|
||||
var r0 queue.InfoT
|
||||
if rf, ok := ret.Get(0).(func(context.Context) queue.InfoT); ok {
|
||||
r0 = rf(c)
|
||||
} else {
|
||||
r0 = ret.Get(0).(queue.InfoT)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// KickAgentWorkers provides a mock function with given fields: agentID
|
||||
func (_m *Queue) KickAgentWorkers(agentID int64) {
|
||||
_m.Called(agentID)
|
||||
}
|
||||
|
||||
// Pause provides a mock function with given fields:
|
||||
func (_m *Queue) Pause() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// Poll provides a mock function with given fields: c, agentID, f
|
||||
func (_m *Queue) Poll(c context.Context, agentID int64, f queue.FilterFn) (*model.Task, error) {
|
||||
ret := _m.Called(c, agentID, f)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Poll")
|
||||
}
|
||||
|
||||
var r0 *model.Task
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, queue.FilterFn) (*model.Task, error)); ok {
|
||||
return rf(c, agentID, f)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, queue.FilterFn) *model.Task); ok {
|
||||
r0 = rf(c, agentID, f)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Task)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, queue.FilterFn) error); ok {
|
||||
r1 = rf(c, agentID, f)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Push provides a mock function with given fields: c, task
|
||||
func (_m *Queue) Push(c context.Context, task *model.Task) error {
|
||||
ret := _m.Called(c, task)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Push")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *model.Task) error); ok {
|
||||
r0 = rf(c, task)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PushAtOnce provides a mock function with given fields: c, tasks
|
||||
func (_m *Queue) PushAtOnce(c context.Context, tasks []*model.Task) error {
|
||||
ret := _m.Called(c, tasks)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PushAtOnce")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []*model.Task) error); ok {
|
||||
r0 = rf(c, tasks)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Resume provides a mock function with given fields:
|
||||
func (_m *Queue) Resume() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// Wait provides a mock function with given fields: c, id
|
||||
func (_m *Queue) Wait(c context.Context, id string) error {
|
||||
ret := _m.Called(c, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Wait")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(c, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewQueue creates a new instance of Queue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewQueue(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Queue {
|
||||
mock := &Queue{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -72,6 +72,8 @@ func (t *InfoT) String() string {
|
|||
// The int return value represents the matching score (higher is better).
|
||||
type FilterFn func(*model.Task) (bool, int)
|
||||
|
||||
//go:generate mockery --name Queue --output mocks --case underscore --note "+build test"
|
||||
|
||||
// Queue defines a task queue for scheduling tasks among
|
||||
// a pool of workers.
|
||||
type Queue interface {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/api"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/api/debug"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
|
@ -74,6 +75,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
org.PATCH("/registries/:registry", api.PatchOrgRegistry)
|
||||
org.DELETE("/registries/:registry", api.DeleteOrgRegistry)
|
||||
|
||||
if !server.Config.Agent.DisableUserRegisteredAgentRegistration {
|
||||
org.GET("/agents", api.GetOrgAgents)
|
||||
org.POST("/agents", api.PostOrgAgent)
|
||||
org.PATCH("/agents/:agent_id", api.PatchOrgAgent)
|
||||
|
@ -81,6 +83,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repo := apiBase.Group("/repos")
|
||||
{
|
||||
|
|
|
@ -46,6 +46,7 @@ func Config(c *gin.Context) {
|
|||
"skip_version_check": server.Config.WebUI.SkipVersionCheck,
|
||||
"root_path": server.Config.Server.RootPath,
|
||||
"enable_swagger": server.Config.WebUI.EnableSwagger,
|
||||
"user_registered_agents": !server.Config.Agent.DisableUserRegisteredAgentRegistration,
|
||||
}
|
||||
|
||||
// default func map with json parser.
|
||||
|
@ -79,4 +80,5 @@ window.WOODPECKER_VERSION = "{{ .version }}";
|
|||
window.WOODPECKER_ROOT_PATH = "{{ .root_path }}";
|
||||
window.WOODPECKER_ENABLE_SWAGGER = {{ .enable_swagger }};
|
||||
window.WOODPECKER_SKIP_VERSION_CHECK = {{ .skip_version_check }}
|
||||
window.WOODPECKER_USER_REGISTERED_AGENTS = {{ .user_registered_agents }}
|
||||
`
|
||||
|
|
|
@ -8,6 +8,7 @@ declare global {
|
|||
WOODPECKER_CSRF: string | undefined;
|
||||
WOODPECKER_ROOT_PATH: string | undefined;
|
||||
WOODPECKER_ENABLE_SWAGGER: boolean | undefined;
|
||||
WOODPECKER_USER_REGISTERED_AGENTS: boolean | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,4 +19,5 @@ export default () => ({
|
|||
csrf: window.WOODPECKER_CSRF ?? null,
|
||||
rootPath: window.WOODPECKER_ROOT_PATH ?? '',
|
||||
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER === true || false,
|
||||
userRegisteredAgents: window.WOODPECKER_USER_REGISTERED_AGENTS || false,
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
|
||||
<UserCLIAndAPITab />
|
||||
</Tab>
|
||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<UserAgentsTab />
|
||||
</Tab>
|
||||
</Scaffold>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<OrgRegistriesTab />
|
||||
</Tab>
|
||||
|
||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<OrgAgentsTab />
|
||||
</Tab>
|
||||
</Scaffold>
|
||||
|
@ -35,6 +35,7 @@ import Tab from '~/components/layout/scaffold/Tab.vue';
|
|||
import OrgAgentsTab from '~/components/org/settings/OrgAgentsTab.vue';
|
||||
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
||||
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
import { inject } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||
|
|
Loading…
Reference in a new issue