Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
191 lines
5.4 KiB
Go
191 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"evobgp/internal/agentserver"
|
|
"evobgp/internal/agentwatch"
|
|
"evobgp/internal/birdfmt"
|
|
)
|
|
|
|
func main() {
|
|
bird := flag.String("bird", "", "path to bird binary (default: bird from PATH)")
|
|
birdc := flag.String("birdc", "", "path to birdc binary (default: birdc from PATH)")
|
|
socket := flag.String("socket", "", "optional birdc control socket (-s)")
|
|
timeout := flag.Duration("timeout", 30*time.Second, "timeout for bird/birdc")
|
|
watchEvery := flag.Duration("watch-interval", 30*time.Second, "for watch: interval between configuration checks")
|
|
listen := flag.String("listen", "", "for serve: listen address (default :8443 or EVOBGP_AGENT_LISTEN)")
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <command>\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, "Commands:\n")
|
|
fmt.Fprintf(os.Stderr, " parse-check <path/to/bird.conf> run bird -c <path> -p (syntax check)\n")
|
|
fmt.Fprintf(os.Stderr, " configure run birdc configure (reload running BIRD)\n")
|
|
fmt.Fprintf(os.Stderr, " watch reload changed BIRD configuration and periodically reconcile\n")
|
|
fmt.Fprintf(os.Stderr, " serve Panel→Node HTTP API (POST /v1/agent/sync)\n")
|
|
flag.PrintDefaults()
|
|
}
|
|
args, err := parseAgentArgs(flag.CommandLine, os.Args[1:])
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(2)
|
|
}
|
|
if len(args) < 1 {
|
|
flag.Usage()
|
|
os.Exit(2)
|
|
}
|
|
|
|
ctl := &birdfmt.BirdCtl{Socket: *socket}
|
|
if *bird != "" {
|
|
ctl.Bird = *bird
|
|
}
|
|
if *birdc != "" {
|
|
ctl.Birdc = *birdc
|
|
}
|
|
|
|
switch args[0] {
|
|
case "serve":
|
|
runServe(*listen, *timeout)
|
|
case "watch":
|
|
runWatch(ctl, *watchEvery, *timeout)
|
|
case "parse-check", "configure":
|
|
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
|
defer cancel()
|
|
runBirdCommand(ctx, args, ctl)
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
|
|
flag.Usage()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
// parseAgentArgs accepts flags before or immediately after the subcommand.
|
|
// A second parse preserves previously supplied flag values and rejects unknown
|
|
// flags instead of silently passing them to watch or serve as positional args.
|
|
func parseAgentArgs(fs *flag.FlagSet, argv []string) ([]string, error) {
|
|
if err := fs.Parse(argv); err != nil {
|
|
return nil, err
|
|
}
|
|
args := fs.Args()
|
|
if len(args) == 0 {
|
|
return args, nil
|
|
}
|
|
command := args[0]
|
|
globalStop := false
|
|
for i, consumed := 0, len(argv)-len(args); i < consumed; i++ {
|
|
if argv[i] == "--" {
|
|
globalStop = true
|
|
break
|
|
}
|
|
name, _, hasValue := strings.Cut(strings.TrimLeft(argv[i], "-"), "=")
|
|
if !hasValue {
|
|
if f := fs.Lookup(name); f != nil {
|
|
boolean, ok := f.Value.(interface{ IsBoolFlag() bool })
|
|
if !ok || !boolean.IsBoolFlag() {
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !globalStop {
|
|
if err := fs.Parse(args[1:]); err != nil {
|
|
return nil, err
|
|
}
|
|
args = append([]string{command}, fs.Args()...)
|
|
}
|
|
if (command == "watch" || command == "serve" || command == "configure") && len(args) != 1 {
|
|
return nil, fmt.Errorf("%s takes no positional arguments", command)
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func runBirdCommand(ctx context.Context, args []string, ctl *birdfmt.BirdCtl) {
|
|
switch args[0] {
|
|
case "parse-check":
|
|
if len(args) != 2 {
|
|
fmt.Fprintln(os.Stderr, "parse-check requires exactly one argument: path to bird.conf")
|
|
os.Exit(2)
|
|
}
|
|
if err := ctl.ParseCheck(ctx, args[1]); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
case "configure":
|
|
if len(args) != 1 {
|
|
fmt.Fprintln(os.Stderr, "configure takes no extra arguments")
|
|
os.Exit(2)
|
|
}
|
|
if err := ctl.Configure(ctx); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func runWatch(ctl *birdfmt.BirdCtl, every, timeout time.Duration) {
|
|
if every <= 0 || timeout <= 0 {
|
|
fmt.Fprintln(os.Stderr, "watch-interval and timeout must be > 0")
|
|
os.Exit(2)
|
|
}
|
|
reconcile := 5 * time.Minute
|
|
if raw := os.Getenv("EVOBGP_AGENT_WATCH_RECONCILE_INTERVAL"); raw != "" {
|
|
var err error
|
|
reconcile, err = time.ParseDuration(raw)
|
|
if err != nil || reconcile <= 0 {
|
|
fmt.Fprintln(os.Stderr, "EVOBGP_AGENT_WATCH_RECONCILE_INTERVAL must be a positive duration")
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
active := os.Getenv("EVOBGP_BIRD_ACTIVE_DIR")
|
|
if active == "" {
|
|
active = "/etc/bird"
|
|
}
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
log.Printf("evobgp-agent watch: checking %s every %s, forced reconciliation every %s (socket=%q)", active, every, reconcile, ctl.Socket)
|
|
if err := agentwatch.Run(ctx, ctl, agentwatch.Config{ActiveDir: active, Every: every, ReconcileEvery: reconcile, CommandTimeout: timeout}); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runServe(listenFlag string, syncTimeout time.Duration) {
|
|
cfg, err := agentserver.ConfigFromEnv()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(2)
|
|
}
|
|
if listenFlag != "" {
|
|
cfg.Listen = listenFlag
|
|
}
|
|
if syncTimeout > 0 {
|
|
cfg.SyncTimeout = syncTimeout
|
|
}
|
|
var mu sync.Mutex
|
|
var lastRev string
|
|
var lastAt time.Time
|
|
cfg.LastSync = func() (string, time.Time) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return lastRev, lastAt
|
|
}
|
|
cfg.OnSyncSuccess = func(rev string) {
|
|
mu.Lock()
|
|
lastRev = rev
|
|
lastAt = time.Now().UTC()
|
|
mu.Unlock()
|
|
}
|
|
if err := agentserver.ListenAndServe(cfg); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|