mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Fix RC upgrade ordering and OPNsense SMART temps
This commit is contained in:
parent
0a68d79871
commit
0c2de2938f
4 changed files with 236 additions and 25 deletions
|
|
@ -135,7 +135,7 @@ type smartTextFallback struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
smartTextTempAttributeRE = regexp.MustCompile(`^\s*(190|194)\s+\S+.*-\s+(\d{1,3})\b`)
|
smartTextTempAttributeRE = regexp.MustCompile(`^\s*(190|194)\s+\S+.*-\s+(\d{1,3})\b`)
|
||||||
smartTextCurrentTempRE = regexp.MustCompile(`(?i)^current temperature:\s*(\d{1,3})\b`)
|
smartTextCurrentTempRE = regexp.MustCompile(`(?i)^current(?: drive)? temperature:\s*(\d{1,3})\b`)
|
||||||
smartTextTemperatureRE = regexp.MustCompile(`(?i)^temperature:\s*(\d{1,3})\b`)
|
smartTextTemperatureRE = regexp.MustCompile(`(?i)^temperature:\s*(\d{1,3})\b`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,40 @@ Current Temperature: 39 C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseSMARTOutputFallsBackToCurrentDriveTemperatureText(t *testing.T) {
|
||||||
|
payload := smartctlJSON{
|
||||||
|
ModelName: "SEAGATE EXOS",
|
||||||
|
SerialNumber: "SG-123",
|
||||||
|
}
|
||||||
|
payload.Device.Protocol = "SCSI"
|
||||||
|
payload.SmartStatus = &struct {
|
||||||
|
Passed bool `json:"passed"`
|
||||||
|
}{Passed: true}
|
||||||
|
payload.Smartctl.Output = []string{
|
||||||
|
"=== START OF INFORMATION SECTION ===",
|
||||||
|
"Current Drive Temperature: 35 C",
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal payload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := parseSMARTOutput(out, smartctlTarget{Path: "/dev/da0"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("expected result")
|
||||||
|
}
|
||||||
|
if result.Temperature != 35 {
|
||||||
|
t.Fatalf("expected current drive temperature 35, got %#v", result)
|
||||||
|
}
|
||||||
|
if result.Model != "SEAGATE EXOS" || result.Serial != "SG-123" || result.Health != "PASSED" {
|
||||||
|
t.Fatalf("expected model/serial/health to be preserved, got %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseRawValue(t *testing.T) {
|
func TestParseRawValue(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -68,39 +68,208 @@ func NormalizeVersion(version string) string {
|
||||||
//
|
//
|
||||||
// -1 if a < b (b is newer)
|
// -1 if a < b (b is newer)
|
||||||
//
|
//
|
||||||
// Handles versions like "4.33.1", "v4.33.1", "4.33" gracefully.
|
// Handles versions like "4.33.1", "v4.33.1", "4.33", and semver prereleases
|
||||||
|
// like "5.1.26-rc.2" gracefully.
|
||||||
func CompareVersions(a, b string) int {
|
func CompareVersions(a, b string) int {
|
||||||
// Normalize both versions
|
coreA, prereleaseA := splitVersionForComparison(a)
|
||||||
a = NormalizeVersion(a)
|
coreB, prereleaseB := splitVersionForComparison(b)
|
||||||
b = NormalizeVersion(b)
|
|
||||||
|
|
||||||
// Split into parts
|
maxLen := len(coreA)
|
||||||
partsA := strings.Split(a, ".")
|
if len(coreB) > maxLen {
|
||||||
partsB := strings.Split(b, ".")
|
maxLen = len(coreB)
|
||||||
|
|
||||||
// Compare each part numerically
|
|
||||||
maxLen := len(partsA)
|
|
||||||
if len(partsB) > maxLen {
|
|
||||||
maxLen = len(partsB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < maxLen; i++ {
|
for i := 0; i < maxLen; i++ {
|
||||||
var numA, numB int
|
partA := versionPartAt(coreA, i)
|
||||||
|
partB := versionPartAt(coreB, i)
|
||||||
if i < len(partsA) {
|
if partA > partB {
|
||||||
fmt.Sscanf(partsA[i], "%d", &numA)
|
|
||||||
}
|
|
||||||
if i < len(partsB) {
|
|
||||||
fmt.Sscanf(partsB[i], "%d", &numB)
|
|
||||||
}
|
|
||||||
|
|
||||||
if numA > numB {
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if numA < numB {
|
if partA < partB {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return compareVersionPrerelease(prereleaseA, prereleaseB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitVersionForComparison(version string) ([]int, []string) {
|
||||||
|
normalized := NormalizeVersion(version)
|
||||||
|
prerelease := ""
|
||||||
|
if idx := strings.Index(normalized, "-"); idx != -1 {
|
||||||
|
prerelease = normalized[idx+1:]
|
||||||
|
normalized = normalized[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(normalized, ".")
|
||||||
|
core := make([]int, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "" {
|
||||||
|
core = append(core, 0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value := 0
|
||||||
|
fmt.Sscanf(part, "%d", &value)
|
||||||
|
core = append(core, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prerelease == "" {
|
||||||
|
return core, nil
|
||||||
|
}
|
||||||
|
return core, strings.Split(prerelease, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionPartAt(parts []int, idx int) int {
|
||||||
|
if idx >= len(parts) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return parts[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareVersionPrerelease(a, b []string) int {
|
||||||
|
switch {
|
||||||
|
case len(a) == 0 && len(b) == 0:
|
||||||
|
return 0
|
||||||
|
case len(a) == 0:
|
||||||
|
return 1
|
||||||
|
case len(b) == 0:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := len(a)
|
||||||
|
if len(b) < limit {
|
||||||
|
limit = len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < limit; i++ {
|
||||||
|
if cmp := compareVersionIdentifier(a[i], b[i]); cmp != 0 {
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(a) > len(b):
|
||||||
|
return 1
|
||||||
|
case len(a) < len(b):
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareVersionIdentifier(a, b string) int {
|
||||||
|
if a == b {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
aNumeric := isNumericVersionIdentifier(a)
|
||||||
|
bNumeric := isNumericVersionIdentifier(b)
|
||||||
|
switch {
|
||||||
|
case aNumeric && bNumeric:
|
||||||
|
return compareNumericIdentifier(a, b)
|
||||||
|
case aNumeric:
|
||||||
|
return -1
|
||||||
|
case bNumeric:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return compareVersionChunks(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNumericVersionIdentifier(value string) bool {
|
||||||
|
if value == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range value {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareNumericIdentifier(a, b string) int {
|
||||||
|
a = strings.TrimLeft(a, "0")
|
||||||
|
b = strings.TrimLeft(b, "0")
|
||||||
|
if a == "" {
|
||||||
|
a = "0"
|
||||||
|
}
|
||||||
|
if b == "" {
|
||||||
|
b = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(a) > len(b):
|
||||||
|
return 1
|
||||||
|
case len(a) < len(b):
|
||||||
|
return -1
|
||||||
|
case a > b:
|
||||||
|
return 1
|
||||||
|
case a < b:
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareVersionChunks(a, b string) int {
|
||||||
|
for a != "" && b != "" {
|
||||||
|
aChunk, aNumeric, nextA := nextVersionChunk(a)
|
||||||
|
bChunk, bNumeric, nextB := nextVersionChunk(b)
|
||||||
|
|
||||||
|
var cmp int
|
||||||
|
switch {
|
||||||
|
case aNumeric && bNumeric:
|
||||||
|
cmp = compareNumericIdentifier(aChunk, bChunk)
|
||||||
|
case aNumeric != bNumeric:
|
||||||
|
cmp = strings.Compare(aChunk, bChunk)
|
||||||
|
default:
|
||||||
|
cmp = strings.Compare(aChunk, bChunk)
|
||||||
|
}
|
||||||
|
if cmp != 0 {
|
||||||
|
return normalizeCompareResult(cmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
a = nextA
|
||||||
|
b = nextB
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case a == "" && b == "":
|
||||||
|
return 0
|
||||||
|
case a == "":
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextVersionChunk(value string) (chunk string, numeric bool, rest string) {
|
||||||
|
if value == "" {
|
||||||
|
return "", false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
numeric = value[0] >= '0' && value[0] <= '9'
|
||||||
|
end := 1
|
||||||
|
for end < len(value) {
|
||||||
|
isDigit := value[end] >= '0' && value[end] <= '9'
|
||||||
|
if isDigit != numeric {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[:end], numeric, value[end:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeCompareResult(cmp int) int {
|
||||||
|
switch {
|
||||||
|
case cmp > 0:
|
||||||
|
return 1
|
||||||
|
case cmp < 0:
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,14 @@ func TestCompareVersions(t *testing.T) {
|
||||||
{"v4.34.0", "v4.33.1", 1},
|
{"v4.34.0", "v4.33.1", 1},
|
||||||
{"v4.33.1", "v4.34.0", -1},
|
{"v4.33.1", "v4.34.0", -1},
|
||||||
|
|
||||||
|
// Prerelease precedence
|
||||||
|
{"5.1.26", "5.1.26-rc.2", 1},
|
||||||
|
{"5.1.26-rc.2", "5.1.26", -1},
|
||||||
|
{"5.1.26-rc.10", "5.1.26-rc.2", 1},
|
||||||
|
{"5.1.26-rc.2", "5.1.26-rc.10", -1},
|
||||||
|
{"v0.0.1-rc2", "v0.0.1-rc1", 1},
|
||||||
|
{"v0.0.1-rc1", "v0.0.1", -1},
|
||||||
|
|
||||||
// Edge cases
|
// Edge cases
|
||||||
{"0.0.1", "0.0.0", 1},
|
{"0.0.1", "0.0.0", 1},
|
||||||
{"0.0.0", "0.0.1", -1},
|
{"0.0.0", "0.0.1", -1},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue