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
+48 -1
View File
@@ -13,6 +13,7 @@ import (
"evobgp/internal/birddeploy"
"evobgp/internal/birdfmt"
"evobgp/internal/nodedispatch"
"evobgp/internal/observability"
"evobgp/internal/pipeline"
"evobgp/internal/store"
@@ -408,6 +409,7 @@ func (w *Worker) runDeployApply(j *Job) {
}
}
applied := make([]string, 0, 8)
var dispatchResults []nodedispatch.Result
applyOne := func(speakerID string) error {
if err := w.Store.SetLastAppliedRevision(j.TenantID, speakerID, revID); err != nil {
return err
@@ -419,21 +421,60 @@ func (w *Worker) runDeployApply(j *Job) {
applied = append(applied, speakerID)
return nil
}
dispatchSpeaker := func(sp *store.Speaker) {
if !nodedispatch.Enabled() || sp == nil {
return
}
meta := store.ParseSpeakerMeta(sp.MetaJSON)
if !store.SpeakerNeedsRemoteDispatch(sp.Role, meta) {
return
}
ctx2, cancel := context.WithTimeout(ctx, 35*time.Second)
defer cancel()
res := nodedispatch.WakeSpeaker(ctx2, sp, nodedispatch.Options{RevisionID: revID})
dispatchResults = append(dispatchResults, res)
patch := store.SpeakerMeta{
LastDispatchAt: time.Now().UTC().Format(time.RFC3339Nano),
LastDispatchStatus: res.Status,
}
if res.Error != "" {
patch.LastDispatchError = res.Error
patch.SyncStatus = "error"
} else if res.Status == "ok" {
patch.LastDispatchError = ""
patch.SyncStatus = "synced"
}
merged := store.MergeSpeakerMetaJSON(sp.MetaJSON, patch)
_, _ = w.Store.UpdateSpeaker(j.TenantID, sp.ID, &store.SpeakerPatch{MetaJSON: &merged})
}
if hasSpeaker && spk != "" {
if err := applyOne(spk); err != nil {
j.Fail(err.Error())
return
}
if sp, err := w.Store.GetSpeaker(j.TenantID, spk); err == nil {
dispatchSpeaker(sp)
}
if len(dispatchResults) > 0 {
j.mergeMeta(map[string]any{"node_dispatch": map[string]any{
"revision_id": revID,
"results": dispatchResults,
}})
}
mergeBirdPostApplyMeta(j)
j.Succeed()
return
}
for _, sp := range w.Store.ListSpeakersForTenant(j.TenantID) {
speakers := w.Store.ListSpeakersForTenant(j.TenantID)
for _, sp := range speakers {
if err := applyOne(sp.ID); err != nil {
j.Fail(err.Error())
return
}
}
for _, sp := range speakers {
dispatchSpeaker(sp)
}
j.mergeMeta(map[string]any{
"apply_summary": map[string]any{
"revision_id": revID,
@@ -442,6 +483,12 @@ func (w *Worker) runDeployApply(j *Job) {
"message": fmt.Sprintf("Ревизия %s применена на %d спикерах", shortID(revID), len(applied)),
},
})
if len(dispatchResults) > 0 {
j.mergeMeta(map[string]any{"node_dispatch": map[string]any{
"revision_id": revID,
"results": dispatchResults,
}})
}
mergeBirdPostApplyMeta(j)
j.Succeed()
}