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) src := strings.TrimSpace(meta.BirdBgpSourceIPv4) if src == "" { src = strings.TrimSpace(meta.NodeIPv4) } if src != "" { loc.routerID = src loc.localV4 = src } 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 { frags = map[string]string{} } 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 }