// Command updater-agent reports this machine's Docker services to the // package-updater master. package main import ( "context" "fmt" "log/slog" "os" "os/signal" "syscall" "time" "github.com/docker/docker/client" "github.com/shcizo/package-updater/internal/agent/config" "github.com/shcizo/package-updater/internal/agent/conn" "github.com/shcizo/package-updater/internal/agent/inventory" "github.com/shcizo/package-updater/internal/logging" ) // Build info is injected at link time via -ldflags="-X main.version=...". var version = "dev" func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "fatal:", err) os.Exit(1) } } func run() error { cfg, err := config.Load() if err != nil { return err } logger := logging.New(cfg.LogLevel) dockerCli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return fmt.Errorf("docker client: %w", err) } defer dockerCli.Close() collector := inventory.New(dockerCli, cfg.SelfID) agent := conn.New(conn.Config{ URL: cfg.MasterURL, Token: cfg.Token, Fingerprint: cfg.MasterKey, Version: version, }, collector, logger) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go watchLoop(ctx, dockerCli, agent, logger) logger.Info("agent_starting", "version", version, "master_url", cfg.MasterURL) return agent.Run(ctx) } // watchLoop keeps a Docker event subscription alive. When the stream breaks // (daemon restart), it resubscribes and asks for a refresh, because events // may have been missed in between. It also asks for a full re-inventory every // 60 seconds, since a Swarm replica dying on another node emits no event here. func watchLoop(ctx context.Context, src inventory.EventSource, agent *conn.Agent, log *slog.Logger) { go func() { ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: agent.Notify() } } }() for { err := inventory.Watch(ctx, src, 2*time.Second, agent.Notify) if ctx.Err() != nil { return } log.Warn("docker_events_lost", "error", err) select { case <-ctx.Done(): return case <-time.After(5 * time.Second): } agent.Notify() } }