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.
CI / changes (push) Successful in 5s
CI / go (push) Successful in 1m40s
CI / openapi (push) Has been skipped
CI / bird2 (push) Successful in 17s

This commit is contained in:
Denozordec
2026-04-05 17:03:21 +07:00
parent bf52b21150
commit 6a55f72ab3
35 changed files with 4609 additions and 193 deletions
+81 -16
View File
@@ -32,6 +32,15 @@ type Memory struct {
peers map[string]*BGPPeer
dohProfiles map[string]*DohProfile
communities map[string]*Community
cdnSources map[string]*CDNSource
asEntries map[string]*ASEntry
domainEnt map[string]*DomainEntry
ipRanges map[string]*IPRangeEntry
settings map[string]map[string]any // tenantID -> key -> JSON-compatible value
revPrefixes map[string][]PrefixRow
// DemoIDs valid after SeedDemo()
demoTenantID string
demoModuleCDN string
@@ -59,9 +68,10 @@ type Module struct {
Enabled bool
RefreshIntervalSec int // 0 = unset
CronExpr string // optional cron for scheduler (display / future use)
Priority int
DefaultCommunityID *string
DohProfileID *string
Priority int
DefaultCommunityID *string
DohProfileID *string
DeletedAt *time.Time
}
type Revision struct {
@@ -77,14 +87,17 @@ type Revision struct {
PreviewFragments map[string]string
}
// BGPPeer is a minimal stand-in until SQL-backed bgp_peer is wired into the API.
// BGPPeer maps to bgp_peer (+ display fields in meta).
type BGPPeer struct {
ID string
TenantID string
SpeakerID *string
Name string
Neighbor string
SessionState string // e.g. Established, Idle, Connect (intent or cached ops view)
ID string
TenantID string
SpeakerID *string
Name string
Neighbor string
RemoteASN int64
Enabled bool
SessionState string
PoliciesJSON string
}
type Speaker struct {
@@ -93,6 +106,7 @@ type Speaker struct {
Role string
Endpoint string
LastAppliedRevisionID *string
MetaJSON string
}
func NewMemory() *Memory {
@@ -103,6 +117,14 @@ func NewMemory() *Memory {
speakers: make(map[string]*Speaker),
publishedRevision: make(map[string]publishedInfo),
peers: make(map[string]*BGPPeer),
dohProfiles: make(map[string]*DohProfile),
communities: make(map[string]*Community),
cdnSources: make(map[string]*CDNSource),
asEntries: make(map[string]*ASEntry),
domainEnt: make(map[string]*DomainEntry),
ipRanges: make(map[string]*IPRangeEntry),
settings: make(map[string]map[string]any),
revPrefixes: make(map[string][]PrefixRow),
}
}
@@ -193,6 +215,8 @@ protocol direct {
SpeakerID: &sid,
Name: "demo-upstream-4",
Neighbor: "198.51.100.2",
RemoteASN: 65001,
Enabled: true,
SessionState: "Established",
}
p2 := uuid.NewString()
@@ -202,10 +226,20 @@ protocol direct {
SpeakerID: &sid,
Name: "demo-upstream-6",
Neighbor: "2001:db8::2",
RemoteASN: 65002,
Enabled: true,
SessionState: "Idle",
}
m.demoTenantID, m.demoModuleCDN, m.demoModuleIP, m.demoRevisionID, m.demoSpeakerID = tid, mCDN, mIP, rid, sid
// Demo materialized prefixes for /revisions/{id}/prefixes
cid := uuid.NewString()
m.communities[cid] = &Community{ID: cid, TenantID: tid, Name: "demo-comm", Kind: "large", ValueJSON: "{}"}
m.revPrefixes[rid] = []PrefixRow{
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "demo"},
{Prefix: "2001:db8::/32", CommunityID: &cid, Source: "demo"},
}
}
// MaterializedPrefixStats returns max and sum of MaterializedPrefixCount across revisions.
@@ -256,7 +290,7 @@ func (m *Memory) ListModules(tenantID string) []*Module {
defer m.mu.RUnlock()
var out []*Module
for _, mod := range m.modules {
if mod.TenantID == tenantID {
if mod.TenantID == tenantID && mod.DeletedAt == nil {
out = append(out, mod)
}
}
@@ -287,7 +321,7 @@ func (m *Memory) GetModule(tenantID, moduleID string) (*Module, error) {
m.mu.RLock()
defer m.mu.RUnlock()
mod, ok := m.modules[moduleID]
if !ok {
if !ok || mod.DeletedAt != nil {
return nil, ErrNotFound
}
if mod.TenantID != tenantID {
@@ -388,6 +422,11 @@ func (m *Memory) CreateRollbackRevision(tenantID, sourceRevisionID string) (newI
MaterializedPrefixCount: src.MaterializedPrefixCount,
PreviewFragments: frag,
}
if px, ok := m.revPrefixes[sourceRevisionID]; ok {
cp := make([]PrefixRow, len(px))
copy(cp, px)
m.revPrefixes[newID] = cp
}
return newID, nil
}
@@ -423,8 +462,10 @@ func (m *Memory) PublishRevisionForSpeaker(speakerID, revisionID string) error {
return nil
}
// RevisionDiff returns a trivial JSON-friendly diff between prefix sets (empty for memory demo).
// RevisionDiff returns prefix set diff from materialized snapshots when present.
func (m *Memory) RevisionDiff(tenantID, aID, bID string) (map[string]any, error) {
m.mu.RLock()
defer m.mu.RUnlock()
_, err := m.getRevisionLocked(tenantID, aID)
if err != nil {
return nil, err
@@ -433,13 +474,37 @@ func (m *Memory) RevisionDiff(tenantID, aID, bID string) (map[string]any, error)
if err != nil {
return nil, err
}
setA := make(map[string]struct{})
setB := make(map[string]struct{})
for _, p := range m.revPrefixes[aID] {
setA[p.Prefix] = struct{}{}
}
for _, p := range m.revPrefixes[bID] {
setB[p.Prefix] = struct{}{}
}
var added, removed []string
unchanged := 0
for p := range setB {
if _, ok := setA[p]; !ok {
added = append(added, p)
} else {
unchanged++
}
}
for p := range setA {
if _, ok := setB[p]; !ok {
removed = append(removed, p)
}
}
sort.Strings(added)
sort.Strings(removed)
return map[string]any{
"revision_a": aID,
"revision_b": bID,
"prefixes": map[string]any{
"added": []string{},
"removed": []string{},
"unchanged_count": 0,
"added": added,
"removed": removed,
"unchanged_count": unchanged,
},
}, nil
}