mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +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",
|
Name: "agent-secret",
|
||||||
Usage: "server-agent shared password",
|
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{
|
&cli.DurationFlag{
|
||||||
Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_MIN_TIME"),
|
Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_MIN_TIME"),
|
||||||
Name: "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)
|
return fmt.Errorf("could not setup log store: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// agents
|
||||||
|
server.Config.Agent.DisableUserRegisteredAgentRegistration = c.Bool("disable-user-agent-registration")
|
||||||
|
|
||||||
// authentication
|
// authentication
|
||||||
server.Config.Pipeline.AuthenticatePublicRepos = c.Bool("authenticate-public-repos")
|
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
|
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
|
## Global registry setting
|
||||||
|
|
||||||
If you want to make available a specific private registry to all pipelines, use the `WOODPECKER_DOCKER_CONFIG` server configuration.
|
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
|
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`
|
### `WOODPECKER_KEEPALIVE_MIN_TIME`
|
||||||
|
|
||||||
> Default: empty
|
> 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
|
CustomCSSFile string
|
||||||
CustomJsFile string
|
CustomJsFile string
|
||||||
}
|
}
|
||||||
|
Agent struct {
|
||||||
|
DisableUserRegisteredAgentRegistration bool
|
||||||
|
}
|
||||||
WebUI struct {
|
WebUI struct {
|
||||||
EnableSwagger bool
|
EnableSwagger bool
|
||||||
SkipVersionCheck 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).
|
// The int return value represents the matching score (higher is better).
|
||||||
type FilterFn func(*model.Task) (bool, int)
|
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
|
// Queue defines a task queue for scheduling tasks among
|
||||||
// a pool of workers.
|
// a pool of workers.
|
||||||
type Queue interface {
|
type Queue interface {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog"
|
"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"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/api/debug"
|
"go.woodpecker-ci.org/woodpecker/v2/server/api/debug"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
"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.PATCH("/registries/:registry", api.PatchOrgRegistry)
|
||||||
org.DELETE("/registries/:registry", api.DeleteOrgRegistry)
|
org.DELETE("/registries/:registry", api.DeleteOrgRegistry)
|
||||||
|
|
||||||
|
if !server.Config.Agent.DisableUserRegisteredAgentRegistration {
|
||||||
org.GET("/agents", api.GetOrgAgents)
|
org.GET("/agents", api.GetOrgAgents)
|
||||||
org.POST("/agents", api.PostOrgAgent)
|
org.POST("/agents", api.PostOrgAgent)
|
||||||
org.PATCH("/agents/:agent_id", api.PatchOrgAgent)
|
org.PATCH("/agents/:agent_id", api.PatchOrgAgent)
|
||||||
|
@ -81,6 +83,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
repo := apiBase.Group("/repos")
|
repo := apiBase.Group("/repos")
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,6 +46,7 @@ func Config(c *gin.Context) {
|
||||||
"skip_version_check": server.Config.WebUI.SkipVersionCheck,
|
"skip_version_check": server.Config.WebUI.SkipVersionCheck,
|
||||||
"root_path": server.Config.Server.RootPath,
|
"root_path": server.Config.Server.RootPath,
|
||||||
"enable_swagger": server.Config.WebUI.EnableSwagger,
|
"enable_swagger": server.Config.WebUI.EnableSwagger,
|
||||||
|
"user_registered_agents": !server.Config.Agent.DisableUserRegisteredAgentRegistration,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default func map with json parser.
|
// default func map with json parser.
|
||||||
|
@ -79,4 +80,5 @@ window.WOODPECKER_VERSION = "{{ .version }}";
|
||||||
window.WOODPECKER_ROOT_PATH = "{{ .root_path }}";
|
window.WOODPECKER_ROOT_PATH = "{{ .root_path }}";
|
||||||
window.WOODPECKER_ENABLE_SWAGGER = {{ .enable_swagger }};
|
window.WOODPECKER_ENABLE_SWAGGER = {{ .enable_swagger }};
|
||||||
window.WOODPECKER_SKIP_VERSION_CHECK = {{ .skip_version_check }}
|
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_CSRF: string | undefined;
|
||||||
WOODPECKER_ROOT_PATH: string | undefined;
|
WOODPECKER_ROOT_PATH: string | undefined;
|
||||||
WOODPECKER_ENABLE_SWAGGER: boolean | undefined;
|
WOODPECKER_ENABLE_SWAGGER: boolean | undefined;
|
||||||
|
WOODPECKER_USER_REGISTERED_AGENTS: boolean | undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,4 +19,5 @@ export default () => ({
|
||||||
csrf: window.WOODPECKER_CSRF ?? null,
|
csrf: window.WOODPECKER_CSRF ?? null,
|
||||||
rootPath: window.WOODPECKER_ROOT_PATH ?? '',
|
rootPath: window.WOODPECKER_ROOT_PATH ?? '',
|
||||||
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER === true || false,
|
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')">
|
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
|
||||||
<UserCLIAndAPITab />
|
<UserCLIAndAPITab />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||||
<UserAgentsTab />
|
<UserAgentsTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<OrgRegistriesTab />
|
<OrgRegistriesTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||||
<OrgAgentsTab />
|
<OrgAgentsTab />
|
||||||
</Tab>
|
</Tab>
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
|
@ -35,6 +35,7 @@ import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||||
import OrgAgentsTab from '~/components/org/settings/OrgAgentsTab.vue';
|
import OrgAgentsTab from '~/components/org/settings/OrgAgentsTab.vue';
|
||||||
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
||||||
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
||||||
|
import useConfig from '~/compositions/useConfig';
|
||||||
import { inject } from '~/compositions/useInjectProvide';
|
import { inject } from '~/compositions/useInjectProvide';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||||
|
|
Loading…
Reference in a new issue