feat: enhance EvoBGP with new command-line options for the evobgp-agent, including a watch command for periodic configuration updates. Update go.mod with additional dependencies and improve Docker Compose setup for new services, including NATS and various worker components.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
// Package birddeploy applies revision preview fragments to a shared BIRD volume with parse-check and LKG (plan §13).
|
||||
package birddeploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"evobgp/internal/birdfmt"
|
||||
"evobgp/internal/store"
|
||||
)
|
||||
|
||||
// Config drives two-phase deploy from a revision in the store.
|
||||
type Config struct {
|
||||
ActiveDir string // e.g. /etc/bird (live config tree)
|
||||
StagingDir string // e.g. /tmp/evobgp-staging (same host/volume as ActiveDir)
|
||||
BirdBin string
|
||||
BirdcBin string
|
||||
Socket string // birdc -s
|
||||
}
|
||||
|
||||
// ApplyRevision writes preview fragments to staging, runs bird -p, swaps into active, runs birdc configure; restores LKG on failure.
|
||||
func ApplyRevision(ctx context.Context, ctl *birdfmt.BirdCtl, rev *store.Revision, cfg Config) error {
|
||||
if rev == nil || len(rev.PreviewFragments) == 0 {
|
||||
return fmt.Errorf("birddeploy: no preview fragments")
|
||||
}
|
||||
active := strings.TrimSpace(cfg.ActiveDir)
|
||||
staging := strings.TrimSpace(cfg.StagingDir)
|
||||
if active == "" || staging == "" {
|
||||
return fmt.Errorf("birddeploy: ActiveDir and StagingDir required")
|
||||
}
|
||||
if err := os.MkdirAll(staging, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(active, "bird.d"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
lkg := filepath.Join(active, ".evobgp_lkg")
|
||||
|
||||
// Phase 1: write staging tree
|
||||
for rel, content := range rev.PreviewFragments {
|
||||
rel = strings.TrimPrefix(rel, "/")
|
||||
dst := filepath.Join(staging, rel)
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(dst, []byte(content), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mainConf := filepath.Join(staging, "bird.conf")
|
||||
if _, err := os.Stat(mainConf); err != nil {
|
||||
return fmt.Errorf("birddeploy: staging missing bird.conf: %w", err)
|
||||
}
|
||||
bc := ctl
|
||||
if bc == nil {
|
||||
bc = &birdfmt.BirdCtl{Bird: cfg.BirdBin, Birdc: cfg.BirdcBin, Socket: cfg.Socket}
|
||||
}
|
||||
if err := bc.ParseCheck(ctx, mainConf); err != nil {
|
||||
return fmt.Errorf("birddeploy: bird -p failed: %w", err)
|
||||
}
|
||||
|
||||
// Snapshot LKG (best-effort copy of current active bird.conf + bird.d)
|
||||
_ = os.RemoveAll(lkg)
|
||||
_ = snapshotBirdTree(active, lkg)
|
||||
|
||||
// Phase 2: atomic swap into active
|
||||
for rel, content := range rev.PreviewFragments {
|
||||
rel = strings.TrimPrefix(rel, "/")
|
||||
dst := filepath.Join(active, rel)
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
_ = restoreLKG(lkg, active)
|
||||
return err
|
||||
}
|
||||
tmp := dst + ".tmp"
|
||||
if err := os.WriteFile(tmp, []byte(content), 0o644); err != nil {
|
||||
_ = restoreLKG(lkg, active)
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmp, dst); err != nil {
|
||||
_ = restoreLKG(lkg, active)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := bc.Configure(ctx); err != nil {
|
||||
_ = restoreLKG(lkg, active)
|
||||
_ = bc.Configure(ctx)
|
||||
return fmt.Errorf("birddeploy: birdc configure failed, restored LKG: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func snapshotBirdTree(activeRoot, dstRoot string) error {
|
||||
if err := os.MkdirAll(dstRoot, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
mc := filepath.Join(activeRoot, "bird.conf")
|
||||
if b, err := os.ReadFile(mc); err == nil {
|
||||
_ = os.WriteFile(filepath.Join(dstRoot, "bird.conf"), b, 0o644)
|
||||
}
|
||||
bd := filepath.Join(activeRoot, "bird.d")
|
||||
if st, err := os.Stat(bd); err == nil && st.IsDir() {
|
||||
_ = filepath.WalkDir(bd, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
suffix, _ := filepath.Rel(activeRoot, path)
|
||||
out := filepath.Join(dstRoot, suffix)
|
||||
_ = os.MkdirAll(filepath.Dir(out), 0o755)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return os.WriteFile(out, b, 0o644)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreLKG(lkg, active string) error {
|
||||
if _, err := os.Stat(filepath.Join(lkg, "bird.conf")); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = filepath.WalkDir(lkg, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
suffix, _ := filepath.Rel(lkg, path)
|
||||
out := filepath.Join(active, suffix)
|
||||
_ = os.MkdirAll(filepath.Dir(out), 0o755)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return os.WriteFile(out, b, 0o644)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user