Files
EvoBGP/internal/pipeline/parse.go
T
Denozordec f6b94a44d0
CI / changes (push) Successful in 8s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 40s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been skipped
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 15s
CI / docker-go-prime (push) Successful in 24s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 59s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 2m18s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m23s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m20s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m24s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m9s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m22s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m24s
refactor: enhance bird metrics polling with context support
Updated the `startBirdMetricsPoller` function to accept a context parameter, allowing for better control over the polling lifecycle. This change was applied in both `evobgp-all` and `evobgp-api` main files. Additionally, modified the `StartBirdProtocolsPoller` function to handle context cancellation, ensuring graceful shutdown of the polling routine. Introduced a new service in the Docker Compose configuration for logging runtime service outputs, improving observability during deployment.
2026-04-08 12:29:07 +07:00

176 lines
3.9 KiB
Go

package pipeline
import (
"bufio"
"encoding/json"
"fmt"
"net/netip"
"strings"
)
// Защита от pathological JSON: глубокая рекурсия при обходе и взрыв числа узлов по пути.
const (
maxJSONWalkDepth = 512
maxJSONPathBreadth = 50000
)
// ParseCIDRLines extracts unique IPv4/IPv6 CIDRs from plain text (one per line, # comments, empty lines skipped).
func ParseCIDRLines(body string) []netip.Prefix {
seen := make(map[string]struct{})
var out []netip.Prefix
sc := bufio.NewScanner(strings.NewReader(body))
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
pfx := parseOneCIDR(line)
if !pfx.IsValid() {
continue
}
m := pfx.Masked()
s := m.String()
if _, ok := seen[s]; ok {
continue
}
seen[s] = struct{}{}
out = append(out, m)
}
return out
}
// ExtractCIDRs parses CIDRs from either plaintext lines or JSON payload.
// For sourceKind="json", prefixPath supports dotted traversal, with [] for arrays:
// e.g. "prefixes[]", "data.items[].cidr".
func ExtractCIDRs(body, sourceKind, prefixPath string) ([]netip.Prefix, error) {
if strings.EqualFold(strings.TrimSpace(sourceKind), "json") {
return parseCIDRsFromJSON(body, prefixPath)
}
return ParseCIDRLines(body), nil
}
func parseCIDRsFromJSON(body, prefixPath string) ([]netip.Prefix, error) {
var root any
if err := json.Unmarshal([]byte(body), &root); err != nil {
return nil, err
}
values, err := jsonValuesAtPath(root, prefixPath)
if err != nil {
return nil, err
}
seen := make(map[string]struct{})
var out []netip.Prefix
for _, raw := range values {
pfx := parseOneCIDR(raw)
if !pfx.IsValid() {
continue
}
m := pfx.Masked()
s := m.String()
if _, ok := seen[s]; ok {
continue
}
seen[s] = struct{}{}
out = append(out, m)
}
return out, nil
}
func jsonValuesAtPath(root any, prefixPath string) ([]string, error) {
path := strings.TrimSpace(prefixPath)
if path == "" {
return flattenJSONStrings(root, 0)
}
parts := strings.Split(path, ".")
nodes := []any{root}
for _, p := range parts {
part := strings.TrimSpace(p)
if part == "" {
continue
}
iter := strings.HasSuffix(part, "[]")
key := strings.TrimSuffix(part, "[]")
var next []any
for _, n := range nodes {
obj, ok := n.(map[string]any)
if !ok {
continue
}
child, ok := obj[key]
if !ok {
continue
}
if iter {
if arr, ok := child.([]any); ok {
next = append(next, arr...)
}
continue
}
next = append(next, child)
}
if len(next) > maxJSONPathBreadth {
return nil, fmt.Errorf("json path: слишком много узлов на шаге (>%d)", maxJSONPathBreadth)
}
nodes = next
if len(nodes) == 0 {
return nil, nil
}
}
var out []string
for _, n := range nodes {
part, err := flattenJSONStrings(n, 0)
if err != nil {
return nil, err
}
out = append(out, part...)
}
return out, nil
}
func flattenJSONStrings(v any, depth int) ([]string, error) {
if depth > maxJSONWalkDepth {
return nil, fmt.Errorf("json: глубина вложенности превышает %d", maxJSONWalkDepth)
}
switch x := v.(type) {
case string:
return []string{strings.TrimSpace(x)}, nil
case []any:
var out []string
for _, item := range x {
part, err := flattenJSONStrings(item, depth+1)
if err != nil {
return nil, err
}
out = append(out, part...)
}
return out, nil
case map[string]any:
var out []string
for _, item := range x {
part, err := flattenJSONStrings(item, depth+1)
if err != nil {
return nil, err
}
out = append(out, part...)
}
return out, nil
default:
return nil, nil
}
}
func parseOneCIDR(s string) netip.Prefix {
if p, err := netip.ParsePrefix(s); err == nil {
return p
}
if addr, err := netip.ParseAddr(s); err == nil {
if addr.Is4() {
p, _ := addr.Prefix(32)
return p
}
p, _ := addr.Prefix(128)
return p
}
return netip.Prefix{}
}