Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
126 lines
3.5 KiB
Go
126 lines
3.5 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func (s *Server) registerPeerDiscoveryRoutes(m *http.ServeMux) {
|
|
m.HandleFunc("GET /peers/discovered", s.handleListPeerDiscoveries)
|
|
m.HandleFunc("POST /peers/discovered/{id}/approve", s.handleApprovePeerDiscovery)
|
|
m.HandleFunc("POST /peers/discovered/{id}/reject", s.handleRejectPeerDiscovery)
|
|
}
|
|
|
|
func peerDiscoveryJSON(d *store.BGPPeerDiscovery) map[string]any {
|
|
if d == nil {
|
|
return nil
|
|
}
|
|
m := map[string]any{
|
|
"id": d.ID,
|
|
"neighbor_id": d.NeighborID,
|
|
"neighbor": d.Neighbor,
|
|
"remote_asn": d.RemoteASN,
|
|
"protocol_name": d.ProtocolName,
|
|
"session_state": d.SessionState,
|
|
"status": d.Status,
|
|
"first_seen_at": d.FirstSeenAt.UTC().Format(time.RFC3339Nano),
|
|
"last_seen_at": d.LastSeenAt.UTC().Format(time.RFC3339Nano),
|
|
}
|
|
if d.SpeakerID != "" {
|
|
m["speaker_id"] = d.SpeakerID
|
|
} else {
|
|
m["speaker_id"] = nil
|
|
}
|
|
if d.ApprovedPeerID != "" {
|
|
m["approved_peer_id"] = d.ApprovedPeerID
|
|
} else {
|
|
m["approved_peer_id"] = nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (s *Server) handleListPeerDiscoveries(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := authFromContext(r.Context())
|
|
if !ok {
|
|
writeProblem(w, http.StatusUnauthorized, "Unauthorized", "missing auth")
|
|
return
|
|
}
|
|
if !s.requirePerm(w, a, "bgp:network:read") {
|
|
return
|
|
}
|
|
status := strings.TrimSpace(r.URL.Query().Get("status"))
|
|
if status == "" {
|
|
status = store.PeerDiscoveryPending
|
|
}
|
|
listStatus := status
|
|
if status == "all" {
|
|
listStatus = ""
|
|
}
|
|
items, err := s.store.ListPeerDiscoveries(a.TenantID, listStatus)
|
|
if err != nil {
|
|
writeStoreErr(w, err)
|
|
return
|
|
}
|
|
out := make([]map[string]any, 0, len(items))
|
|
for _, d := range items {
|
|
out = append(out, peerDiscoveryJSON(d))
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"items": out})
|
|
}
|
|
|
|
func (s *Server) handleApprovePeerDiscovery(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := authFromContext(r.Context())
|
|
if !ok || !s.requirePerm(w, a, "bgp:network:write") {
|
|
return
|
|
}
|
|
var body struct {
|
|
Name string `json:"name"`
|
|
BGPSpeakerID *string `json:"bgp_speaker_id"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
peer, disc, err := s.store.ApprovePeerDiscovery(a.TenantID, r.PathValue("id"), &store.PeerDiscoveryApproveInput{
|
|
Name: body.Name,
|
|
SpeakerID: body.BGPSpeakerID,
|
|
Enabled: body.Enabled,
|
|
})
|
|
if err != nil {
|
|
writeStoreErr(w, err)
|
|
return
|
|
}
|
|
s.enqueuePeerReconcile(w, r, a.TenantID, "peer_discovery_approve")
|
|
s.recordCRUDAudit(r, a, "bgp.peer.discovery.approve", "Approved discovered peer "+peer.Neighbor, peer.ID, map[string]any{
|
|
"peer_id": peer.ID,
|
|
"discovery_id": disc.ID,
|
|
"neighbor": peer.Neighbor,
|
|
"neighbor_id": disc.NeighborID,
|
|
"remote_asn": peer.RemoteASN,
|
|
})
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"peer": peerJSON(peer),
|
|
"discovery": peerDiscoveryJSON(disc),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleRejectPeerDiscovery(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := authFromContext(r.Context())
|
|
if !ok || !s.requirePerm(w, a, "bgp:network:write") {
|
|
return
|
|
}
|
|
disc, err := s.store.RejectPeerDiscovery(a.TenantID, r.PathValue("id"))
|
|
if err != nil {
|
|
writeStoreErr(w, err)
|
|
return
|
|
}
|
|
s.recordCRUDAudit(r, a, "bgp.peer.discovery.reject", "Rejected discovered peer "+disc.Neighbor, disc.ID, map[string]any{
|
|
"discovery_id": disc.ID,
|
|
"neighbor": disc.Neighbor,
|
|
"neighbor_id": disc.NeighborID,
|
|
})
|
|
writeJSON(w, http.StatusOK, peerDiscoveryJSON(disc))
|
|
}
|