Public source validation / validate (push) Failing after 3m8s
158 lines
4.9 KiB
Go
158 lines
4.9 KiB
Go
// Command pulse-agent collects read-only host telemetry and publishes it as bounded
|
|
// snapshots for pulse-api to read.
|
|
//
|
|
// It exposes no network port, holds no Docker socket, and performs no mutation: it
|
|
// reads procfs/sysfs and writes one row per capability through agentstore.Writer. See
|
|
// docs/architecture/SYSTEM_ARCHITECTURE.md ("pulse-agent"), ADR-0005 and
|
|
// docs/operations/WORKER_AGENT_HEALTHCHECK_CONTRACT.md.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/itworx/pulse/internal/array"
|
|
"github.com/itworx/pulse/internal/container"
|
|
"github.com/itworx/pulse/internal/disk"
|
|
"github.com/itworx/pulse/internal/host"
|
|
"github.com/itworx/pulse/internal/hostcollect"
|
|
"github.com/itworx/pulse/internal/pool"
|
|
"github.com/itworx/pulse/internal/process"
|
|
"github.com/itworx/pulse/internal/runtimeconfig"
|
|
"github.com/itworx/pulse/internal/share"
|
|
"github.com/itworx/pulse/internal/unraid"
|
|
)
|
|
|
|
type configuredSource struct {
|
|
host *hostcollect.Collector
|
|
}
|
|
|
|
func (s configuredSource) Host(ctx context.Context) (host.RawSnapshot, error) {
|
|
return s.host.Host(ctx)
|
|
}
|
|
func (s configuredSource) Processes(ctx context.Context) (process.RawSnapshot, error) {
|
|
return s.host.Processes(ctx)
|
|
}
|
|
|
|
type configuredUnraidSource struct {
|
|
configuredSource
|
|
containers interface {
|
|
Snapshot(context.Context) (container.RawSnapshot, error)
|
|
}
|
|
array interface {
|
|
Snapshot(context.Context) (array.RawSnapshot, error)
|
|
}
|
|
disks interface {
|
|
Snapshot(context.Context) (disk.RawSnapshot, error)
|
|
}
|
|
pools interface {
|
|
Snapshot(context.Context) (pool.RawSnapshot, error)
|
|
}
|
|
shares interface {
|
|
Snapshot(context.Context) (share.RawSnapshot, error)
|
|
}
|
|
}
|
|
|
|
func (s configuredUnraidSource) Containers(ctx context.Context) (container.RawSnapshot, error) {
|
|
if s.containers == nil {
|
|
return container.RawSnapshot{}, errUnraidSourceNotConfigured
|
|
}
|
|
return s.containers.Snapshot(ctx)
|
|
}
|
|
func (s configuredUnraidSource) Array(ctx context.Context) (array.RawSnapshot, error) {
|
|
if s.array == nil {
|
|
return array.RawSnapshot{}, errUnraidSourceNotConfigured
|
|
}
|
|
return s.array.Snapshot(ctx)
|
|
}
|
|
func (s configuredUnraidSource) Disks(ctx context.Context) (disk.RawSnapshot, error) {
|
|
if s.disks == nil {
|
|
return disk.RawSnapshot{}, errUnraidSourceNotConfigured
|
|
}
|
|
return s.disks.Snapshot(ctx)
|
|
}
|
|
func (s configuredUnraidSource) Pools(ctx context.Context) (pool.RawSnapshot, error) {
|
|
if s.pools == nil {
|
|
return pool.RawSnapshot{}, errUnraidSourceNotConfigured
|
|
}
|
|
return s.pools.Snapshot(ctx)
|
|
}
|
|
func (s configuredUnraidSource) Shares(ctx context.Context) (share.RawSnapshot, error) {
|
|
if s.shares == nil {
|
|
return share.RawSnapshot{}, errUnraidSourceNotConfigured
|
|
}
|
|
return s.shares.Snapshot(ctx)
|
|
}
|
|
|
|
var errUnraidSourceNotConfigured = errors.New("Unraid source is not configured")
|
|
|
|
func main() {
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
if err := run(logger); err != nil {
|
|
logger.Error("pulse agent failed", "error", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(logger *slog.Logger) error {
|
|
config, err := runtimeconfig.LoadAgent()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
collector, err := hostcollect.New(hostcollect.Options{
|
|
ProcRoot: config.ProcRoot,
|
|
SysRoot: config.SysRoot,
|
|
FilesystemRoot: config.FilesystemRoot,
|
|
HostName: config.HostName,
|
|
SourceID: "host",
|
|
ProcessLimits: process.Limits{MaxRows: config.MaxProcesses},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
writer, closeStore, err := openStore(ctx, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeStore()
|
|
|
|
logger.Info("pulse agent configuration loaded",
|
|
"agent_id", config.AgentID,
|
|
"proc_root", config.ProcRoot,
|
|
"sys_root", config.SysRoot,
|
|
"filesystem_root", config.FilesystemRoot,
|
|
"collect_interval", config.CollectInterval.String(),
|
|
"shutdown_timeout", config.Service.ShutdownAfter.String(),
|
|
)
|
|
var source snapshotSource = configuredSource{host: collector}
|
|
if config.UnraidURL != "" {
|
|
var client *unraid.Client
|
|
if config.UnraidCAFile == "" {
|
|
client, err = unraid.New(config.UnraidURL, config.UnraidAPIToken, nil)
|
|
} else {
|
|
info, statErr := os.Stat(config.UnraidCAFile)
|
|
if statErr != nil || info.Size() <= 0 || info.Size() > 1<<20 {
|
|
return errors.New("PULSE_UNRAID_CA_FILE must be a readable certificate no larger than 1 MiB")
|
|
}
|
|
caPEM, readErr := os.ReadFile(config.UnraidCAFile)
|
|
if readErr != nil {
|
|
return errors.New("read PULSE_UNRAID_CA_FILE")
|
|
}
|
|
client, err = unraid.NewWithCAPEM(config.UnraidURL, config.UnraidAPIToken, caPEM)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
source = configuredUnraidSource{configuredSource: configuredSource{host: collector}, containers: unraid.ContainerSource{Client: client}, array: unraid.ArraySource{Client: client}, disks: unraid.DiskSource{Client: client}, pools: unraid.PoolSource{Client: client}, shares: unraid.ShareSource{Client: client}}
|
|
}
|
|
return newAgent(config, source, writer, logger).run(ctx)
|
|
}
|