This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user