feat: enhance EvoBGP with new command-line options for the evobgp-agent, including a watch command for periodic configuration updates. Update go.mod with additional dependencies and improve Docker Compose setup for new services, including NATS and various worker components.
This commit is contained in:
@@ -0,0 +1,798 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var _ Backend = (*Memory)(nil)
|
||||
|
||||
func (m *Memory) CreateModule(tenantID string, in *Module) (*Module, error) {
|
||||
if in == nil || strings.TrimSpace(in.Type) == "" || strings.TrimSpace(in.Name) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return nil, ErrTenantScope
|
||||
}
|
||||
id := uuid.NewString()
|
||||
mod := &Module{
|
||||
ID: id,
|
||||
TenantID: tenantID,
|
||||
Type: in.Type,
|
||||
Name: strings.TrimSpace(in.Name),
|
||||
Enabled: in.Enabled,
|
||||
Priority: in.Priority,
|
||||
RefreshIntervalSec: in.RefreshIntervalSec,
|
||||
CronExpr: in.CronExpr,
|
||||
DefaultCommunityID: in.DefaultCommunityID,
|
||||
DohProfileID: in.DohProfileID,
|
||||
}
|
||||
m.modules[id] = mod
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateModule(tenantID, moduleID string, patch *ModulePatch) (*Module, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, ok := m.modules[moduleID]
|
||||
if !ok || mod.DeletedAt != nil || mod.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Name != nil {
|
||||
mod.Name = strings.TrimSpace(*patch.Name)
|
||||
}
|
||||
if patch.Enabled != nil {
|
||||
mod.Enabled = *patch.Enabled
|
||||
}
|
||||
if patch.Priority != nil {
|
||||
mod.Priority = *patch.Priority
|
||||
}
|
||||
if patch.RefreshIntervalSec != nil {
|
||||
mod.RefreshIntervalSec = *patch.RefreshIntervalSec
|
||||
}
|
||||
if patch.CronExpr != nil {
|
||||
mod.CronExpr = *patch.CronExpr
|
||||
}
|
||||
if patch.DefaultCommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.DefaultCommunityID)
|
||||
if v == "" {
|
||||
mod.DefaultCommunityID = nil
|
||||
} else {
|
||||
mod.DefaultCommunityID = &v
|
||||
}
|
||||
}
|
||||
if patch.DohProfileID != nil {
|
||||
v := strings.TrimSpace(*patch.DohProfileID)
|
||||
if v == "" {
|
||||
mod.DohProfileID = nil
|
||||
} else {
|
||||
mod.DohProfileID = &v
|
||||
}
|
||||
}
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func (m *Memory) SoftDeleteModule(tenantID, moduleID string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, ok := m.modules[moduleID]
|
||||
if !ok || mod.TenantID != tenantID {
|
||||
return ErrNotFound
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
mod.DeletedAt = &now
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) moduleWriteOK(tenantID, moduleID string) (*Module, error) {
|
||||
mod, ok := m.modules[moduleID]
|
||||
if !ok || mod.DeletedAt != nil || mod.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListCDNSources(tenantID, moduleID string) ([]*CDNSource, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "CDN_CIDRS" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
var out []*CDNSource
|
||||
for _, s := range m.cdnSources {
|
||||
if s.ModuleID == moduleID {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateCDNSource(tenantID, moduleID string, in *CDNSource) (*CDNSource, error) {
|
||||
if in == nil || strings.TrimSpace(in.URL) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "CDN_CIDRS" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
id := uuid.NewString()
|
||||
s := &CDNSource{
|
||||
ID: id,
|
||||
ModuleID: moduleID,
|
||||
SourceKind: in.SourceKind,
|
||||
URL: strings.TrimSpace(in.URL),
|
||||
Etag: in.Etag,
|
||||
RefreshIntervalSec: in.RefreshIntervalSec,
|
||||
CommunityID: in.CommunityID,
|
||||
}
|
||||
m.cdnSources[id] = s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateCDNSource(tenantID, moduleID, sourceID string, patch *CDNSourcePatch) (*CDNSource, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, ok := m.cdnSources[sourceID]
|
||||
if !ok || s.ModuleID != moduleID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.SourceKind != nil {
|
||||
s.SourceKind = *patch.SourceKind
|
||||
}
|
||||
if patch.URL != nil {
|
||||
s.URL = strings.TrimSpace(*patch.URL)
|
||||
}
|
||||
if patch.Etag != nil {
|
||||
s.Etag = *patch.Etag
|
||||
}
|
||||
if patch.RefreshIntervalSec != nil {
|
||||
s.RefreshIntervalSec = patch.RefreshIntervalSec
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
s.CommunityID = nil
|
||||
} else {
|
||||
s.CommunityID = &v
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteCDNSource(tenantID, moduleID, sourceID string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
s, ok := m.cdnSources[sourceID]
|
||||
if !ok || s.ModuleID != moduleID {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(m.cdnSources, sourceID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListASEntries(tenantID, moduleID string) ([]*ASEntry, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "AS_PREFIXES" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
var out []*ASEntry
|
||||
for _, e := range m.asEntries {
|
||||
if e.ModuleID == moduleID {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateASEntry(tenantID, moduleID string, in *ASEntry) (*ASEntry, error) {
|
||||
if in == nil || (in.ASN == nil && (in.Prefix == nil || strings.TrimSpace(*in.Prefix) == "")) {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "AS_PREFIXES" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
id := uuid.NewString()
|
||||
e := &ASEntry{ID: id, ModuleID: moduleID, ASN: in.ASN, Prefix: in.Prefix, CommunityID: in.CommunityID}
|
||||
m.asEntries[id] = e
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateASEntry(tenantID, moduleID, entryID string, patch *ASEntryPatch) (*ASEntry, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, ok := m.asEntries[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.ASN != nil {
|
||||
e.ASN = patch.ASN
|
||||
}
|
||||
if patch.Prefix != nil {
|
||||
p := strings.TrimSpace(*patch.Prefix)
|
||||
if p == "" {
|
||||
e.Prefix = nil
|
||||
} else {
|
||||
e.Prefix = &p
|
||||
}
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
e.CommunityID = nil
|
||||
} else {
|
||||
e.CommunityID = &v
|
||||
}
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteASEntry(tenantID, moduleID, entryID string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
e, ok := m.asEntries[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(m.asEntries, entryID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListDomainEntries(tenantID, moduleID string) ([]*DomainEntry, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "DOMAINS" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
var out []*DomainEntry
|
||||
for _, e := range m.domainEnt {
|
||||
if e.ModuleID == moduleID {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateDomainEntry(tenantID, moduleID string, in *DomainEntry) (*DomainEntry, error) {
|
||||
if in == nil || strings.TrimSpace(in.FQDN) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "DOMAINS" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
id := uuid.NewString()
|
||||
e := &DomainEntry{ID: id, ModuleID: moduleID, FQDN: strings.TrimSpace(in.FQDN), CommunityID: in.CommunityID}
|
||||
m.domainEnt[id] = e
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateDomainEntry(tenantID, moduleID, entryID string, patch *DomainEntryPatch) (*DomainEntry, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, ok := m.domainEnt[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.FQDN != nil {
|
||||
e.FQDN = strings.TrimSpace(*patch.FQDN)
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
e.CommunityID = nil
|
||||
} else {
|
||||
e.CommunityID = &v
|
||||
}
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteDomainEntry(tenantID, moduleID, entryID string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
e, ok := m.domainEnt[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(m.domainEnt, entryID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListIPRangeEntries(tenantID, moduleID string) ([]*IPRangeEntry, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "IP_RANGES" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
var out []*IPRangeEntry
|
||||
for _, e := range m.ipRanges {
|
||||
if e.ModuleID == moduleID {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateIPRangeEntry(tenantID, moduleID string, in *IPRangeEntry) (*IPRangeEntry, error) {
|
||||
if in == nil || strings.TrimSpace(in.Prefix) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
mod, err := m.moduleWriteOK(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "IP_RANGES" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
id := uuid.NewString()
|
||||
e := &IPRangeEntry{ID: id, ModuleID: moduleID, Prefix: strings.TrimSpace(in.Prefix), CommunityID: in.CommunityID}
|
||||
m.ipRanges[id] = e
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateIPRangeEntry(tenantID, moduleID, entryID string, patch *IPRangePatch) (*IPRangeEntry, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e, ok := m.ipRanges[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Prefix != nil {
|
||||
e.Prefix = strings.TrimSpace(*patch.Prefix)
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
e.CommunityID = nil
|
||||
} else {
|
||||
e.CommunityID = &v
|
||||
}
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteIPRangeEntry(tenantID, moduleID, entryID string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, err := m.moduleWriteOK(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
e, ok := m.ipRanges[entryID]
|
||||
if !ok || e.ModuleID != moduleID {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(m.ipRanges, entryID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListDohProfiles(tenantID string) ([]*DohProfile, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var out []*DohProfile
|
||||
for _, p := range m.dohProfiles {
|
||||
if p.TenantID == tenantID {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) GetDohProfile(tenantID, id string) (*DohProfile, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
p, ok := m.dohProfiles[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateDohProfile(tenantID string, in *DohProfile) (*DohProfile, error) {
|
||||
if in == nil || strings.TrimSpace(in.URL) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return nil, ErrTenantScope
|
||||
}
|
||||
id := uuid.NewString()
|
||||
p := &DohProfile{
|
||||
ID: id,
|
||||
TenantID: tenantID,
|
||||
Name: in.Name,
|
||||
URL: strings.TrimSpace(in.URL),
|
||||
TimeoutMs: in.TimeoutMs,
|
||||
SecretRef: in.SecretRef,
|
||||
}
|
||||
m.dohProfiles[id] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateDohProfile(tenantID, id string, patch *DohProfilePatch) (*DohProfile, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
p, ok := m.dohProfiles[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Name != nil {
|
||||
p.Name = *patch.Name
|
||||
}
|
||||
if patch.URL != nil {
|
||||
p.URL = strings.TrimSpace(*patch.URL)
|
||||
}
|
||||
if patch.TimeoutMs != nil {
|
||||
p.TimeoutMs = patch.TimeoutMs
|
||||
}
|
||||
if patch.SecretRef != nil {
|
||||
p.SecretRef = patch.SecretRef
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteDohProfile(tenantID, id string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
p, ok := m.dohProfiles[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return ErrNotFound
|
||||
}
|
||||
for _, mod := range m.modules {
|
||||
if mod.DohProfileID != nil && *mod.DohProfileID == id {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
}
|
||||
delete(m.dohProfiles, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListCommunities(tenantID string) ([]*Community, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var out []*Community
|
||||
for _, c := range m.communities {
|
||||
if c.TenantID == tenantID {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) GetCommunity(tenantID, id string) (*Community, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
c, ok := m.communities[id]
|
||||
if !ok || c.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateCommunity(tenantID string, in *Community) (*Community, error) {
|
||||
if in == nil || strings.TrimSpace(in.Kind) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return nil, ErrTenantScope
|
||||
}
|
||||
id := uuid.NewString()
|
||||
vj := in.ValueJSON
|
||||
if strings.TrimSpace(vj) == "" {
|
||||
vj = "{}"
|
||||
}
|
||||
c := &Community{ID: id, TenantID: tenantID, Name: in.Name, Kind: strings.TrimSpace(in.Kind), ValueJSON: vj}
|
||||
m.communities[id] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateCommunity(tenantID, id string, patch *CommunityPatch) (*Community, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c, ok := m.communities[id]
|
||||
if !ok || c.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Name != nil {
|
||||
c.Name = *patch.Name
|
||||
}
|
||||
if patch.Kind != nil {
|
||||
c.Kind = strings.TrimSpace(*patch.Kind)
|
||||
}
|
||||
if patch.ValueJSON != nil {
|
||||
c.ValueJSON = *patch.ValueJSON
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeleteCommunity(tenantID, id string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c, ok := m.communities[id]
|
||||
if !ok || c.TenantID != tenantID {
|
||||
return ErrNotFound
|
||||
}
|
||||
_ = c
|
||||
// weak check: modules default_community
|
||||
for _, mod := range m.modules {
|
||||
if mod.DefaultCommunityID != nil && *mod.DefaultCommunityID == id {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
}
|
||||
delete(m.communities, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) GetPeer(tenantID, id string) (*BGPPeer, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
p, ok := m.peers[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreatePeer(tenantID string, in *BGPPeer) (*BGPPeer, error) {
|
||||
if in == nil || strings.TrimSpace(in.Neighbor) == "" || in.RemoteASN == 0 {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return nil, ErrTenantScope
|
||||
}
|
||||
id := uuid.NewString()
|
||||
p := &BGPPeer{
|
||||
ID: id, TenantID: tenantID, SpeakerID: in.SpeakerID, Name: in.Name,
|
||||
Neighbor: strings.TrimSpace(in.Neighbor), RemoteASN: in.RemoteASN, Enabled: in.Enabled,
|
||||
SessionState: in.SessionState, PoliciesJSON: in.PoliciesJSON,
|
||||
}
|
||||
if !p.Enabled && p.SessionState == "" {
|
||||
p.Enabled = true
|
||||
}
|
||||
m.peers[id] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdatePeer(tenantID, id string, patch *PeerPatch) (*BGPPeer, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
p, ok := m.peers[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Neighbor != nil {
|
||||
p.Neighbor = strings.TrimSpace(*patch.Neighbor)
|
||||
}
|
||||
if patch.RemoteASN != nil {
|
||||
p.RemoteASN = *patch.RemoteASN
|
||||
}
|
||||
if patch.SpeakerID != nil {
|
||||
v := strings.TrimSpace(*patch.SpeakerID)
|
||||
if v == "" {
|
||||
p.SpeakerID = nil
|
||||
} else {
|
||||
p.SpeakerID = &v
|
||||
}
|
||||
}
|
||||
if patch.Enabled != nil {
|
||||
p.Enabled = *patch.Enabled
|
||||
}
|
||||
if patch.Name != nil {
|
||||
p.Name = *patch.Name
|
||||
}
|
||||
if patch.SessionState != nil {
|
||||
p.SessionState = *patch.SessionState
|
||||
}
|
||||
if patch.PoliciesJSON != nil {
|
||||
p.PoliciesJSON = *patch.PoliciesJSON
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Memory) DeletePeer(tenantID, id string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
p, ok := m.peers[id]
|
||||
if !ok || p.TenantID != tenantID {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(m.peers, id)
|
||||
_ = p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) CreateSpeaker(tenantID string, in *Speaker) (*Speaker, error) {
|
||||
if in == nil || strings.TrimSpace(in.Role) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return nil, ErrTenantScope
|
||||
}
|
||||
id := uuid.NewString()
|
||||
sp := &Speaker{ID: id, TenantID: tenantID, Role: strings.TrimSpace(in.Role), Endpoint: in.Endpoint, MetaJSON: in.MetaJSON}
|
||||
m.speakers[id] = sp
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (m *Memory) UpdateSpeaker(tenantID, id string, patch *SpeakerPatch) (*Speaker, error) {
|
||||
if patch == nil {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
sp, ok := m.speakers[id]
|
||||
if !ok || sp.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Role != nil {
|
||||
sp.Role = strings.TrimSpace(*patch.Role)
|
||||
}
|
||||
if patch.Endpoint != nil {
|
||||
sp.Endpoint = *patch.Endpoint
|
||||
}
|
||||
if patch.MetaJSON != nil {
|
||||
sp.MetaJSON = *patch.MetaJSON
|
||||
}
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (m *Memory) ListRevisionPrefixes(tenantID, revisionID string, cursor string, limit int) ([]PrefixRow, string, bool) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if _, err := m.getRevisionLocked(tenantID, revisionID); err != nil {
|
||||
return nil, "", false
|
||||
}
|
||||
all := m.revPrefixes[revisionID]
|
||||
off := 0
|
||||
if cursor != "" {
|
||||
if n, err := strconv.Atoi(cursor); err == nil && n >= 0 {
|
||||
off = n
|
||||
}
|
||||
}
|
||||
end := off + limit
|
||||
next := ""
|
||||
more := false
|
||||
if end > len(all) {
|
||||
end = len(all)
|
||||
} else {
|
||||
more = true
|
||||
next = fmt.Sprintf("%d", end)
|
||||
}
|
||||
if off >= len(all) {
|
||||
return nil, "", false
|
||||
}
|
||||
return all[off:end], next, more
|
||||
}
|
||||
|
||||
func (m *Memory) ListGlobalSettings(tenantID string) (map[string]any, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.settings[tenantID] == nil {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
out := make(map[string]any, len(m.settings[tenantID]))
|
||||
for k, v := range m.settings[tenantID] {
|
||||
out[k] = v
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Memory) PatchGlobalSettings(tenantID string, patch map[string]any) error {
|
||||
if patch == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.tenants[tenantID]; !ok {
|
||||
return ErrTenantScope
|
||||
}
|
||||
if m.settings[tenantID] == nil {
|
||||
m.settings[tenantID] = make(map[string]any)
|
||||
}
|
||||
for key, val := range patch {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
continue
|
||||
}
|
||||
if val == nil {
|
||||
delete(m.settings[tenantID], key)
|
||||
continue
|
||||
}
|
||||
m.settings[tenantID][key] = val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user