package main import ( "context" "errors" "fmt" "time" "github.com/itworx/pulse/internal/agentstore" "github.com/itworx/pulse/internal/database" "github.com/itworx/pulse/internal/runtimeconfig" "github.com/jackc/pgx/v5/pgxpool" ) const ( // storeConnectTimeout bounds the start-up reachability check. The healthcheck // contract wants the first heartbeat written after the database is reachable, so // this must stay far below the 45 second staleness threshold. storeConnectTimeout = 5 * time.Second // agentPoolMaxConns keeps the agent's footprint on the shared database small: it // issues one small write per capability per interval and never reads. agentPoolMaxConns = 2 ) // errStoreNotLinked is returned by every write while no snapshot store implementation // is compiled into this binary. It is deliberately an error on the write path rather // than a silent drop: the reader turns a missing snapshot into Unknown, and the agent // log states plainly why nothing arrives. var errStoreNotLinked = errors.New("no agent snapshot store is linked into this build") // openStore connects to PostgreSQL and returns the narrow Writer the agent publishes // through, plus a close function. // // The agent depends on the agentstore.Writer interface only; the concrete PostgreSQL // store owns migration 0016 and the agent_snapshots table. func openStore(ctx context.Context, config runtimeconfig.AgentConfig) (agentstore.Writer, func(), error) { pool, err := database.NewPool(ctx, database.Config{ URL: config.DatabaseURL, MaxConns: agentPoolMaxConns, MinConns: 1, }) if err != nil { // Never wrap the URL itself into the error: it carries the database password. return nil, nil, errors.New("agent database pool could not be created") } reachable, cancel := context.WithTimeout(ctx, storeConnectTimeout) defer cancel() if err := database.Ping(reachable, pool); err != nil { pool.Close() return nil, nil, fmt.Errorf("agent database is unreachable: %w", err) } writer, err := newSnapshotWriter(pool) if err != nil { pool.Close() return nil, nil, err } return writer, pool.Close, nil } // newSnapshotWriter builds the store the agent writes through. A nil pool would make // every publish fail silently at the driver, so it is refused here instead: the agent // must either have a real store or fail to start. func newSnapshotWriter(pool *pgxpool.Pool) (agentstore.Writer, error) { if pool == nil { return nil, errStoreNotLinked } return agentstore.PostgresStore{Pool: pool}, nil } var _ agentstore.Writer = agentstore.PostgresStore{}