@@ -5,11 +5,13 @@ import (
55 "encoding/json"
66 "fmt"
77 "os"
8+ "sync"
89 "time"
910
1011 "patchmon-agent/internal/client"
1112 "patchmon-agent/internal/hardware"
1213 "patchmon-agent/internal/integrations"
14+ "patchmon-agent/internal/integrations/compliance"
1315 "patchmon-agent/internal/integrations/docker"
1416 "patchmon-agent/internal/network"
1517 "patchmon-agent/internal/packages"
@@ -102,10 +104,10 @@ func sendReport(outputJson bool) error {
102104 needsReboot , rebootReason := systemDetector .CheckRebootRequired ()
103105 installedKernel := systemDetector .GetLatestInstalledKernel ()
104106 logger .WithFields (logrus.Fields {
105- "needs_reboot" : needsReboot ,
106- "reason" : rebootReason ,
107- "installed_kernel" : installedKernel ,
108- "running_kernel" : systemInfo .KernelVersion ,
107+ "needs_reboot" : needsReboot ,
108+ "reason" : rebootReason ,
109+ "installed_kernel" : installedKernel ,
110+ "running_kernel" : systemInfo .KernelVersion ,
109111 }).Info ("Reboot status check completed" )
110112
111113 // Get package information
@@ -172,31 +174,31 @@ func sendReport(outputJson bool) error {
172174
173175 // Create payload
174176 payload := & models.ReportPayload {
175- Packages : packageList ,
176- Repositories : repoList ,
177- OSType : osType ,
178- OSVersion : osVersion ,
179- Hostname : hostname ,
180- IP : ipAddress ,
181- Architecture : architecture ,
182- AgentVersion : version .Version ,
183- MachineID : systemDetector .GetMachineID (),
184- KernelVersion : systemInfo .KernelVersion ,
177+ Packages : packageList ,
178+ Repositories : repoList ,
179+ OSType : osType ,
180+ OSVersion : osVersion ,
181+ Hostname : hostname ,
182+ IP : ipAddress ,
183+ Architecture : architecture ,
184+ AgentVersion : version .Version ,
185+ MachineID : systemDetector .GetMachineID (),
186+ KernelVersion : systemInfo .KernelVersion ,
185187 InstalledKernelVersion : installedKernel ,
186- SELinuxStatus : systemInfo .SELinuxStatus ,
187- SystemUptime : systemInfo .SystemUptime ,
188- LoadAverage : systemInfo .LoadAverage ,
189- CPUModel : hardwareInfo .CPUModel ,
190- CPUCores : hardwareInfo .CPUCores ,
191- RAMInstalled : hardwareInfo .RAMInstalled ,
192- SwapSize : hardwareInfo .SwapSize ,
193- DiskDetails : hardwareInfo .DiskDetails ,
194- GatewayIP : networkInfo .GatewayIP ,
195- DNSServers : networkInfo .DNSServers ,
196- NetworkInterfaces : networkInfo .NetworkInterfaces ,
197- ExecutionTime : executionTime ,
198- NeedsReboot : needsReboot ,
199- RebootReason : rebootReason ,
188+ SELinuxStatus : systemInfo .SELinuxStatus ,
189+ SystemUptime : systemInfo .SystemUptime ,
190+ LoadAverage : systemInfo .LoadAverage ,
191+ CPUModel : hardwareInfo .CPUModel ,
192+ CPUCores : hardwareInfo .CPUCores ,
193+ RAMInstalled : hardwareInfo .RAMInstalled ,
194+ SwapSize : hardwareInfo .SwapSize ,
195+ DiskDetails : hardwareInfo .DiskDetails ,
196+ GatewayIP : networkInfo .GatewayIP ,
197+ DNSServers : networkInfo .DNSServers ,
198+ NetworkInterfaces : networkInfo .NetworkInterfaces ,
199+ ExecutionTime : executionTime ,
200+ NeedsReboot : needsReboot ,
201+ RebootReason : rebootReason ,
200202 }
201203
202204 // If --report-json flag is set, output JSON and exit
@@ -241,13 +243,27 @@ func sendReport(outputJson bool) error {
241243 return nil
242244 }
243245 } else {
244- // Proactive update check after report (non-blocking with timeout)
245- // Run in a goroutine to avoid blocking the report completion
246+ // Proactive update check after report (with timeout to prevent hanging)
247+ // Use a WaitGroup to ensure the goroutine completes before function returns
248+ var wg sync.WaitGroup
249+ wg .Add (1 )
246250 go func () {
251+ defer wg .Done ()
252+
253+ // Create a context with timeout to prevent indefinite hanging
254+ ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Minute )
255+ defer cancel ()
256+
247257 // Add a delay to prevent immediate checks after service restart
248258 // This gives the new process time to fully initialize
249- time .Sleep (5 * time .Second )
250-
259+ select {
260+ case <- time .After (5 * time .Second ):
261+ // Continue with update check
262+ case <- ctx .Done ():
263+ logger .Debug ("Update check cancelled due to timeout" )
264+ return
265+ }
266+
251267 logger .Info ("Checking for agent updates..." )
252268 versionInfo , err := getServerVersionInfo ()
253269 if err != nil {
@@ -277,6 +293,8 @@ func sendReport(outputJson bool) error {
277293 logger .WithField ("version" , versionInfo .CurrentVersion ).Info ("Agent is up to date" )
278294 }
279295 }()
296+ // Wait for the update check to complete (with the internal timeout)
297+ wg .Wait ()
280298 }
281299
282300 // Collect and send integration data (Docker, etc.) separately
@@ -305,11 +323,23 @@ func sendIntegrationData() {
305323
306324 // Register available integrations
307325 integrationMgr .Register (docker .New (logger ))
326+
327+ // Only register compliance integration if not set to on-demand only
328+ // When compliance_on_demand_only is true, compliance scans will only run when triggered from the UI
329+ if ! cfgManager .IsComplianceOnDemandOnly () {
330+ complianceInteg := compliance .New (logger )
331+ complianceInteg .SetDockerIntegrationEnabled (cfgManager .IsIntegrationEnabled ("docker" ))
332+ integrationMgr .Register (complianceInteg )
333+ } else {
334+ logger .Info ("Skipping compliance scan during scheduled report (compliance_on_demand_only=true)" )
335+ }
308336 // Future: integrationMgr.Register(proxmox.New(logger))
309337 // Future: integrationMgr.Register(kubernetes.New(logger))
310338
311339 // Discover and collect from all available integrations
312- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
340+ // 25 minute timeout to allow OpenSCAP scans to complete (they can take 15+ minutes on complex systems)
341+ // This gives time for both OpenSCAP and Docker Bench to complete
342+ ctx , cancel := context .WithTimeout (context .Background (), 25 * time .Minute )
313343 defer cancel ()
314344
315345 integrationData := integrationMgr .CollectAll (ctx )
@@ -332,6 +362,11 @@ func sendIntegrationData() {
332362 sendDockerData (httpClient , dockerData , hostname , machineID )
333363 }
334364
365+ // Send Compliance data if available
366+ if complianceData , exists := integrationData ["compliance" ]; exists && complianceData .Error == "" {
367+ sendComplianceData (httpClient , complianceData , hostname , machineID )
368+ }
369+
335370 // Future: Send other integration data here
336371}
337372
@@ -375,3 +410,49 @@ func sendDockerData(httpClient *client.Client, integrationData *models.Integrati
375410 "updates" : response .UpdatesFound ,
376411 }).Info ("Docker data sent successfully" )
377412}
413+
414+ // sendComplianceData sends compliance scan data to server
415+ func sendComplianceData (httpClient * client.Client , integrationData * models.IntegrationData , hostname , machineID string ) {
416+ // Extract Compliance data from integration data
417+ complianceData , ok := integrationData .Data .(* models.ComplianceData )
418+ if ! ok {
419+ logger .Warn ("Failed to extract compliance data from integration" )
420+ return
421+ }
422+
423+ if len (complianceData .Scans ) == 0 {
424+ logger .Debug ("No compliance scans to send" )
425+ return
426+ }
427+
428+ payload := & models.CompliancePayload {
429+ ComplianceData : * complianceData ,
430+ Hostname : hostname ,
431+ MachineID : machineID ,
432+ AgentVersion : version .Version ,
433+ }
434+
435+ totalRules := 0
436+ for _ , scan := range complianceData .Scans {
437+ totalRules += scan .TotalRules
438+ }
439+
440+ logger .WithFields (logrus.Fields {
441+ "scans" : len (complianceData .Scans ),
442+ "total_rules" : totalRules ,
443+ }).Info ("Sending compliance data to server..." )
444+
445+ ctx , cancel := context .WithTimeout (context .Background (), 120 * time .Second ) // Longer timeout for compliance
446+ defer cancel ()
447+
448+ response , err := httpClient .SendComplianceData (ctx , payload )
449+ if err != nil {
450+ logger .WithError (err ).Warn ("Failed to send compliance data (will retry on next report)" )
451+ return
452+ }
453+
454+ logger .WithFields (logrus.Fields {
455+ "scans_received" : response .ScansReceived ,
456+ "message" : response .Message ,
457+ }).Info ("Compliance data sent successfully" )
458+ }
0 commit comments