feat: show Ubuntu phased updates apart and leave them out of pending counts
Chart Release / chart (push) Successful in 29s
Server Deploy / deploy (push) Successful in 5m9s

apt lists phased updates as upgradable while an upgrade defers them until
Ubuntu selects the host, so a freshly patched server kept reporting pending
updates. The agent now flags them; the server stores the flag and leaves them
out of patch run counts, and the server page shows them in their own section.
This commit is contained in:
2026-09-15 14:55:30 +00:00
parent 0e464c4bb8
commit fbcf436ef6
13 changed files with 87 additions and 15 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ require (
)
require (
gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0
gitea.hostxtra.co.uk/vantage/vantage-shared v0.6.0
github.com/bytedance/sonic v1.15.3 // indirect
github.com/bytedance/sonic/loader v0.5.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+2 -2
View File
@@ -1,5 +1,5 @@
gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0 h1:xwSIEkQKTd4Qk+BYHvoGN+h84Isr2h5qqnitUWF1m2w=
gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0/go.mod h1:Zo66XhqF8No3dveIowLCepvMxVg8KnhsNMz0k0Xpuck=
gitea.hostxtra.co.uk/vantage/vantage-shared v0.6.0 h1:EtojZ1d3cN9foHpc/CAI3KzBewYGn4sKWdkWs2MV78Q=
gitea.hostxtra.co.uk/vantage/vantage-shared v0.6.0/go.mod h1:Zo66XhqF8No3dveIowLCepvMxVg8KnhsNMz0k0Xpuck=
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM=
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
github.com/aquasecurity/trivy-db v0.0.0-20260813095258-0e0340a01b57 h1:A3Lz/9ip/qigafSxqBWcu7S8i+tJbQS7DB2V0XibOKs=
+4
View File
@@ -1389,6 +1389,10 @@
},
"new_version": {
"type": "string"
},
"phased": {
"description": "Phased is an Ubuntu phased update the host is not yet selected for: apt\nlists it but an upgrade defers it, so pending counts leave it out.",
"type": "boolean"
}
},
"type": "object"
+1
View File
@@ -103,6 +103,7 @@ func (s *vantageServer) ReportUpdates(ctx context.Context, req *pb.ReportUpdates
Name: u.Name,
CurrentVersion: u.CurrentVersion,
NewVersion: u.NewVersion,
Phased: u.Phased,
}
}
if err := services.StoreAvailableUpdates(srv.ServerID, pkgs); err != nil {
+4 -2
View File
@@ -80,6 +80,8 @@ type pendingUpdate struct {
Package string `json:"package"`
CurrentVersion string `json:"current_version,omitempty"`
NewVersion string `json:"new_version"`
// Phased is an Ubuntu phased update apt defers until the host is selected.
Phased bool `json:"phased,omitempty"`
}
type listPendingUpdatesResult struct {
@@ -390,7 +392,7 @@ func init() {
}
out = append(out, pendingUpdate{
ServerID: srv.ServerID, Hostname: srv.Hostname,
Package: u.Name, CurrentVersion: u.CurrentVersion, NewVersion: u.NewVersion,
Package: u.Name, CurrentVersion: u.CurrentVersion, NewVersion: u.NewVersion, Phased: u.Phased,
})
}
return listPendingUpdatesResult{Updates: out, Shown: len(out)}, nil
@@ -411,7 +413,7 @@ func init() {
}
out = append(out, pendingUpdate{
ServerID: srv.ServerID, Hostname: srv.Hostname,
Package: u.Name, CurrentVersion: u.CurrentVersion, NewVersion: u.NewVersion,
Package: u.Name, CurrentVersion: u.CurrentVersion, NewVersion: u.NewVersion, Phased: u.Phased,
})
}
}
+3
View File
@@ -10,6 +10,9 @@ type PackageUpdate struct {
Name string `bson:"name" json:"name"`
CurrentVersion string `bson:"current_version,omitempty" json:"current_version,omitempty"`
NewVersion string `bson:"new_version" json:"new_version"`
// Phased is an Ubuntu phased update the host is not yet selected for: apt
// lists it but an upgrade defers it, so pending counts leave it out.
Phased bool `bson:"phased,omitempty" json:"phased,omitempty"`
}
type CPUInfo struct {
+1 -1
View File
@@ -83,7 +83,7 @@ func serverBootTime(ctx context.Context, instanceID, serverID string) *time.Time
}
func newServerRun(s models.Server, now time.Time) models.PatchServerRun {
r := models.PatchServerRun{ServerID: s.ServerID, Hostname: s.Hostname, Status: models.PatchSrvQueued, PendingBefore: len(s.AvailableUpdates)}
r := models.PatchServerRun{ServerID: s.ServerID, Hostname: s.Hostname, Status: models.PatchSrvQueued, PendingBefore: InstallableUpdateCount(s.AvailableUpdates)}
if !patchrun.AgentSupportsPatchResults(s.AgentVersion) {
v := s.AgentVersion
if v == "" {
+17
View File
@@ -0,0 +1,17 @@
package services
import "gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
// InstallableUpdateCount is the number of pending updates an upgrade would
// install now. Ubuntu phased updates are listed by apt but deferred until the
// host's phase comes up, so they are not counted: counting them made a patch
// run look as if it installed less than it did.
func InstallableUpdateCount(ups []models.PackageUpdate) int {
n := 0
for _, u := range ups {
if !u.Phased {
n++
}
}
return n
}
@@ -0,0 +1,23 @@
package services
import (
"testing"
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
)
// Phased updates are listed but not installable yet, so they never count as
// pending: they must not lower "updates installed" on a patch run.
func TestInstallableUpdateCount(t *testing.T) {
ups := []models.PackageUpdate{
{Name: "curl"},
{Name: "netplan.io", Phased: true},
{Name: "openssl"},
}
if got := InstallableUpdateCount(ups); got != 2 {
t.Fatalf("got %d, want 2", got)
}
if got := InstallableUpdateCount(nil); got != 0 {
t.Fatalf("got %d for nil, want 0", got)
}
}