types: persist Node JSON slices via named IsZero types

Endpoints, Tags and ApprovedRoutes serialize as JSON on Node. GORM's
struct Updates path skips fields it considers zero, and reflect treats
a nil slice as zero — clearing any of these columns via the State
persist path would leave the previous value in the database.

Introduce Strings, Prefixes and AddrPorts as named slice types whose
IsZero() always reports false, so GORM keeps the column in the UPDATE
regardless of the slice being nil or empty. JSON marshalling is
unchanged: nil serializes to null, empty to []. List() returns the
underlying unnamed slice for callers (mainly testify assertions over
reflect.DeepEqual) that distinguish the named type from its base.

Regenerated types_clone.go and types_view.go follow the field-type
swap. Test assertions across hscontrol/{db,state,servertest} updated
to call .List() where reflect.DeepEqual previously matched the raw
slice type.

Fixes #3110
This commit is contained in:
Kristoffer Dalby
2026-05-13 09:53:01 +00:00
parent e78a24b892
commit 7a20db9f49
8 changed files with 64 additions and 18 deletions

View File

@@ -120,7 +120,7 @@ type Node struct {
NodeKey key.NodePublic `gorm:"serializer:text"`
DiscoKey key.DiscoPublic `gorm:"serializer:text"`
Endpoints []netip.AddrPort `gorm:"serializer:json"`
Endpoints AddrPorts `gorm:"serializer:json"`
Hostinfo *tailcfg.Hostinfo `gorm:"column:host_info;serializer:json"`
@@ -150,7 +150,7 @@ type Node struct {
// When non-empty, the node is "tagged" and tags define its identity.
// Empty for user-owned nodes.
// Tags cannot be removed once set (one-way transition).
Tags []string `gorm:"column:tags;serializer:json"`
Tags Strings `gorm:"column:tags;serializer:json"`
// When a node has been created with a PreAuthKey, we need to
// prevent the preauthkey from being deleted before the node.
@@ -169,7 +169,7 @@ type Node struct {
// as a subnet router. They are not necessarily the routes that the node
// announces at the moment.
// See [Node.Hostinfo]
ApprovedRoutes []netip.Prefix `gorm:"column:approved_routes;serializer:json"`
ApprovedRoutes Prefixes `gorm:"column:approved_routes;serializer:json"`
CreatedAt time.Time
UpdatedAt time.Time