feat: store workload reports and route log results

This commit is contained in:
2026-08-07 08:56:26 +01:00
parent 6a4ef5b6c6
commit cf9d85b3cd
7 changed files with 403 additions and 15 deletions
+3
View File
@@ -40,6 +40,9 @@ type ReportWorkloadsRequest struct {
SystemdOk bool `json:"systemd_ok"`
SystemdError string `json:"systemd_error,omitempty"`
Workloads []Workload `json:"workloads,omitempty"` // empty on the offer call
// Full marks the second call. It is not inferred from an empty Workloads
// slice: a host running nothing sends an empty list as its full report.
Full bool `json:"full,omitempty"`
}
type ReportWorkloadsResponse struct {
+71
View File
@@ -163,6 +163,70 @@ func (s *vantageServer) ReportPackages(ctx context.Context, req *pb.ReportPackag
return &pb.ReportPackagesResponse{NeedFull: false}, nil
}
// ReportWorkloads stores what a server is running.
//
// It is not gated by licence: the workload registry reads as core fleet
// management rather than a premium add-on. If that ever changes, the check
// belongs here — gating collection, not display — for the same reason it does
// in ReportPackages.
func (s *vantageServer) ReportWorkloads(ctx context.Context, req *pb.ReportWorkloadsRequest) (*pb.ReportWorkloadsResponse, error) {
srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
}
// The offer call: a hash and no body. Answering NeedFull=false here is what
// keeps an unchanged 60-second report to one small message.
//
// The offer is identified by Full, not by an empty Workloads slice: a host
// genuinely running nothing sends an empty list as its FULL report, and
// inferring the offer from emptiness would leave that host answering
// NeedFull=true forever and never storing anything.
if !req.Full {
known, err := services.HasWorkloadHash(srv.InstanceID, srv.ServerID, req.Hash)
if err != nil {
log.Printf("workload hash lookup for %s: %v", srv.ServerID, err)
return nil, status.Errorf(codes.Internal, "workload hash lookup failed")
}
return &pb.ReportWorkloadsResponse{NeedFull: !known}, nil
}
wls := make([]models.Workload, len(req.Workloads))
for i, w := range req.Workloads {
wls[i] = models.Workload{
Kind: w.Kind,
ID: w.Id,
Name: w.Name,
State: w.State,
Health: w.Health,
Image: w.Image,
Stack: w.Stack,
Ports: w.Ports,
Restarts: int(w.Restarts),
Protected: w.Protected,
}
if w.StartedAt != "" {
if t, err := time.Parse(time.RFC3339, w.StartedAt); err == nil {
wls[i].StartedAt = t
}
}
}
if err := storeWorkloadReport(srv.InstanceID, srv.ServerID, req, wls); err != nil {
log.Printf("store workloads for %s: %v", srv.ServerID, err)
return nil, status.Errorf(codes.Internal, "failed to store workloads")
}
return &pb.ReportWorkloadsResponse{NeedFull: false}, nil
}
func storeWorkloadReport(instanceID, serverID string, req *pb.ReportWorkloadsRequest, wls []models.Workload) error {
if wls == nil {
wls = []models.Workload{}
}
return services.StoreWorkloads(instanceID, serverID, req.Hash, wls,
req.DockerOk, req.DockerError, req.SystemdOk, req.SystemdError)
}
func (s *vantageServer) ReportInventory(ctx context.Context, req *pb.InventoryReport) (*pb.InventoryReportResponse, error) {
srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
if err != nil {
@@ -257,6 +321,13 @@ func (s *vantageServer) CommandStream(stream pb.Vantage_CommandStreamServer) err
if m.Result != nil {
r := m.Result
log.Printf("agent %s cmd %s: success=%v %s", srv.ServerID, r.CommandId, r.Success, r.Message)
// Republished so a control action waiting on another pod sees
// it. Publishing with no subscriber is a no-op, so this is safe
// for every command result rather than only the awaited ones.
services.WorkloadResults.DeliverCommand(r)
}
if m.WorkloadLogsResult != nil {
services.WorkloadResults.Deliver(m.WorkloadLogsResult)
}
if m.StepResult != nil {
services.StepResults.Deliver(m.StepResult)