mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-20 23:59:55 +00:00
Resolve merge conflicts with main
This commit is contained in:
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -12,6 +12,7 @@
|
|||||||
- [ ] Is a feature enhancement
|
- [ ] Is a feature enhancement
|
||||||
- [ ] It is a refactor
|
- [ ] It is a refactor
|
||||||
- [ ] Created tests that fail without the change (if possible)
|
- [ ] Created tests that fail without the change (if possible)
|
||||||
|
- [ ] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first).
|
||||||
|
|
||||||
> By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
|
> By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ If you haven't already, join our slack workspace [here](https://docs.netbird.io/
|
|||||||
- [Contributing to NetBird](#contributing-to-netbird)
|
- [Contributing to NetBird](#contributing-to-netbird)
|
||||||
- [Contents](#contents)
|
- [Contents](#contents)
|
||||||
- [Code of conduct](#code-of-conduct)
|
- [Code of conduct](#code-of-conduct)
|
||||||
|
- [Discuss changes with the NetBird team first](#discuss-changes-with-the-netbird-team-first)
|
||||||
- [Directory structure](#directory-structure)
|
- [Directory structure](#directory-structure)
|
||||||
- [Development setup](#development-setup)
|
- [Development setup](#development-setup)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
@@ -33,6 +34,14 @@ Conduct which can be found in the file [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
|||||||
By participating, you are expected to uphold this code. Please report
|
By participating, you are expected to uphold this code. Please report
|
||||||
unacceptable behavior to community@netbird.io.
|
unacceptable behavior to community@netbird.io.
|
||||||
|
|
||||||
|
## Discuss changes with the NetBird team first
|
||||||
|
|
||||||
|
Changes to the **public API**, **gRPC protocols**, **functionality behavior**, **CLI / service flags**, or **new features** should be discussed with the NetBird team before you start the work. These surfaces are part of NetBird's contract with operators, self-hosters, and downstream integrators, and changes to them have compatibility, security, and release-planning implications that benefit from an early conversation.
|
||||||
|
|
||||||
|
Open an issue or reach out on [Slack](https://docs.netbird.io/slack-url) to talk through what you have in mind. We'll help shape the change, flag any constraints we know about, and confirm the direction so the PR review can focus on implementation rather than design.
|
||||||
|
|
||||||
|
Typical bug fixes, internal refactors, documentation updates, and tests do not need pre-discussion — open the PR directly.
|
||||||
|
|
||||||
## Directory structure
|
## Directory structure
|
||||||
|
|
||||||
The NetBird project monorepo is organized to maintain most of its individual dependencies code within their directories, except for a few auxiliary or shared packages.
|
The NetBird project monorepo is organized to maintain most of its individual dependencies code within their directories, except for a few auxiliary or shared packages.
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ func (d *Detector) triggerCallback(event EventType, cb func(event EventType), do
|
|||||||
}
|
}
|
||||||
|
|
||||||
doneChan := make(chan struct{})
|
doneChan := make(chan struct{})
|
||||||
timeout := time.NewTimer(500 * time.Millisecond)
|
// macOS forces sleep ~30s after kIOMessageSystemWillSleep, so block long
|
||||||
|
// enough for teardown to finish while staying under that deadline.
|
||||||
|
timeout := time.NewTimer(20 * time.Second)
|
||||||
defer timeout.Stop()
|
defer timeout.Stop()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type store interface {
|
|||||||
UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error
|
UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error
|
||||||
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
||||||
GetActiveProxyClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error)
|
GetActiveProxyClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error)
|
||||||
GetActiveProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error)
|
GetProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error)
|
||||||
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (m *mockStore) GetActiveProxyClusterAddressesForAccount(ctx context.Context
|
|||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockStore) GetActiveProxyClusters(_ context.Context, _ string) ([]proxy.Cluster, error) {
|
func (m *mockStore) GetProxyClusters(_ context.Context, _ string) ([]proxy.Cluster, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockStore) CleanupStaleProxies(ctx context.Context, d time.Duration) error {
|
func (m *mockStore) CleanupStaleProxies(ctx context.Context, d time.Duration) error {
|
||||||
|
|||||||
@@ -42,10 +42,35 @@ func (Proxy) TableName() string {
|
|||||||
return "proxies"
|
return "proxies"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClusterType is the source of a proxy cluster.
|
||||||
|
type ClusterType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ClusterTypeAccount is a cluster operated by the account itself (BYOP) —
|
||||||
|
// at least one proxy row in the cluster carries a non-NULL account_id.
|
||||||
|
ClusterTypeAccount ClusterType = "account"
|
||||||
|
// ClusterTypeShared is a cluster operated by NetBird and shared across
|
||||||
|
// accounts — all proxy rows in the cluster have account_id IS NULL.
|
||||||
|
ClusterTypeShared ClusterType = "shared"
|
||||||
|
)
|
||||||
|
|
||||||
// Cluster represents a group of proxy nodes serving the same address.
|
// Cluster represents a group of proxy nodes serving the same address.
|
||||||
|
//
|
||||||
|
// Online and ConnectedProxies derive from the same 2-min active window
|
||||||
|
// the rest of the module uses, but Cluster rows are not gated on it —
|
||||||
|
// the cluster listing surfaces offline clusters too so operators can
|
||||||
|
// see and clean them up. The 1-hour heartbeat reaper still bounds the
|
||||||
|
// table eventually.
|
||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
ID string
|
ID string
|
||||||
Address string
|
Address string
|
||||||
|
Type ClusterType
|
||||||
|
Online bool
|
||||||
ConnectedProxies int
|
ConnectedProxies int
|
||||||
SelfHosted bool
|
// Capability flags. *bool because nil means "no proxy reported a
|
||||||
|
// capability for this cluster" — the dashboard renders these as
|
||||||
|
// unknown rather than false.
|
||||||
|
SupportsCustomPorts *bool
|
||||||
|
RequireSubdomain *bool
|
||||||
|
SupportsCrowdSec *bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
GetActiveClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error)
|
GetClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error)
|
||||||
DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error
|
DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error
|
||||||
GetAllServices(ctx context.Context, accountID, userID string) ([]*Service, error)
|
GetAllServices(ctx context.Context, accountID, userID string) ([]*Service, error)
|
||||||
GetService(ctx context.Context, accountID, userID, serviceID string) (*Service, error)
|
GetService(ctx context.Context, accountID, userID, serviceID string) (*Service, error)
|
||||||
|
|||||||
@@ -65,20 +65,6 @@ func (mr *MockManagerMockRecorder) CreateServiceFromPeer(ctx, accountID, peerID,
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServiceFromPeer", reflect.TypeOf((*MockManager)(nil).CreateServiceFromPeer), ctx, accountID, peerID, req)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServiceFromPeer", reflect.TypeOf((*MockManager)(nil).CreateServiceFromPeer), ctx, accountID, peerID, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAllServices mocks base method.
|
|
||||||
func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DeleteAllServices", ctx, accountID, userID)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAllServices indicates an expected call of DeleteAllServices.
|
|
||||||
func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllServices", reflect.TypeOf((*MockManager)(nil).DeleteAllServices), ctx, accountID, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAccountCluster mocks base method.
|
// DeleteAccountCluster mocks base method.
|
||||||
func (m *MockManager) DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error {
|
func (m *MockManager) DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -93,6 +79,20 @@ func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, accountID, userID,
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, accountID, userID, clusterAddress)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, accountID, userID, clusterAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAllServices mocks base method.
|
||||||
|
func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteAllServices", ctx, accountID, userID)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAllServices indicates an expected call of DeleteAllServices.
|
||||||
|
func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllServices", reflect.TypeOf((*MockManager)(nil).DeleteAllServices), ctx, accountID, userID)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteService mocks base method.
|
// DeleteService mocks base method.
|
||||||
func (m *MockManager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error {
|
func (m *MockManager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -122,21 +122,6 @@ func (mr *MockManagerMockRecorder) GetAccountServices(ctx, accountID interface{}
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountServices", reflect.TypeOf((*MockManager)(nil).GetAccountServices), ctx, accountID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountServices", reflect.TypeOf((*MockManager)(nil).GetAccountServices), ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveClusters mocks base method.
|
|
||||||
func (m *MockManager) GetActiveClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetActiveClusters", ctx, accountID, userID)
|
|
||||||
ret0, _ := ret[0].([]proxy.Cluster)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveClusters indicates an expected call of GetActiveClusters.
|
|
||||||
func (mr *MockManagerMockRecorder) GetActiveClusters(ctx, accountID, userID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveClusters", reflect.TypeOf((*MockManager)(nil).GetActiveClusters), ctx, accountID, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllServices mocks base method.
|
// GetAllServices mocks base method.
|
||||||
func (m *MockManager) GetAllServices(ctx context.Context, accountID, userID string) ([]*Service, error) {
|
func (m *MockManager) GetAllServices(ctx context.Context, accountID, userID string) ([]*Service, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -152,19 +137,19 @@ func (mr *MockManagerMockRecorder) GetAllServices(ctx, accountID, userID interfa
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllServices", reflect.TypeOf((*MockManager)(nil).GetAllServices), ctx, accountID, userID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllServices", reflect.TypeOf((*MockManager)(nil).GetAllServices), ctx, accountID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceByDomain mocks base method.
|
// GetClusters mocks base method.
|
||||||
func (m *MockManager) GetServiceByDomain(ctx context.Context, domain string) (*Service, error) {
|
func (m *MockManager) GetClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetServiceByDomain", ctx, domain)
|
ret := m.ctrl.Call(m, "GetClusters", ctx, accountID, userID)
|
||||||
ret0, _ := ret[0].(*Service)
|
ret0, _ := ret[0].([]proxy.Cluster)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceByDomain indicates an expected call of GetServiceByDomain.
|
// GetClusters indicates an expected call of GetClusters.
|
||||||
func (mr *MockManagerMockRecorder) GetServiceByDomain(ctx, domain interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) GetClusters(ctx, accountID, userID interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByDomain", reflect.TypeOf((*MockManager)(nil).GetServiceByDomain), ctx, domain)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusters", reflect.TypeOf((*MockManager)(nil).GetClusters), ctx, accountID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGlobalServices mocks base method.
|
// GetGlobalServices mocks base method.
|
||||||
@@ -197,6 +182,21 @@ func (mr *MockManagerMockRecorder) GetService(ctx, accountID, userID, serviceID
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockManager)(nil).GetService), ctx, accountID, userID, serviceID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockManager)(nil).GetService), ctx, accountID, userID, serviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetServiceByDomain mocks base method.
|
||||||
|
func (m *MockManager) GetServiceByDomain(ctx context.Context, domain string) (*Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetServiceByDomain", ctx, domain)
|
||||||
|
ret0, _ := ret[0].(*Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServiceByDomain indicates an expected call of GetServiceByDomain.
|
||||||
|
func (mr *MockManagerMockRecorder) GetServiceByDomain(ctx, domain interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByDomain", reflect.TypeOf((*MockManager)(nil).GetServiceByDomain), ctx, domain)
|
||||||
|
}
|
||||||
|
|
||||||
// GetServiceByID mocks base method.
|
// GetServiceByID mocks base method.
|
||||||
func (m *MockManager) GetServiceByID(ctx context.Context, accountID, serviceID string) (*Service, error) {
|
func (m *MockManager) GetServiceByID(ctx context.Context, accountID, serviceID string) (*Service, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func (h *handler) getClusters(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
clusters, err := h.manager.GetActiveClusters(r.Context(), userAuth.AccountId, userAuth.UserId)
|
clusters, err := h.manager.GetClusters(r.Context(), userAuth.AccountId, userAuth.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(r.Context(), err, w)
|
util.WriteError(r.Context(), err, w)
|
||||||
return
|
return
|
||||||
@@ -196,10 +196,14 @@ func (h *handler) getClusters(w http.ResponseWriter, r *http.Request) {
|
|||||||
apiClusters := make([]api.ProxyCluster, 0, len(clusters))
|
apiClusters := make([]api.ProxyCluster, 0, len(clusters))
|
||||||
for _, c := range clusters {
|
for _, c := range clusters {
|
||||||
apiClusters = append(apiClusters, api.ProxyCluster{
|
apiClusters = append(apiClusters, api.ProxyCluster{
|
||||||
Id: c.ID,
|
Id: c.ID,
|
||||||
Address: c.Address,
|
Address: c.Address,
|
||||||
ConnectedProxies: c.ConnectedProxies,
|
Type: api.ProxyClusterType(c.Type),
|
||||||
SelfHosted: c.SelfHosted,
|
Online: c.Online,
|
||||||
|
ConnectedProxies: c.ConnectedProxies,
|
||||||
|
SupportsCustomPorts: c.SupportsCustomPorts,
|
||||||
|
RequireSubdomain: c.RequireSubdomain,
|
||||||
|
SupportsCrowdsec: c.SupportsCrowdSec,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ type ClusterDeriver interface {
|
|||||||
type CapabilityProvider interface {
|
type CapabilityProvider interface {
|
||||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
|
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -112,8 +113,12 @@ func (m *Manager) StartExposeReaper(ctx context.Context) {
|
|||||||
m.exposeReaper.StartExposeReaper(ctx)
|
m.exposeReaper.StartExposeReaper(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveClusters returns all active proxy clusters with their connected proxy count.
|
// GetClusters returns every proxy cluster visible to the account
|
||||||
func (m *Manager) GetActiveClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
// (shared + its own BYOP), regardless of whether any proxy in the
|
||||||
|
// cluster is currently heartbeating. Each cluster is enriched with the
|
||||||
|
// capability flags reported by its active proxies so the dashboard can
|
||||||
|
// render feature support without a second round-trip.
|
||||||
|
func (m *Manager) GetClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
||||||
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
@@ -122,7 +127,18 @@ func (m *Manager) GetActiveClusters(ctx context.Context, accountID, userID strin
|
|||||||
return nil, status.NewPermissionDeniedError()
|
return nil, status.NewPermissionDeniedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.store.GetActiveProxyClusters(ctx, accountID)
|
clusters, err := m.store.GetProxyClusters(ctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range clusters {
|
||||||
|
clusters[i].SupportsCustomPorts = m.capabilities.ClusterSupportsCustomPorts(ctx, clusters[i].Address)
|
||||||
|
clusters[i].RequireSubdomain = m.capabilities.ClusterRequireSubdomain(ctx, clusters[i].Address)
|
||||||
|
clusters[i].SupportsCrowdSec = m.capabilities.ClusterSupportsCrowdSec(ctx, clusters[i].Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccountCluster removes all proxy registrations for the given cluster address
|
// DeleteAccountCluster removes all proxy registrations for the given cluster address
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (m *mockReverseProxyManager) GetServiceByDomain(_ context.Context, domain s
|
|||||||
return nil, errors.New("service not found for domain: " + domain)
|
return nil, errors.New("service not found for domain: " + domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockReverseProxyManager) GetActiveClusters(_ context.Context, _, _ string) ([]proxy.Cluster, error) {
|
func (m *mockReverseProxyManager) GetClusters(_ context.Context, _, _ string) ([]proxy.Cluster, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ func (m *testValidateSessionServiceManager) GetServiceByDomain(ctx context.Conte
|
|||||||
return m.store.GetServiceByDomain(ctx, domain)
|
return m.store.GetServiceByDomain(ctx, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testValidateSessionServiceManager) GetActiveClusters(_ context.Context, _, _ string) ([]proxy.Cluster, error) {
|
func (m *testValidateSessionServiceManager) GetClusters(_ context.Context, _, _ string) ([]proxy.Cluster, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ func (m *testServiceManager) GetServiceByDomain(ctx context.Context, domain stri
|
|||||||
return m.store.GetServiceByDomain(ctx, domain)
|
return m.store.GetServiceByDomain(ctx, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testServiceManager) GetActiveClusters(_ context.Context, _, _ string) ([]nbproxy.Cluster, error) {
|
func (m *testServiceManager) GetClusters(_ context.Context, _, _ string) ([]nbproxy.Cluster, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,11 @@ func TestMigrateNetIPFieldFromBlobToJSON_WithJSONData(t *testing.T) {
|
|||||||
require.NoError(t, err, "Failed to insert account")
|
require.NoError(t, err, "Failed to insert account")
|
||||||
|
|
||||||
account.PeersG = []nbpeer.Peer{
|
account.PeersG = []nbpeer.Peer{
|
||||||
{AccountID: "1234", Location: nbpeer.Location{ConnectionIP: net.IP{10, 0, 0, 1}}},
|
{
|
||||||
|
AccountID: "1234",
|
||||||
|
Location: nbpeer.Location{ConnectionIP: net.IP{10, 0, 0, 1}},
|
||||||
|
Status: &nbpeer.PeerStatus{LastSeen: time.Now()},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.Save(account).Error
|
err = db.Save(account).Error
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ type PeerStatus struct { //nolint:revive
|
|||||||
// active session". Integer nanoseconds are used so equality is
|
// active session". Integer nanoseconds are used so equality is
|
||||||
// precision-safe across drivers, and so the predicates compose to a
|
// precision-safe across drivers, and so the predicates compose to a
|
||||||
// single bigint comparison.
|
// single bigint comparison.
|
||||||
SessionStartedAt int64
|
SessionStartedAt int64 `gorm:"not null;default:0"`
|
||||||
// Connected indicates whether peer is connected to the management service or not
|
// Connected indicates whether peer is connected to the management service or not
|
||||||
Connected bool
|
Connected bool
|
||||||
// LoginExpired
|
// LoginExpired
|
||||||
|
|||||||
@@ -2218,6 +2218,9 @@ func Test_IsUniqueConstraintError(t *testing.T) {
|
|||||||
ID: "test-peer-id",
|
ID: "test-peer-id",
|
||||||
AccountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
AccountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||||
DNSLabel: "test-peer-dns-label",
|
DNSLabel: "test-peer-dns-label",
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
LastSeen: time.Now(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -5736,19 +5736,67 @@ func (s *SqlStore) DeleteAccountCluster(ctx context.Context, clusterAddress, acc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqlStore) GetActiveProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error) {
|
// GetProxyClusters returns every cluster the account can see (shared
|
||||||
var clusters []proxy.Cluster
|
// plus its own BYOP), regardless of whether any proxy in the cluster
|
||||||
|
// is currently heartbeating. Online and ConnectedProxies are derived
|
||||||
|
// from the 2-min active window so the dashboard can render offline
|
||||||
|
// clusters distinctly; the 1-hour heartbeat reaper still removes rows
|
||||||
|
// that go quiet for too long.
|
||||||
|
//
|
||||||
|
// AccountOwned is determined by whether any proxy row in the group
|
||||||
|
// carries a non-NULL account_id; the caller maps that to Cluster.Type.
|
||||||
|
// Capability flags are NOT filled here — the handler enriches them via
|
||||||
|
// the per-cluster capability lookups.
|
||||||
|
func (s *SqlStore) GetProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error) {
|
||||||
|
activeCutoff := time.Now().Add(-proxyActiveThreshold)
|
||||||
|
|
||||||
|
type clusterRow struct {
|
||||||
|
ID string
|
||||||
|
Address string
|
||||||
|
ConnectedProxies int
|
||||||
|
Online bool
|
||||||
|
AccountOwned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []clusterRow
|
||||||
result := s.db.Model(&proxy.Proxy{}).
|
result := s.db.Model(&proxy.Proxy{}).
|
||||||
Select("MIN(id) as id, cluster_address as address, COUNT(*) as connected_proxies, COUNT(account_id) > 0 as self_hosted").
|
Select(
|
||||||
Where("status = ? AND last_seen > ? AND (account_id IS NULL OR account_id = ?)",
|
"MIN(id) AS id, "+
|
||||||
proxy.StatusConnected, time.Now().Add(-proxyActiveThreshold), accountID).
|
"cluster_address AS address, "+
|
||||||
|
// COUNT(CASE WHEN ... THEN 1 END) counts only non-NULL — i.e. only
|
||||||
|
// rows that satisfy the predicate — so it works portably across
|
||||||
|
// sqlite/postgres/mysql without dialect-specific FILTER syntax.
|
||||||
|
"COUNT(CASE WHEN status = ? AND last_seen > ? THEN 1 END) AS connected_proxies, "+
|
||||||
|
// MAX(CASE …) > 0 expresses BOOL_OR in a way Postgres tolerates
|
||||||
|
// (Postgres can't MAX a boolean column).
|
||||||
|
"MAX(CASE WHEN status = ? AND last_seen > ? THEN 1 ELSE 0 END) > 0 AS online, "+
|
||||||
|
"MAX(CASE WHEN account_id IS NOT NULL THEN 1 ELSE 0 END) > 0 AS account_owned",
|
||||||
|
proxy.StatusConnected, activeCutoff,
|
||||||
|
proxy.StatusConnected, activeCutoff,
|
||||||
|
).
|
||||||
|
Where("account_id IS NULL OR account_id = ?", accountID).
|
||||||
Group("cluster_address").
|
Group("cluster_address").
|
||||||
Scan(&clusters)
|
Scan(&rows)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.WithContext(ctx).Errorf("failed to get active proxy clusters: %v", result.Error)
|
log.WithContext(ctx).Errorf("failed to get proxy clusters: %v", result.Error)
|
||||||
return nil, status.Errorf(status.Internal, "get active proxy clusters")
|
return nil, status.Errorf(status.Internal, "get proxy clusters")
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters := make([]proxy.Cluster, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
c := proxy.Cluster{
|
||||||
|
ID: r.ID,
|
||||||
|
Address: r.Address,
|
||||||
|
Online: r.Online,
|
||||||
|
ConnectedProxies: r.ConnectedProxies,
|
||||||
|
}
|
||||||
|
if r.AccountOwned {
|
||||||
|
c.Type = proxy.ClusterTypeAccount
|
||||||
|
} else {
|
||||||
|
c.Type = proxy.ClusterTypeShared
|
||||||
|
}
|
||||||
|
clusters = append(clusters, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return clusters, nil
|
return clusters, nil
|
||||||
|
|||||||
109
management/server/store/sql_store_proxy_clusters_test.go
Normal file
109
management/server/store/sql_store_proxy_clusters_test.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
rpproxy "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSqlStore_GetProxyClusters_DerivesOnlineAndType guards the
|
||||||
|
// account-visible cluster list against silent regressions in two
|
||||||
|
// dimensions:
|
||||||
|
//
|
||||||
|
// 1. Online derivation: a cluster with one stale and one fresh proxy
|
||||||
|
// is online and counts only the fresh proxy; a cluster whose
|
||||||
|
// proxies all heartbeated outside the 2-min window appears offline
|
||||||
|
// with connected_proxies = 0 (rather than disappearing, which is
|
||||||
|
// what the old query did).
|
||||||
|
// 2. Type derivation: a cluster scoped to the calling account is
|
||||||
|
// reported as `account`; a cluster with account_id IS NULL is
|
||||||
|
// reported as `shared`. Clusters scoped to other accounts must not
|
||||||
|
// leak into the result.
|
||||||
|
//
|
||||||
|
// Capability flags are intentionally not asserted here — they're filled
|
||||||
|
// by the manager (handler) layer from the per-cluster capability
|
||||||
|
// lookups, not by the store query.
|
||||||
|
func TestSqlStore_GetProxyClusters_DerivesOnlineAndType(t *testing.T) {
|
||||||
|
if (os.Getenv("CI") == "true" && runtime.GOOS == "darwin") || runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skip CI tests on darwin and windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
|
||||||
|
ctx := context.Background()
|
||||||
|
accountID := "acct-clusters"
|
||||||
|
require.NoError(t, store.SaveAccount(ctx, newAccountWithId(ctx, accountID, "user-1", "")))
|
||||||
|
|
||||||
|
otherAccountID := "acct-other"
|
||||||
|
require.NoError(t, store.SaveAccount(ctx, newAccountWithId(ctx, otherAccountID, "user-2", "")))
|
||||||
|
|
||||||
|
acctID := accountID
|
||||||
|
otherID := otherAccountID
|
||||||
|
|
||||||
|
fresh := time.Now().Add(-30 * time.Second)
|
||||||
|
stale := time.Now().Add(-30 * time.Minute)
|
||||||
|
|
||||||
|
mustSave := func(id, cluster string, accID *string, status string, lastSeen time.Time) {
|
||||||
|
require.NoError(t, store.SaveProxy(ctx, &rpproxy.Proxy{
|
||||||
|
ID: id,
|
||||||
|
SessionID: id + "-sess",
|
||||||
|
ClusterAddress: cluster,
|
||||||
|
IPAddress: "10.0.0.1",
|
||||||
|
AccountID: accID,
|
||||||
|
LastSeen: lastSeen,
|
||||||
|
Status: status,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared-mixed: one fresh + one stale proxy → online, connected=1
|
||||||
|
mustSave("p-shared-fresh", "shared-mixed.netbird.io", nil, rpproxy.StatusConnected, fresh)
|
||||||
|
mustSave("p-shared-stale", "shared-mixed.netbird.io", nil, rpproxy.StatusConnected, stale)
|
||||||
|
|
||||||
|
// shared-offline: only stale proxies → offline, connected=0,
|
||||||
|
// but row must still appear (this is the new semantic — old
|
||||||
|
// query would have dropped it entirely).
|
||||||
|
mustSave("p-shared-off", "shared-offline.netbird.io", nil, rpproxy.StatusConnected, stale)
|
||||||
|
|
||||||
|
// account-online: BYOP cluster owned by acctID, fresh
|
||||||
|
mustSave("p-acct-fresh", "byop.acct.example", &acctID, rpproxy.StatusConnected, fresh)
|
||||||
|
|
||||||
|
// other-account: must not surface for acctID
|
||||||
|
mustSave("p-other", "byop.other.example", &otherID, rpproxy.StatusConnected, fresh)
|
||||||
|
|
||||||
|
clusters, err := store.GetProxyClusters(ctx, accountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
byAddr := map[string]rpproxy.Cluster{}
|
||||||
|
for _, c := range clusters {
|
||||||
|
byAddr[c.Address] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotContains(t, byAddr, "byop.other.example",
|
||||||
|
"another account's BYOP cluster must not leak into this account's listing")
|
||||||
|
|
||||||
|
require.Contains(t, byAddr, "shared-mixed.netbird.io")
|
||||||
|
mixed := byAddr["shared-mixed.netbird.io"]
|
||||||
|
assert.Equal(t, rpproxy.ClusterTypeShared, mixed.Type, "shared cluster (account_id IS NULL) must be reported as Type=shared")
|
||||||
|
assert.True(t, mixed.Online, "cluster with a fresh proxy must be online")
|
||||||
|
assert.Equal(t, 1, mixed.ConnectedProxies, "connected_proxies must count only fresh proxies; the stale one should not bump the count")
|
||||||
|
|
||||||
|
require.Contains(t, byAddr, "shared-offline.netbird.io",
|
||||||
|
"offline clusters must still appear so the dashboard can render them — the old GetActiveProxyClusters would have dropped this row, which is the regression this test guards against")
|
||||||
|
offline := byAddr["shared-offline.netbird.io"]
|
||||||
|
assert.Equal(t, rpproxy.ClusterTypeShared, offline.Type)
|
||||||
|
assert.False(t, offline.Online, "no fresh heartbeat → offline")
|
||||||
|
assert.Equal(t, 0, offline.ConnectedProxies, "no fresh proxies → connected_proxies=0")
|
||||||
|
|
||||||
|
require.Contains(t, byAddr, "byop.acct.example")
|
||||||
|
acct := byAddr["byop.acct.example"]
|
||||||
|
assert.Equal(t, rpproxy.ClusterTypeAccount, acct.Type, "BYOP cluster owned by the account must be reported as Type=account")
|
||||||
|
assert.True(t, acct.Online)
|
||||||
|
assert.Equal(t, 1, acct.ConnectedProxies)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -307,7 +307,7 @@ type Store interface {
|
|||||||
UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error
|
UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error
|
||||||
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
||||||
GetActiveProxyClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error)
|
GetActiveProxyClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error)
|
||||||
GetActiveProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error)
|
GetProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error)
|
||||||
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
@@ -471,6 +471,9 @@ func getMigrationsPreAuto(ctx context.Context) []migrationFunc {
|
|||||||
func(db *gorm.DB) error {
|
func(db *gorm.DB) error {
|
||||||
return migration.MigrateNewField[types.User](ctx, db, "email", "")
|
return migration.MigrateNewField[types.User](ctx, db, "email", "")
|
||||||
},
|
},
|
||||||
|
func(db *gorm.DB) error {
|
||||||
|
return migration.MigrateNewField[nbpeer.Peer](ctx, db, "peer_status_session_started_at", int64(0))
|
||||||
|
},
|
||||||
func(db *gorm.DB) error {
|
func(db *gorm.DB) error {
|
||||||
return migration.RemoveDuplicatePeerKeys(ctx, db)
|
return migration.RemoveDuplicatePeerKeys(ctx, db)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -380,6 +380,20 @@ func (mr *MockStoreMockRecorder) DeleteAccount(ctx, account interface{}) *gomock
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccount", reflect.TypeOf((*MockStore)(nil).DeleteAccount), ctx, account)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccount", reflect.TypeOf((*MockStore)(nil).DeleteAccount), ctx, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAccountCluster mocks base method.
|
||||||
|
func (m *MockStore) DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteAccountCluster", ctx, clusterAddress, accountID)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccountCluster indicates an expected call of DeleteAccountCluster.
|
||||||
|
func (mr *MockStoreMockRecorder) DeleteAccountCluster(ctx, clusterAddress, accountID interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockStore)(nil).DeleteAccountCluster), ctx, clusterAddress, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteCustomDomain mocks base method.
|
// DeleteCustomDomain mocks base method.
|
||||||
func (m *MockStore) DeleteCustomDomain(ctx context.Context, accountID, domainID string) error {
|
func (m *MockStore) DeleteCustomDomain(ctx context.Context, accountID, domainID string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -577,20 +591,6 @@ func (mr *MockStoreMockRecorder) DeletePostureChecks(ctx, accountID, postureChec
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePostureChecks", reflect.TypeOf((*MockStore)(nil).DeletePostureChecks), ctx, accountID, postureChecksID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePostureChecks", reflect.TypeOf((*MockStore)(nil).DeletePostureChecks), ctx, accountID, postureChecksID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccountCluster mocks base method.
|
|
||||||
func (m *MockStore) DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DeleteAccountCluster", ctx, clusterAddress, accountID)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAccountCluster indicates an expected call of DeleteAccountCluster.
|
|
||||||
func (mr *MockStoreMockRecorder) DeleteAccountCluster(ctx, clusterAddress, accountID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockStore)(nil).DeleteAccountCluster), ctx, clusterAddress, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRoute mocks base method.
|
// DeleteRoute mocks base method.
|
||||||
func (m *MockStore) DeleteRoute(ctx context.Context, accountID, routeID string) error {
|
func (m *MockStore) DeleteRoute(ctx context.Context, accountID, routeID string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -731,6 +731,20 @@ func (mr *MockStoreMockRecorder) DeleteZoneDNSRecords(ctx, accountID, zoneID int
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteZoneDNSRecords", reflect.TypeOf((*MockStore)(nil).DeleteZoneDNSRecords), ctx, accountID, zoneID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteZoneDNSRecords", reflect.TypeOf((*MockStore)(nil).DeleteZoneDNSRecords), ctx, accountID, zoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisconnectProxy mocks base method.
|
||||||
|
func (m *MockStore) DisconnectProxy(ctx context.Context, proxyID, sessionID string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DisconnectProxy", ctx, proxyID, sessionID)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisconnectProxy indicates an expected call of DisconnectProxy.
|
||||||
|
func (mr *MockStoreMockRecorder) DisconnectProxy(ctx, proxyID, sessionID interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisconnectProxy", reflect.TypeOf((*MockStore)(nil).DisconnectProxy), ctx, proxyID, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
// EphemeralServiceExists mocks base method.
|
// EphemeralServiceExists mocks base method.
|
||||||
func (m *MockStore) EphemeralServiceExists(ctx context.Context, lockStrength LockingStrength, accountID, peerID, domain string) (bool, error) {
|
func (m *MockStore) EphemeralServiceExists(ctx context.Context, lockStrength LockingStrength, accountID, peerID, domain string) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -1332,21 +1346,6 @@ func (mr *MockStoreMockRecorder) GetActiveProxyClusterAddressesForAccount(ctx, a
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveProxyClusterAddressesForAccount", reflect.TypeOf((*MockStore)(nil).GetActiveProxyClusterAddressesForAccount), ctx, accountID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveProxyClusterAddressesForAccount", reflect.TypeOf((*MockStore)(nil).GetActiveProxyClusterAddressesForAccount), ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveProxyClusters mocks base method.
|
|
||||||
func (m *MockStore) GetActiveProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetActiveProxyClusters", ctx, accountID)
|
|
||||||
ret0, _ := ret[0].([]proxy.Cluster)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveProxyClusters indicates an expected call of GetActiveProxyClusters.
|
|
||||||
func (mr *MockStoreMockRecorder) GetActiveProxyClusters(ctx, accountID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveProxyClusters", reflect.TypeOf((*MockStore)(nil).GetActiveProxyClusters), ctx, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllAccounts mocks base method.
|
// GetAllAccounts mocks base method.
|
||||||
func (m *MockStore) GetAllAccounts(ctx context.Context) []*types2.Account {
|
func (m *MockStore) GetAllAccounts(ctx context.Context) []*types2.Account {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -2048,6 +2047,21 @@ func (mr *MockStoreMockRecorder) GetProxyByAccountID(ctx, accountID interface{})
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProxyByAccountID", reflect.TypeOf((*MockStore)(nil).GetProxyByAccountID), ctx, accountID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProxyByAccountID", reflect.TypeOf((*MockStore)(nil).GetProxyByAccountID), ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProxyClusters mocks base method.
|
||||||
|
func (m *MockStore) GetProxyClusters(ctx context.Context, accountID string) ([]proxy.Cluster, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetProxyClusters", ctx, accountID)
|
||||||
|
ret0, _ := ret[0].([]proxy.Cluster)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProxyClusters indicates an expected call of GetProxyClusters.
|
||||||
|
func (mr *MockStoreMockRecorder) GetProxyClusters(ctx, accountID interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProxyClusters", reflect.TypeOf((*MockStore)(nil).GetProxyClusters), ctx, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
// GetResourceGroups mocks base method.
|
// GetResourceGroups mocks base method.
|
||||||
func (m *MockStore) GetResourceGroups(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) ([]*types2.Group, error) {
|
func (m *MockStore) GetResourceGroups(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) ([]*types2.Group, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -2950,20 +2964,6 @@ func (mr *MockStoreMockRecorder) SaveProxy(ctx, proxy interface{}) *gomock.Call
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveProxy", reflect.TypeOf((*MockStore)(nil).SaveProxy), ctx, proxy)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveProxy", reflect.TypeOf((*MockStore)(nil).SaveProxy), ctx, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectProxy mocks base method.
|
|
||||||
func (m *MockStore) DisconnectProxy(ctx context.Context, proxyID, sessionID string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DisconnectProxy", ctx, proxyID, sessionID)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectProxy indicates an expected call of DisconnectProxy.
|
|
||||||
func (mr *MockStoreMockRecorder) DisconnectProxy(ctx, proxyID, sessionID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisconnectProxy", reflect.TypeOf((*MockStore)(nil).DisconnectProxy), ctx, proxyID, sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveProxyAccessToken mocks base method.
|
// SaveProxyAccessToken mocks base method.
|
||||||
func (m *MockStore) SaveProxyAccessToken(ctx context.Context, token *types2.ProxyAccessToken) error {
|
func (m *MockStore) SaveProxyAccessToken(ctx context.Context, token *types2.ProxyAccessToken) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ func (m *storeBackedServiceManager) GetServiceByDomain(ctx context.Context, doma
|
|||||||
return m.store.GetServiceByDomain(ctx, domain)
|
return m.store.GetServiceByDomain(ctx, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *storeBackedServiceManager) GetActiveClusters(_ context.Context, _, _ string) ([]nbproxy.Cluster, error) {
|
func (m *storeBackedServiceManager) GetClusters(_ context.Context, _, _ string) ([]nbproxy.Cluster, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3421,19 +3421,43 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: Cluster address used for CNAME targets
|
description: Cluster address used for CNAME targets
|
||||||
example: "eu.proxy.netbird.io"
|
example: "eu.proxy.netbird.io"
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/ProxyClusterType'
|
||||||
|
online:
|
||||||
|
type: boolean
|
||||||
|
description: Whether at least one proxy in the cluster has heartbeated within the active window
|
||||||
|
example: true
|
||||||
connected_proxies:
|
connected_proxies:
|
||||||
type: integer
|
type: integer
|
||||||
description: Number of proxy nodes connected in this cluster
|
description: Number of proxy nodes currently connected (heartbeat within the active window)
|
||||||
example: 3
|
example: 3
|
||||||
self_hosted:
|
supports_custom_ports:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether this cluster is a self-hosted (BYOP) proxy managed by the account owner
|
description: Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||||
|
example: true
|
||||||
|
require_subdomain:
|
||||||
|
type: boolean
|
||||||
|
description: Whether services on this cluster must include a subdomain label
|
||||||
|
example: false
|
||||||
|
supports_crowdsec:
|
||||||
|
type: boolean
|
||||||
|
description: Whether all active proxies in the cluster have CrowdSec configured
|
||||||
example: false
|
example: false
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- address
|
- address
|
||||||
|
- type
|
||||||
|
- online
|
||||||
- connected_proxies
|
- connected_proxies
|
||||||
- self_hosted
|
ProxyClusterType:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||||
|
`shared` clusters are operated by NetBird and shared across accounts.
|
||||||
|
enum:
|
||||||
|
- account
|
||||||
|
- shared
|
||||||
|
example: shared
|
||||||
ReverseProxyDomainType:
|
ReverseProxyDomainType:
|
||||||
type: string
|
type: string
|
||||||
description: Type of Reverse Proxy Domain
|
description: Type of Reverse Proxy Domain
|
||||||
|
|||||||
@@ -878,6 +878,24 @@ func (e PolicyRuleUpdateProtocol) Valid() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defines values for ProxyClusterType.
|
||||||
|
const (
|
||||||
|
ProxyClusterTypeAccount ProxyClusterType = "account"
|
||||||
|
ProxyClusterTypeShared ProxyClusterType = "shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Valid indicates whether the value is a known member of the ProxyClusterType enum.
|
||||||
|
func (e ProxyClusterType) Valid() bool {
|
||||||
|
switch e {
|
||||||
|
case ProxyClusterTypeAccount:
|
||||||
|
return true
|
||||||
|
case ProxyClusterTypeShared:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Defines values for ResourceType.
|
// Defines values for ResourceType.
|
||||||
const (
|
const (
|
||||||
ResourceTypeDomain ResourceType = "domain"
|
ResourceTypeDomain ResourceType = "domain"
|
||||||
@@ -3795,16 +3813,33 @@ type ProxyCluster struct {
|
|||||||
// Address Cluster address used for CNAME targets
|
// Address Cluster address used for CNAME targets
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
|
||||||
// ConnectedProxies Number of proxy nodes connected in this cluster
|
// ConnectedProxies Number of proxy nodes currently connected (heartbeat within the active window)
|
||||||
ConnectedProxies int `json:"connected_proxies"`
|
ConnectedProxies int `json:"connected_proxies"`
|
||||||
|
|
||||||
// Id Unique identifier of a proxy in this cluster
|
// Id Unique identifier of a proxy in this cluster
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// SelfHosted Whether this cluster is a self-hosted (BYOP) proxy managed by the account owner
|
// Online Whether at least one proxy in the cluster has heartbeated within the active window
|
||||||
SelfHosted bool `json:"self_hosted"`
|
Online bool `json:"online"`
|
||||||
|
|
||||||
|
// RequireSubdomain Whether services on this cluster must include a subdomain label
|
||||||
|
RequireSubdomain *bool `json:"require_subdomain,omitempty"`
|
||||||
|
|
||||||
|
// SupportsCrowdsec Whether all active proxies in the cluster have CrowdSec configured
|
||||||
|
SupportsCrowdsec *bool `json:"supports_crowdsec,omitempty"`
|
||||||
|
|
||||||
|
// SupportsCustomPorts Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||||
|
SupportsCustomPorts *bool `json:"supports_custom_ports,omitempty"`
|
||||||
|
|
||||||
|
// Type Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||||
|
// `shared` clusters are operated by NetBird and shared across accounts.
|
||||||
|
Type ProxyClusterType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyClusterType Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||||
|
// `shared` clusters are operated by NetBird and shared across accounts.
|
||||||
|
type ProxyClusterType string
|
||||||
|
|
||||||
// ProxyToken defines model for ProxyToken.
|
// ProxyToken defines model for ProxyToken.
|
||||||
type ProxyToken struct {
|
type ProxyToken struct {
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|||||||
Reference in New Issue
Block a user