feat(remote-speakers): enhance remote speaker management and API integration
CI / changes (push) Successful in 8s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Failing after 34s
CI / go (push) Failing after 19s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped

- Added support for remote speaker configuration in the README and documentation.
- Implemented a new endpoint for retrieving the bundle signing public key.
- Updated the `evobgp-agent` to include a `serve` command for Panel→Node sync API.
- Enhanced CI workflow to validate remote speaker compose files.
- Introduced new fields in the API and UI for managing speaker metadata, including dispatch status and sync status.
- Improved error handling and response formatting in speaker-related API endpoints.
- Updated documentation to reflect changes in remote speaker functionality and usage guidelines.
This commit is contained in:
Denozordec
2026-05-21 12:42:06 +07:00
parent ec65249bf1
commit 2aecbf96fd
29 changed files with 2003 additions and 63 deletions
+67
View File
@@ -0,0 +1,67 @@
package pipeline
import (
"fmt"
"strings"
"evobgp/internal/birdfmt"
"evobgp/internal/store"
)
// BirdLocalsForSpeaker merges tenant settings with per-speaker meta_json overrides.
func BirdLocalsForSpeaker(st store.Backend, tenantID, speakerID string) birdLocals {
loc := birdLocalsFromStore(st, tenantID)
if st == nil || strings.TrimSpace(speakerID) == "" {
return loc
}
sp, err := st.GetSpeaker(tenantID, speakerID)
if err != nil || sp == nil {
return loc
}
meta := store.ParseSpeakerMeta(sp.MetaJSON)
if s := strings.TrimSpace(meta.BirdBgpSourceIPv4); s != "" {
loc.routerID = s
loc.localV4 = s
}
if s := strings.TrimSpace(meta.BirdBgpSourceIPv6); s != "" {
loc.localV6 = s
}
return loc
}
// OverlayFragmentsForSpeaker re-renders bird.conf and peers fragment with speaker-specific BIRD locals.
func OverlayFragmentsForSpeaker(st store.Backend, tenantID, speakerID, revisionID string, frags map[string]string) (map[string]string, error) {
if frags == nil {
return nil, fmt.Errorf("pipeline: overlay: nil fragments")
}
locals := BirdLocalsForSpeaker(st, tenantID, speakerID)
out := make(map[string]string, len(frags))
for k, v := range frags {
out[k] = v
}
moduleHint := "aggregate"
if main := frags["bird.conf"]; main != "" {
if idx := strings.Index(main, "trigger module "); idx >= 0 {
rest := main[idx+len("trigger module "):]
if end := strings.Index(rest, ")"); end > 0 {
moduleHint = strings.TrimSpace(rest[:end])
}
}
}
main, err := birdfmt.RenderMainBirdConf(birdfmt.MainBirdConfOptions{
RouterID: locals.routerID,
Includes: birdfmt.StandardIncludeFragments(),
Preamble: fmt.Sprintf("EvoBGP tenant aggregate config (trigger module %s) revision %s speaker %s", moduleHint, revisionID, speakerID),
})
if err != nil {
return nil, err
}
out["bird.conf"] = main
peersBody, err := renderPeersBirdFragment(st, tenantID, locals)
if err != nil {
return nil, err
}
pPeers := birdfmt.FragmentIncludePath(birdfmt.FragmentPeers)
out[pPeers] = peersBody
return out, nil
}
+42
View File
@@ -0,0 +1,42 @@
package pipeline_test
import (
"strings"
"testing"
"evobgp/internal/pipeline"
"evobgp/internal/store"
)
func TestOverlayFragmentsForSpeaker_differentRouterID(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, _, _, _ := m.DemoIDs()
sp1, _ := m.CreateSpeaker(tenant, &store.Speaker{
Role: "replica",
Endpoint: "https://203.0.113.1",
MetaJSON: `{"bird_bgp_source_ipv4":"203.0.113.1"}`,
})
sp2, _ := m.CreateSpeaker(tenant, &store.Speaker{
Role: "replica",
Endpoint: "https://203.0.113.2",
MetaJSON: `{"bird_bgp_source_ipv4":"203.0.113.2"}`,
})
base := map[string]string{
"bird.conf": "router id 192.0.2.1;\n# EvoBGP tenant aggregate config (trigger module mod) revision rev1",
}
out1, err := pipeline.OverlayFragmentsForSpeaker(m, tenant, sp1.ID, "rev1", base)
if err != nil {
t.Fatal(err)
}
out2, err := pipeline.OverlayFragmentsForSpeaker(m, tenant, sp2.ID, "rev1", base)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out1["bird.conf"], "203.0.113.1") {
t.Fatalf("sp1 router: %s", out1["bird.conf"])
}
if !strings.Contains(out2["bird.conf"], "203.0.113.2") {
t.Fatalf("sp2 router: %s", out2["bird.conf"])
}
}