Files
EvoBGP/internal/pipeline/module_hash.go
T

123 lines
3.6 KiB
Go

package pipeline
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
"evobgp/internal/store"
)
// moduleIngestInputHash fingerprints module config and child entries so snapshots invalidate on CRUD.
func moduleIngestInputHash(st store.Backend, tenantID string, mod *store.Module) (string, error) {
if st == nil || mod == nil {
return "", fmt.Errorf("pipeline: module hash: missing store or module")
}
h := sha256.New()
_, _ = fmt.Fprintf(h, "type=%s\n", strings.TrimSpace(mod.Type))
_, _ = fmt.Fprintf(h, "enabled=%t\n", mod.Enabled)
if mod.DefaultCommunityID != nil {
_, _ = fmt.Fprintf(h, "default_community=%s\n", strings.TrimSpace(*mod.DefaultCommunityID))
}
_, _ = fmt.Fprintf(h, "doh_policy=%s\n", store.NormalizeDohResolverPolicy(mod.DohResolverPolicy))
for _, pid := range mod.EffectiveDohProfileIDs() {
_, _ = fmt.Fprintf(h, "doh_profile=%s\n", pid)
if prof, err := st.GetDohProfile(tenantID, pid); err == nil && prof != nil {
_, _ = fmt.Fprintf(h, "doh_url=%s\n", strings.TrimSpace(prof.URL))
if prof.TimeoutMs != nil {
_, _ = fmt.Fprintf(h, "doh_timeout=%d\n", *prof.TimeoutMs)
}
}
}
switch mod.Type {
case "IP_RANGES":
list, err := st.ListIPRangeEntries(tenantID, mod.ID)
if err != nil {
return "", err
}
sort.Slice(list, func(i, j int) bool { return list[i].Prefix < list[j].Prefix })
for _, e := range list {
comm := ""
if e.CommunityID != nil {
comm = *e.CommunityID
}
_, _ = fmt.Fprintf(h, "ip=%s|c=%s\n", e.Prefix, comm)
}
case "AS_PREFIXES":
list, err := st.ListASEntries(tenantID, mod.ID)
if err != nil {
return "", err
}
sort.Slice(list, func(i, j int) bool { return list[i].ASN < list[j].ASN })
for _, e := range list {
comm := ""
if e.CommunityID != nil {
comm = *e.CommunityID
}
_, _ = fmt.Fprintf(h, "as=%d|c=%s\n", e.ASN, comm)
}
case "CDN_CIDRS":
list, err := st.ListCDNSources(tenantID, mod.ID)
if err != nil {
return "", err
}
sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID })
for _, s := range list {
comm := ""
if s.CommunityID != nil {
comm = *s.CommunityID
}
interval := 0
if s.RefreshIntervalSec != nil {
interval = *s.RefreshIntervalSec
}
_, _ = fmt.Fprintf(h, "cdn=%s|url=%s|kind=%s|path=%s|c=%s|etag=%s|interval=%d\n",
s.ID, strings.TrimSpace(s.URL), s.SourceKind, strings.TrimSpace(s.PrefixPath), comm,
strings.TrimSpace(s.Etag), interval)
}
case "DOMAINS":
list, err := st.ListDomainEntries(tenantID, mod.ID)
if err != nil {
return "", err
}
sort.Slice(list, func(i, j int) bool { return list[i].FQDN < list[j].FQDN })
for _, e := range list {
comm := ""
if e.CommunityID != nil {
comm = *e.CommunityID
}
_, _ = fmt.Fprintf(h, "dom=%s|c=%s\n", strings.TrimSpace(e.FQDN), comm)
}
default:
_, _ = fmt.Fprintf(h, "unknown_type=%s\n", mod.Type)
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func persistModuleSnapshot(st store.Backend, tenantID string, mod *store.Module, rows []store.PrefixRow) error {
if st == nil || mod == nil {
return nil
}
hash, err := moduleIngestInputHash(st, tenantID, mod)
if err != nil {
return err
}
return st.SetModulePrefixSnapshot(tenantID, mod.ID, hash, rows)
}
func moduleRowsFromSnapshot(st store.Backend, tenantID string, mod *store.Module) ([]store.PrefixRow, bool, error) {
hash, err := moduleIngestInputHash(st, tenantID, mod)
if err != nil {
return nil, false, err
}
snap, ok, err := st.GetModulePrefixSnapshot(tenantID, mod.ID)
if err != nil || !ok || snap == nil || snap.InputHash != hash {
return nil, false, err
}
cp := append([]store.PrefixRow(nil), snap.Prefixes...)
return cp, true, nil
}