Use checked Proxmox replication parsing

This commit is contained in:
rcourtman 2026-04-01 16:18:02 +01:00
parent 7950cfe0e2
commit 072ca71e7c
4 changed files with 52 additions and 19 deletions

View file

@ -291,6 +291,11 @@ canonical physical-disk model and the Proxmox polling runtime in
`internal/monitoring/monitor_pve.go` must evaluate disk alerts only after that
merged disk view exists, so controller-backed disks do not lose health and
endurance coverage between collection and alerting.
That same Proxmox monitoring boundary also owns checked response parsing for
polymorphic numeric fields. Shared client parsers such as
`pkg/proxmox/replication.go` must use the package's checked integer conversion
helpers instead of direct casts, so malformed or oversized Proxmox values do
not overflow into monitoring state.
Backup polling and recovery guest identity assembly now derive workload node,
name, and type context from canonical `ReadState` instead of from

View file

@ -126,6 +126,13 @@ func floatToIntTrunc(v float64) (int, error) {
return int(v), nil
}
func floatToIntRounded(v float64) (int, error) {
if math.IsNaN(v) || math.IsInf(v, 0) {
return 0, fmt.Errorf("non-finite float %v cannot be converted to int", v)
}
return floatToIntTrunc(math.Round(v))
}
func looksLikeNumericLiteral(raw string) bool {
hasDigit := false
var prev rune

View file

@ -368,33 +368,39 @@ func intFromAny(value interface{}) (int, bool) {
case int32:
return int(v), true
case int64:
return int(v), true
parsed, err := int64ToInt(v)
return parsed, err == nil
case uint:
return int(v), true
parsed, err := uint64ToInt(uint64(v))
return parsed, err == nil
case uint8:
return int(v), true
case uint16:
return int(v), true
case uint32:
return int(v), true
parsed, err := uint64ToInt(uint64(v))
return parsed, err == nil
case uint64:
return int(v), true
parsed, err := uint64ToInt(v)
return parsed, err == nil
case float32:
if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
return 0, false
}
return int(math.Round(float64(v))), true
parsed, err := floatToIntRounded(float64(v))
return parsed, err == nil
case float64:
if math.IsNaN(v) || math.IsInf(v, 0) {
parsed, err := floatToIntRounded(v)
return parsed, err == nil
case json.Number:
s := strings.TrimSpace(v.String())
if i, err := v.Int64(); err == nil {
parsed, err := int64ToInt(i)
return parsed, err == nil
}
if !looksLikeFloatLiteral(s) {
return 0, false
}
return int(math.Round(v)), true
case json.Number:
if i, err := v.Int64(); err == nil {
return int(i), true
}
if f, err := v.Float64(); err == nil && !math.IsNaN(f) && !math.IsInf(f, 0) {
return int(math.Round(f)), true
if f, err := v.Float64(); err == nil {
parsed, err := floatToIntRounded(f)
return parsed, err == nil
}
case string:
s := strings.TrimSpace(v)
@ -402,15 +408,24 @@ func intFromAny(value interface{}) (int, bool) {
return 0, false
}
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return int(i), true
parsed, err := int64ToInt(i)
return parsed, err == nil
}
if f, err := strconv.ParseFloat(s, 64); err == nil && !math.IsNaN(f) && !math.IsInf(f, 0) {
return int(math.Round(f)), true
if !looksLikeFloatLiteral(s) {
return 0, false
}
if f, err := strconv.ParseFloat(s, 64); err == nil {
parsed, err := floatToIntRounded(f)
return parsed, err == nil
}
}
return 0, false
}
func looksLikeFloatLiteral(s string) bool {
return strings.ContainsAny(s, ".eE")
}
func boolFromAny(value interface{}) (bool, bool) {
switch v := value.(type) {
case nil:

View file

@ -94,6 +94,7 @@ func TestIntFromAny(t *testing.T) {
{"uint16", uint16(65535), 65535, true},
{"uint32", uint32(42), 42, true},
{"uint64", uint64(42), 42, true},
{"uint64 overflow", ^uint64(0), 0, false},
// float types (rounded)
{"float32 integer", float32(42.0), 42, true},
@ -107,16 +108,21 @@ func TestIntFromAny(t *testing.T) {
{"float64 NaN", math.NaN(), 0, false},
{"float64 +Inf", math.Inf(1), 0, false},
{"float64 -Inf", math.Inf(-1), 0, false},
{"float64 overflow", math.MaxFloat64, 0, false},
// json.Number
{"json.Number int", json.Number("42"), 42, true},
{"json.Number float", json.Number("42.6"), 43, true},
{"json.Number scientific float", json.Number("4.2e1"), 42, true},
{"json.Number oversized int", json.Number("9223372036854775808"), 0, false},
{"json.Number invalid", json.Number("abc"), 0, false},
// string
{"string int", "42", 42, true},
{"string negative", "-42", -42, true},
{"string float", "42.6", 43, true},
{"string scientific float", "4.2e1", 42, true},
{"string oversized int", "9223372036854775808", 0, false},
{"string empty", "", 0, false},
{"string whitespace", " 42 ", 42, true},
{"string invalid", "abc", 0, false},