Skip to content

Commit 957c9de

Browse files
committed
lint fixes
1 parent 6c34814 commit 957c9de

File tree

3 files changed

+169
-52
lines changed

3 files changed

+169
-52
lines changed

cmd/agent/main.go

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"gitmdm/internal/gitmdm"
1616
"io"
1717
"log"
18+
"math"
1819
"net/http"
1920
"os"
2021
"os/exec"
@@ -34,6 +35,14 @@ import (
3435
const (
3536
// Command execution timeout.
3637
commandTimeout = 10 * time.Second
38+
39+
// OS constants.
40+
osWindows = "windows"
41+
osDarwin = "darwin"
42+
osLinux = "linux"
43+
44+
// Windows command constants.
45+
wmicCmd = "wmic"
3746
// Maximum output size to prevent memory exhaustion.
3847
maxOutputSize = 92160 // 90KB limit (matching server)
3948
// Maximum log output length for readability.
@@ -156,10 +165,39 @@ func main() {
156165
LoggedInUsers: agent.loggedInUsers(ctx),
157166
}
158167

159-
if err := agent.sendReport(ctx, report); err != nil {
168+
// Retry the verification with exponential backoff
169+
var lastErr error
170+
for attempt := 0; attempt <= maxRetries; attempt++ {
171+
if attempt > 0 {
172+
backoff := time.Duration(float64(initialBackoff) * math.Pow(2, float64(attempt-1)))
173+
if backoff > maxBackoff {
174+
backoff = maxBackoff
175+
}
176+
log.Printf("[INFO] Retrying verification (attempt %d/%d) after %v...", attempt, maxRetries, backoff)
177+
select {
178+
case <-time.After(backoff):
179+
case <-ctx.Done():
180+
cancel()
181+
//nolint:gocritic,lll // exitAfterDefer
182+
log.Fatalf("Verification cancelled: %v", ctx.Err())
183+
}
184+
}
185+
186+
if err := agent.sendReport(ctx, report); err != nil {
187+
lastErr = err
188+
log.Printf("[WARN] Verification attempt %d failed: %v", attempt+1, err)
189+
continue
190+
}
191+
// Success!
192+
lastErr = nil
193+
break
194+
}
195+
196+
if lastErr != nil {
160197
cancel()
161-
//nolint:gocritic,lll // exitAfterDefer
162-
log.Fatalf("Failed to verify server connection: %v\nPlease check your --server and --join parameters", err)
198+
//nolint:lll // exitAfterDefer
199+
log.Fatalf("Failed to verify server connection after %d attempts: %v\n"+
200+
"Please check your --server and --join parameters", maxRetries+1, lastErr)
163201
}
164202

165203
log.Println("✓ Server connection verified successfully")
@@ -550,8 +588,8 @@ func (*Agent) systemUptime(ctx context.Context) string {
550588
cmd = exec.CommandContext(ctx, "uptime")
551589
case "solaris", "illumos":
552590
cmd = exec.CommandContext(ctx, "uptime")
553-
case "windows":
554-
cmd = exec.CommandContext(ctx, "wmic", "os", "get", "lastbootuptime")
591+
case osWindows:
592+
cmd = exec.CommandContext(ctx, wmicCmd, "os", "get", "lastbootuptime")
555593
default:
556594
return "unsupported"
557595
}
@@ -568,22 +606,22 @@ func (*Agent) cpuLoad(ctx context.Context) string {
568606

569607
var cmd *exec.Cmd
570608
switch runtime.GOOS {
571-
case "linux":
609+
case osLinux:
572610
cmd = exec.CommandContext(ctx, "cat", "/proc/loadavg")
573611
case "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
574612
cmd = exec.CommandContext(ctx, "sysctl", "-n", "vm.loadavg")
575613
case "solaris", "illumos":
576614
cmd = exec.CommandContext(ctx, "uptime")
577-
case "windows":
578-
cmd = exec.CommandContext(ctx, "wmic", "cpu", "get", "loadpercentage")
615+
case osWindows:
616+
cmd = exec.CommandContext(ctx, wmicCmd, "cpu", "get", "loadpercentage")
579617
default:
580618
return "unsupported"
581619
}
582620

583621
if output, err := cmd.Output(); err == nil {
584622
result := strings.TrimSpace(string(output))
585623
// For Linux /proc/loadavg, extract just the three load averages
586-
if runtime.GOOS == "linux" {
624+
if runtime.GOOS == osLinux {
587625
fields := strings.Fields(result)
588626
if len(fields) >= 3 {
589627
result = strings.Join(fields[:3], " ")
@@ -609,16 +647,16 @@ func (*Agent) loggedInUsers(ctx context.Context) string {
609647
switch runtime.GOOS {
610648
case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "illumos":
611649
cmd = exec.CommandContext(ctx, "who")
612-
case "windows":
613-
cmd = exec.CommandContext(ctx, "wmic", "computersystem", "get", "username")
650+
case osWindows:
651+
cmd = exec.CommandContext(ctx, wmicCmd, "computersystem", "get", "username")
614652
default:
615653
return "unsupported"
616654
}
617655

618656
if output, err := cmd.Output(); err == nil {
619657
result := strings.TrimSpace(string(output))
620658
// Count unique users for Unix-like systems
621-
if runtime.GOOS != "windows" {
659+
if runtime.GOOS != osWindows {
622660
lines := strings.Split(result, "\n")
623661
userMap := make(map[string]bool)
624662
for _, line := range lines {
@@ -646,7 +684,7 @@ func (*Agent) osInfo(ctx context.Context) string {
646684
defer cancel()
647685
var cmd *exec.Cmd
648686
switch runtime.GOOS {
649-
case "linux":
687+
case osLinux:
650688
// Try to get pretty name from os-release
651689
if data, err := os.ReadFile("/etc/os-release"); err == nil {
652690
lines := strings.Split(string(data), "\n")
@@ -659,17 +697,17 @@ func (*Agent) osInfo(ctx context.Context) string {
659697
}
660698
// Fallback to uname
661699
cmd = exec.CommandContext(ctx, "uname", "-s")
662-
case "darwin":
700+
case osDarwin:
663701
cmd = exec.CommandContext(ctx, "sw_vers", "-productName")
664-
case "windows":
665-
cmd = exec.CommandContext(ctx, "wmic", "os", "get", "Caption", "/value")
702+
case osWindows:
703+
cmd = exec.CommandContext(ctx, wmicCmd, "os", "get", "Caption", "/value")
666704
default:
667705
cmd = exec.CommandContext(ctx, "uname", "-s")
668706
}
669707
if cmd != nil {
670708
if output, err := cmd.Output(); err == nil {
671709
result := strings.TrimSpace(string(output))
672-
if runtime.GOOS == "windows" && strings.Contains(result, "Caption=") {
710+
if runtime.GOOS == osWindows && strings.Contains(result, "Caption=") {
673711
result = strings.TrimPrefix(result, "Caption=")
674712
}
675713
if result != "" {
@@ -691,18 +729,18 @@ func (*Agent) osVersion(ctx context.Context) string {
691729
defer cancel()
692730
var cmd *exec.Cmd
693731
switch runtime.GOOS {
694-
case "linux":
732+
case osLinux:
695733
cmd = exec.CommandContext(ctx, "uname", "-r")
696-
case "darwin":
734+
case osDarwin:
697735
cmd = exec.CommandContext(ctx, "sw_vers", "-productVersion")
698-
case "windows":
699-
cmd = exec.CommandContext(ctx, "wmic", "os", "get", "Version", "/value")
736+
case osWindows:
737+
cmd = exec.CommandContext(ctx, wmicCmd, "os", "get", "Version", "/value")
700738
default:
701739
cmd = exec.CommandContext(ctx, "uname", "-r")
702740
}
703741
if output, err := cmd.Output(); err == nil {
704742
result := strings.TrimSpace(string(output))
705-
if runtime.GOOS == "windows" && strings.Contains(result, "Version=") {
743+
if runtime.GOOS == osWindows && strings.Contains(result, "Version=") {
706744
result = strings.TrimPrefix(result, "Version=")
707745
}
708746
if result != "" {
@@ -827,7 +865,7 @@ func illumosHardwareID() string {
827865
func windowsHardwareID() string {
828866
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
829867
defer cancel()
830-
cmd := exec.CommandContext(ctx, "wmic", "csproduct", "get", "UUID")
868+
cmd := exec.CommandContext(ctx, wmicCmd, "csproduct", "get", "UUID")
831869
output, err := cmd.Output()
832870
if err != nil {
833871
if *debug {
@@ -857,17 +895,17 @@ func hardwareID() string {
857895

858896
var id string
859897
switch runtime.GOOS {
860-
case "darwin":
898+
case osDarwin:
861899
id = darwinHardwareID()
862-
case "linux":
900+
case osLinux:
863901
id = linuxHardwareID()
864902
case "freebsd", "openbsd", "netbsd", "dragonfly":
865903
id = bsdHardwareID()
866904
case "solaris":
867905
id = solarisHardwareID()
868906
case "illumos":
869907
id = illumosHardwareID()
870-
case "windows":
908+
case osWindows:
871909
id = windowsHardwareID()
872910
default:
873911
if *debug {
@@ -1218,7 +1256,7 @@ func (*Agent) displayAllChecks(results map[string]CheckResult, checkOrder []stri
12181256
}
12191257
printLine("[Command %d of %d - %s]", i+1, len(result.Outputs), status)
12201258
}
1221-
1259+
12221260
if output.Command != "" {
12231261
printLine("Command: %s", output.Command)
12241262
} else if output.File != "" {

internal/config/types.go

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// Package config defines configuration structures for gitMDM checks.
22
package config
33

4+
import (
5+
"strings"
6+
7+
"gopkg.in/yaml.v3"
8+
)
9+
410
// Config represents the complete checks configuration.
511
type Config struct {
612
Checks map[string]CheckDefinition `yaml:"checks"`
@@ -9,18 +15,70 @@ type Config struct {
915
// CheckDefinition defines how a check should be performed across different platforms.
1016
// Field names follow Go build tag conventions.
1117
type CheckDefinition struct {
12-
Description string `yaml:"description,omitempty"`
13-
All []CommandRule `yaml:"all,omitempty"`
14-
Unix []CommandRule `yaml:"unix,omitempty"` // All Unix-like systems (not Windows)
15-
Darwin []CommandRule `yaml:"darwin,omitempty"` // macOS
16-
Linux []CommandRule `yaml:"linux,omitempty"` // Linux
17-
Windows []CommandRule `yaml:"windows,omitempty"` // Windows
18-
FreeBSD []CommandRule `yaml:"freebsd,omitempty"` // FreeBSD
19-
OpenBSD []CommandRule `yaml:"openbsd,omitempty"` // OpenBSD
20-
NetBSD []CommandRule `yaml:"netbsd,omitempty"` // NetBSD
21-
Dragonfly []CommandRule `yaml:"dragonfly,omitempty"` // DragonflyBSD (Go uses "dragonfly")
22-
Solaris []CommandRule `yaml:"solaris,omitempty"` // Solaris/OpenSolaris
23-
Illumos []CommandRule `yaml:"illumos,omitempty"` // Illumos
18+
additionalRules map[string][]CommandRule
19+
Description string `yaml:"description,omitempty"`
20+
FreeBSD []CommandRule `yaml:"freebsd,omitempty"`
21+
Darwin []CommandRule `yaml:"darwin,omitempty"`
22+
Linux []CommandRule `yaml:"linux,omitempty"`
23+
Windows []CommandRule `yaml:"windows,omitempty"`
24+
Unix []CommandRule `yaml:"unix,omitempty"`
25+
OpenBSD []CommandRule `yaml:"openbsd,omitempty"`
26+
NetBSD []CommandRule `yaml:"netbsd,omitempty"`
27+
Dragonfly []CommandRule `yaml:"dragonfly,omitempty"`
28+
Solaris []CommandRule `yaml:"solaris,omitempty"`
29+
Illumos []CommandRule `yaml:"illumos,omitempty"`
30+
All []CommandRule `yaml:"all,omitempty"`
31+
}
32+
33+
// UnmarshalYAML implements custom YAML unmarshaling to support comma-separated OS keys.
34+
func (cd *CheckDefinition) UnmarshalYAML(node *yaml.Node) error {
35+
// Create a temporary type to avoid recursion
36+
type checkDefAlias CheckDefinition
37+
38+
// First unmarshal into a map to handle dynamic keys
39+
var raw map[string]any
40+
if err := node.Decode(&raw); err != nil {
41+
return err
42+
}
43+
44+
// Initialize the additional rules map
45+
cd.additionalRules = make(map[string][]CommandRule)
46+
47+
// Process each key-value pair
48+
for key, value := range raw {
49+
// Check if the key contains a comma (multi-OS specification)
50+
if !strings.Contains(key, ",") {
51+
continue
52+
}
53+
54+
// Parse the command rules for this multi-OS key
55+
yamlBytes, err := yaml.Marshal(value)
56+
if err != nil {
57+
continue
58+
}
59+
var rules []CommandRule
60+
if err := yaml.Unmarshal(yamlBytes, &rules); err != nil {
61+
continue
62+
}
63+
64+
// Split the key and store rules for each OS
65+
osNames := strings.Split(key, ",")
66+
for _, osName := range osNames {
67+
osName = strings.TrimSpace(osName)
68+
cd.additionalRules[osName] = rules
69+
}
70+
71+
// Remove from raw map so it doesn't interfere with standard unmarshaling
72+
delete(raw, key)
73+
}
74+
75+
// Marshal the cleaned map back to YAML and unmarshal into the struct
76+
cleanedYAML, err := yaml.Marshal(raw)
77+
if err != nil {
78+
return err
79+
}
80+
81+
return yaml.Unmarshal(cleanedYAML, (*checkDefAlias)(cd))
2482
}
2583

2684
// CommandRule defines a single command or file check with evaluation criteria.
@@ -46,7 +104,14 @@ type CommandRule struct {
46104

47105
// CommandsForOS returns the command rules for a specific OS.
48106
func (cd *CheckDefinition) CommandsForOS(osName string) []CommandRule {
49-
// Check exact OS match first
107+
// First check if there are rules from comma-separated OS specifications
108+
if cd.additionalRules != nil {
109+
if rules, exists := cd.additionalRules[osName]; exists && len(rules) > 0 {
110+
return rules
111+
}
112+
}
113+
114+
// Check exact OS match
50115
switch osName {
51116
case "darwin":
52117
if len(cd.Darwin) > 0 {

0 commit comments

Comments
 (0)