From 71b1a43b6eccc0aef91030b894414fda8f0b48ca Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 6 Feb 2026 07:24:16 +0000 Subject: [PATCH] all: fix err113 and nilnil lint issues (batch 4) - Add sentinel errors and use %w wrapping for err113 compliance - Add nolint:nilnil comments for valid nil,nil returns in config - Files modified: hscontrol/db/ip.go, node.go, text_serialiser.go, users.go, hscontrol/noise.go, hscontrol/tailsql.go, hscontrol/types/config.go, config_test.go Co-Authored-By: Claude Opus 4.5 --- hscontrol/db/ip.go | 11 ++++++++--- hscontrol/db/node.go | 5 ++++- hscontrol/db/text_serialiser.go | 13 ++++++++++--- hscontrol/db/users.go | 12 +++++++----- hscontrol/noise.go | 5 ++++- hscontrol/tailsql.go | 5 ++++- hscontrol/types/config.go | 4 ++-- hscontrol/types/config_test.go | 2 +- 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/hscontrol/db/ip.go b/hscontrol/db/ip.go index 4a17765c..7402f473 100644 --- a/hscontrol/db/ip.go +++ b/hscontrol/db/ip.go @@ -17,7 +17,11 @@ import ( "tailscale.com/net/tsaddr" ) -var errGeneratedIPBytesInvalid = errors.New("generated ip bytes are invalid ip") +var ( + errGeneratedIPBytesInvalid = errors.New("generated ip bytes are invalid ip") + errGeneratedIPNotInPrefix = errors.New("generated ip not in prefix") + errIPAllocatorNil = errors.New("ip allocator was nil") +) // IPAllocator is a singleton responsible for allocating // IP addresses for nodes and making sure the same @@ -251,7 +255,8 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) { if !pfx.Contains(ip) { return netip.Addr{}, fmt.Errorf( - "generated ip(%s) not in prefix(%s)", + "%w: ip(%s) not in prefix(%s)", + errGeneratedIPNotInPrefix, ip.String(), pfx.String(), ) @@ -283,7 +288,7 @@ func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) { err = db.Write(func(tx *gorm.DB) error { if i == nil { - return errors.New("backfilling IPs: ip allocator was nil") + return fmt.Errorf("backfilling IPs: %w", errIPAllocatorNil) } log.Trace().Caller().Msgf("starting to backfill IPs") diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index 00175ade..46dd58ba 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -29,6 +29,9 @@ const ( NodeGivenNameTrimSize = 2 ) +// ErrNodeNameNotUnique is returned when a node name is not unique. +var ErrNodeNameNotUnique = errors.New("node name is not unique") + var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+") var ( @@ -300,7 +303,7 @@ func RenameNode(tx *gorm.DB, } if count > 0 { - return errors.New("name is not unique") + return ErrNodeNameNotUnique } if err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("given_name", newName).Error; err != nil { diff --git a/hscontrol/db/text_serialiser.go b/hscontrol/db/text_serialiser.go index 59c897bb..0a7a7362 100644 --- a/hscontrol/db/text_serialiser.go +++ b/hscontrol/db/text_serialiser.go @@ -3,12 +3,19 @@ package db import ( "context" "encoding" + "errors" "fmt" "reflect" "gorm.io/gorm/schema" ) +var ( + errUnmarshalTextValue = errors.New("unmarshalling text value") + errUnsupportedType = errors.New("unsupported type") + errTextMarshalerOnly = errors.New("only encoding.TextMarshaler is supported") +) + // Got from https://github.com/xdg-go/strum/blob/main/types.go var textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() @@ -49,7 +56,7 @@ func (TextSerialiser) Scan(ctx context.Context, field *schema.Field, dst reflect case string: bytes = []byte(v) default: - return fmt.Errorf("unmarshalling text value: %#v", dbValue) + return fmt.Errorf("%w: %#v", errUnmarshalTextValue, dbValue) } if isTextUnmarshaler(fieldValue) { @@ -75,7 +82,7 @@ func (TextSerialiser) Scan(ctx context.Context, field *schema.Field, dst reflect return nil } else { - return fmt.Errorf("unsupported type: %T", fieldValue.Interface()) + return fmt.Errorf("%w: %T", errUnsupportedType, fieldValue.Interface()) } } @@ -99,6 +106,6 @@ func (TextSerialiser) Value(ctx context.Context, field *schema.Field, dst reflec return string(b), nil default: - return nil, fmt.Errorf("only encoding.TextMarshaler is supported, got %t", v) + return nil, fmt.Errorf("%w, got %T", errTextMarshalerOnly, v) } } diff --git a/hscontrol/db/users.go b/hscontrol/db/users.go index 5de8374a..e95a192d 100644 --- a/hscontrol/db/users.go +++ b/hscontrol/db/users.go @@ -12,9 +12,11 @@ import ( ) var ( - ErrUserExists = errors.New("user already exists") - ErrUserNotFound = errors.New("user not found") - ErrUserStillHasNodes = errors.New("user not empty: node(s) found") + ErrUserExists = errors.New("user already exists") + ErrUserNotFound = errors.New("user not found") + ErrUserStillHasNodes = errors.New("user not empty: node(s) found") + ErrUserWhereInvalidCount = errors.New("expect 0 or 1 where User structs") + ErrUserNotUnique = errors.New("expected exactly one user") ) func (hsdb *HSDatabase) CreateUser(user types.User) (*types.User, error) { @@ -158,7 +160,7 @@ func (hsdb *HSDatabase) ListUsers(where ...*types.User) ([]types.User, error) { // ListUsers gets all the existing users. func ListUsers(tx *gorm.DB, where ...*types.User) ([]types.User, error) { if len(where) > 1 { - return nil, fmt.Errorf("expect 0 or 1 where User structs, got %d", len(where)) + return nil, fmt.Errorf("%w, got %d", ErrUserWhereInvalidCount, len(where)) } var user *types.User @@ -189,7 +191,7 @@ func (hsdb *HSDatabase) GetUserByName(name string) (*types.User, error) { } if len(users) != 1 { - return nil, fmt.Errorf("expected exactly one user, found %d", len(users)) + return nil, fmt.Errorf("%w, found %d", ErrUserNotUnique, len(users)) } return &users[0], nil diff --git a/hscontrol/noise.go b/hscontrol/noise.go index d43c7642..a09d9340 100644 --- a/hscontrol/noise.go +++ b/hscontrol/noise.go @@ -19,6 +19,9 @@ import ( "tailscale.com/types/key" ) +// ErrUnsupportedClientVersion is returned when a client connects with an unsupported protocol version. +var ErrUnsupportedClientVersion = errors.New("unsupported client version") + const ( // ts2021UpgradePath is the path that the server listens on for the WebSockets upgrade. ts2021UpgradePath = "/ts2021" @@ -117,7 +120,7 @@ func (h *Headscale) NoiseUpgradeHandler( } func unsupportedClientError(version tailcfg.CapabilityVersion) error { - return fmt.Errorf("unsupported client version: %s (%d)", capver.TailscaleVersion(version), version) + return fmt.Errorf("%w: %s (%d)", ErrUnsupportedClientVersion, capver.TailscaleVersion(version), version) } func (ns *noiseServer) earlyNoise(protocolVersion int, writer io.Writer) error { diff --git a/hscontrol/tailsql.go b/hscontrol/tailsql.go index e891e30f..2e3509a2 100644 --- a/hscontrol/tailsql.go +++ b/hscontrol/tailsql.go @@ -13,6 +13,9 @@ import ( "tailscale.com/types/logger" ) +// ErrNoCertDomains is returned when no cert domains are available for HTTPS. +var ErrNoCertDomains = errors.New("no cert domains available for HTTPS") + func runTailSQLService(ctx context.Context, logf logger.Logf, stateDir, dbPath string) error { opts := tailsql.Options{ Hostname: "tailsql-headscale", @@ -73,7 +76,7 @@ func runTailSQLService(ctx context.Context, logf logger.Logf, stateDir, dbPath s // When serving TLS, add a redirect from HTTP on port 80 to HTTPS on 443. certDomains := tsNode.CertDomains() if len(certDomains) == 0 { - return errors.New("no cert domains available for HTTPS") + return ErrNoCertDomains } base := "https://" + certDomains[0] diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index e58f5e5c..418c0faf 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -845,7 +845,7 @@ func prefixV4() (*netip.Prefix, error) { prefixV4Str := viper.GetString("prefixes.v4") if prefixV4Str == "" { - return nil, nil + return nil, nil //nolint:nilnil // empty prefix is valid, not an error } prefixV4, err := netip.ParsePrefix(prefixV4Str) @@ -870,7 +870,7 @@ func prefixV6() (*netip.Prefix, error) { prefixV6Str := viper.GetString("prefixes.v6") if prefixV6Str == "" { - return nil, nil + return nil, nil //nolint:nilnil // empty prefix is valid, not an error } prefixV6, err := netip.ParsePrefix(prefixV6Str) diff --git a/hscontrol/types/config_test.go b/hscontrol/types/config_test.go index 0bc82d56..e1e88087 100644 --- a/hscontrol/types/config_test.go +++ b/hscontrol/types/config_test.go @@ -284,7 +284,7 @@ func TestReadConfigFromEnv(t *testing.T) { assert.Equal(t, "100.64.0.0/10", viper.GetString("prefixes.v4")) assert.False(t, viper.GetBool("database.sqlite.write_ahead_log")) - return nil, nil + return nil, nil //nolint:nilnil // test setup returns nil to indicate no expected value }, want: nil, },