types: drop the proto builders

Remove the Proto() methods and RegisterMethodToV1Enum; the v1 API builds
responses from the state views directly now.
This commit is contained in:
Kristoffer Dalby
2026-06-17 18:04:40 +00:00
parent 8f5e2e7291
commit db6b99d414
6 changed files with 1 additions and 235 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ func TestPreAuthKeyACLTags(t *testing.T) {
require.NoError(t, err)
require.Len(t, listedPaks, 1)
gotTags := listedPaks[0].Proto().GetAclTags()
gotTags := listedPaks[0].Tags
slices.Sort(gotTags)
assert.Equal(t, expectedTags, gotTags)
},
-24
View File
@@ -3,10 +3,8 @@ package types
import (
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog"
"google.golang.org/protobuf/types/known/timestamppb"
)
// NewAPIKeyPrefixLength is the length of the prefix for new API keys.
@@ -24,28 +22,6 @@ type APIKey struct {
LastSeen *time.Time
}
func (key *APIKey) Proto() *v1.ApiKey {
protoKey := v1.ApiKey{
Id: key.ID,
}
protoKey.Prefix = key.maskedPrefix()
if key.Expiration != nil {
protoKey.Expiration = timestamppb.New(*key.Expiration)
}
if key.CreatedAt != nil {
protoKey.CreatedAt = timestamppb.New(*key.CreatedAt)
}
if key.LastSeen != nil {
protoKey.LastSeen = timestamppb.New(*key.LastSeen)
}
return &protoKey
}
// maskedPrefix returns the API key prefix in masked format for safe logging.
// SECURITY: Never log the full key or hash, only the masked prefix.
func (k *APIKey) maskedPrefix() string {
-73
View File
@@ -10,13 +10,11 @@ import (
"strings"
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/policy/matcher"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog"
"go4.org/netipx"
"google.golang.org/protobuf/types/known/timestamppb"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
@@ -483,55 +481,6 @@ func (nodes Nodes) ContainsNodeKey(nodeKey key.NodePublic) bool {
return false
}
func (node *Node) Proto() *v1.Node {
nodeProto := &v1.Node{
Id: uint64(node.ID),
MachineKey: node.MachineKey.String(),
NodeKey: node.NodeKey.String(),
DiscoKey: node.DiscoKey.String(),
// TODO(kradalby): replace list with v4, v6 field?
IpAddresses: node.IPsAsString(),
Name: node.Hostname,
GivenName: node.GivenName,
User: nil, // Will be set below based on node type
Tags: node.Tags,
Online: node.IsOnline != nil && *node.IsOnline,
// Only ApprovedRoutes and AvailableRoutes is set here. SubnetRoutes has
// to be populated manually with PrimaryRoute, to ensure it includes the
// routes that are actively served from the node.
ApprovedRoutes: util.PrefixesToString(node.ApprovedRoutes),
AvailableRoutes: util.PrefixesToString(node.AnnouncedRoutes()),
RegisterMethod: node.RegisterMethodToV1Enum(),
CreatedAt: timestamppb.New(node.CreatedAt),
}
// Set User field based on node ownership
// Note: User will be set to [TaggedDevices] in the gRPC layer (grpcv1.go)
// for proper [tailcfg.MapResponse] formatting
if node.User != nil {
nodeProto.User = node.User.Proto()
}
if node.AuthKey != nil {
nodeProto.PreAuthKey = node.AuthKey.Proto()
}
if node.LastSeen != nil {
nodeProto.LastSeen = timestamppb.New(*node.LastSeen)
}
if node.Expiry != nil {
nodeProto.Expiry = timestamppb.New(*node.Expiry)
}
return nodeProto
}
func (node *Node) GetFQDN(baseDomain string) (string, error) {
if node.GivenName == "" {
return "", fmt.Errorf("creating valid FQDN: %w", ErrNodeHasNoGivenName)
@@ -684,19 +633,6 @@ func EndpointsChanged(oldEndpoints, newEndpoints []netip.AddrPort) bool {
return !equalUnordered(oldEndpoints, newEndpoints, netip.AddrPort.Compare)
}
func (node *Node) RegisterMethodToV1Enum() v1.RegisterMethod {
switch node.RegisterMethod {
case "authkey":
return v1.RegisterMethod_REGISTER_METHOD_AUTH_KEY
case "oidc":
return v1.RegisterMethod_REGISTER_METHOD_OIDC
case "cli":
return v1.RegisterMethod_REGISTER_METHOD_CLI
default:
return v1.RegisterMethod_REGISTER_METHOD_UNSPECIFIED
}
}
// ApplyPeerChange takes a [tailcfg.PeerChange] struct and updates the node.
func (node *Node) ApplyPeerChange(change *tailcfg.PeerChange) {
if change.Key != nil {
@@ -998,15 +934,6 @@ func (nv NodeView) RequestTags() []string {
return nv.Hostinfo().RequestTags().AsSlice()
}
// Proto converts the [NodeView] to a protobuf representation.
func (nv NodeView) Proto() *v1.Node {
if !nv.Valid() {
return nil
}
return nv.ж.Proto()
}
// HasIP reports if a node has a given IP address.
func (nv NodeView) HasIP(i netip.Addr) bool {
if !nv.Valid() {
-51
View File
@@ -8,7 +8,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/policy/matcher"
"github.com/juanfont/headscale/hscontrol/util"
"tailscale.com/tailcfg"
@@ -657,56 +656,6 @@ func TestApplyPeerChange(t *testing.T) {
}
}
func TestNodeRegisterMethodToV1Enum(t *testing.T) {
tests := []struct {
name string
node Node
want v1.RegisterMethod
}{
{
name: "authkey",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodAuthKey,
},
want: v1.RegisterMethod_REGISTER_METHOD_AUTH_KEY,
},
{
name: "oidc",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodOIDC,
},
want: v1.RegisterMethod_REGISTER_METHOD_OIDC,
},
{
name: "cli",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodCLI,
},
want: v1.RegisterMethod_REGISTER_METHOD_CLI,
},
{
name: "unknown",
node: Node{
ID: 0,
},
want: v1.RegisterMethod_REGISTER_METHOD_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.node.RegisterMethodToV1Enum()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("RegisterMethodToV1Enum() unexpected result (-want +got):\n%s", diff)
}
})
}
}
// TestHasNetworkChanges tests the [NodeView] method for detecting
// when a node's network properties have changed.
func TestHasNetworkChanges(t *testing.T) {
-62
View File
@@ -3,11 +3,9 @@ package types
import (
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"google.golang.org/protobuf/types/known/timestamppb"
)
type PAKError string
@@ -56,66 +54,6 @@ type PreAuthKeyNew struct {
User *User // Can be nil for system-created tagged keys
}
func (key *PreAuthKeyNew) Proto() *v1.PreAuthKey {
protoKey := v1.PreAuthKey{
Id: key.ID,
Key: key.Key,
User: nil, // Will be set below if not nil
Reusable: key.Reusable,
Ephemeral: key.Ephemeral,
AclTags: key.Tags,
}
if key.User != nil {
protoKey.User = key.User.Proto()
}
if key.Expiration != nil {
protoKey.Expiration = timestamppb.New(*key.Expiration)
}
if key.CreatedAt != nil {
protoKey.CreatedAt = timestamppb.New(*key.CreatedAt)
}
return &protoKey
}
func (key *PreAuthKey) Proto() *v1.PreAuthKey {
protoKey := v1.PreAuthKey{
User: nil, // Will be set below if not nil
Id: key.ID,
Ephemeral: key.Ephemeral,
Reusable: key.Reusable,
Used: key.Used,
AclTags: key.Tags,
}
if key.User != nil {
protoKey.User = key.User.Proto()
}
// For new keys (with prefix/hash), show the prefix so users can identify the key
// For legacy keys (with plaintext key), show the full key for backwards compatibility
if masked := key.maskedPrefix(); masked != "" {
protoKey.Key = masked
} else if key.Key != "" {
// Legacy key - show full key for backwards compatibility
// TODO: Consider hiding this in a future major version
protoKey.Key = key.Key
}
if key.Expiration != nil {
protoKey.Expiration = timestamppb.New(*key.Expiration)
}
if key.CreatedAt != nil {
protoKey.CreatedAt = timestamppb.New(*key.CreatedAt)
}
return &protoKey
}
// Validate checks if a pre auth key can be used.
func (pak *PreAuthKey) Validate() error {
if pak == nil {
-24
View File
@@ -11,12 +11,10 @@ import (
"strconv"
"strings"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
"tailscale.com/tailcfg"
)
@@ -192,28 +190,6 @@ func (u UserView) TailscaleUserProfile() tailcfg.UserProfile {
return u.ж.TailscaleUserProfile()
}
func (u *User) Proto() *v1.User {
// Use Name if set, otherwise fall back to Username() which provides
// a display-friendly identifier (Email > ProviderIdentifier > ID).
// This ensures OIDC users (who typically have empty Name) display
// their email, while CLI users retain their original Name.
name := u.Name
if name == "" {
name = u.Username()
}
return &v1.User{
Id: uint64(u.ID),
Name: name,
CreatedAt: timestamppb.New(u.CreatedAt),
DisplayName: u.DisplayName,
Email: u.Email,
ProviderId: u.ProviderIdentifier.String,
Provider: u.Provider,
ProfilePicUrl: u.ProfilePicURL,
}
}
// MarshalZerologObject implements [zerolog.LogObjectMarshaler] for safe logging.
func (u *User) MarshalZerologObject(e *zerolog.Event) {
if u == nil {