From fad54d7c2047dcde91a8246e9ec261ba833a4831 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Wed, 5 Nov 2025 16:42:48 -0800 Subject: [PATCH] all: migrate omitempty to omitzero These are statically discovered using #17670. This migrates all usages of omitempty to instead use omitzero where it is safe to do so. These should behave the same as before, but allow the behavior to be identical between v1 and v2. Updates tailscale/corp#791 Signed-off-by: Joe Tsai --- client/web/web.go | 2 +- cmd/k8s-operator/ingress-for-pg.go | 2 +- cmd/tailscale/cli/configure-synology-cert.go | 2 +- cmd/vet/jsontags_allowlist | 98 -------------------- health/state.go | 2 +- internal/client/tailscale/vip_service.go | 2 +- ipn/auditlog/auditlog.go | 2 +- ipn/backend.go | 2 +- ipn/conf.go | 40 ++++---- ipn/ipnstate/ipnstate.go | 12 +-- ipn/prefs.go | 6 +- kube/ingressservices/ingressservices.go | 4 +- kube/k8s-proxy/conf/conf.go | 34 +++---- kube/kubeclient/client.go | 2 +- kube/kubetypes/grants.go | 2 +- kube/kubetypes/types_test.go | 2 +- sessionrecording/header.go | 2 +- tailcfg/derpmap.go | 2 +- tailcfg/tailcfg.go | 64 ++++++------- tsweb/log.go | 2 +- types/opt/bool_test.go | 2 +- types/views/views_test.go | 4 +- wgengine/magicsock/endpoint.go | 4 +- 23 files changed, 98 insertions(+), 196 deletions(-) diff --git a/client/web/web.go b/client/web/web.go index dbd3d5df0..39d85ce1f 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -706,7 +706,7 @@ func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) { type authResponse struct { ServerMode ServerMode `json:"serverMode"` Authorized bool `json:"authorized"` // has an authorized management session - ViewerIdentity *viewerIdentity `json:"viewerIdentity,omitempty"` + ViewerIdentity *viewerIdentity `json:"viewerIdentity,omitzero"` NeedsSynoAuth bool `json:"needsSynoAuth,omitempty"` } diff --git a/cmd/k8s-operator/ingress-for-pg.go b/cmd/k8s-operator/ingress-for-pg.go index 3afeb528f..f1414a8bd 100644 --- a/cmd/k8s-operator/ingress-for-pg.go +++ b/cmd/k8s-operator/ingress-for-pg.go @@ -860,7 +860,7 @@ type ownerAnnotationValue struct { type OwnerRef struct { // OperatorID is the stable ID of the operator's Tailscale device. OperatorID string `json:"operatorID,omitempty"` - Resource *Resource `json:"resource,omitempty"` // optional, used to identify the ProxyGroup that owns this Tailscale Service. + Resource *Resource `json:"resource,omitzero"` // optional, used to identify the ProxyGroup that owns this Tailscale Service. } type Resource struct { diff --git a/cmd/tailscale/cli/configure-synology-cert.go b/cmd/tailscale/cli/configure-synology-cert.go index b5168ef92..e219e75f3 100644 --- a/cmd/tailscale/cli/configure-synology-cert.go +++ b/cmd/tailscale/cli/configure-synology-cert.go @@ -194,7 +194,7 @@ type synoAPICaller interface { type apiResponse struct { Success bool `json:"success"` - Error *apiError `json:"error,omitempty"` + Error *apiError `json:"error,omitzero"` Data json.RawMessage `json:"data"` } diff --git a/cmd/vet/jsontags_allowlist b/cmd/vet/jsontags_allowlist index 060a81b05..68f05f3ef 100644 --- a/cmd/vet/jsontags_allowlist +++ b/cmd/vet/jsontags_allowlist @@ -1,39 +1,3 @@ -OmitEmptyShouldBeOmitZero tailscale.com/client/web.authResponse.ViewerIdentity -OmitEmptyShouldBeOmitZero tailscale.com/cmd/k8s-operator.OwnerRef.Resource -OmitEmptyShouldBeOmitZero tailscale.com/cmd/tailscale/cli.apiResponse.Error -OmitEmptyShouldBeOmitZero tailscale.com/health.UnhealthyState.PrimaryAction -OmitEmptyShouldBeOmitZero tailscale.com/internal/client/tailscale.VIPService.Name -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AcceptDNS -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AcceptRoutes -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AllowLANWhileUsingExitNode -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AppConnector -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AuthKey -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.AutoUpdate -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.DisableSNAT -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.Enabled -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.ExitNode -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.Hostname -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.Locked -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.NetfilterMode -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.NoStatefulFiltering -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.OperatorUser -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.PostureChecking -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.RunSSHServer -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.RunWebClient -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.ServeConfigTemp -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.ServerURL -OmitEmptyShouldBeOmitZero tailscale.com/ipn.ConfigVAlpha.ShieldsUp -OmitEmptyShouldBeOmitZero tailscale.com/ipn.OutgoingFile.PeerID -OmitEmptyShouldBeOmitZero tailscale.com/ipn.Prefs.AutoExitNode -OmitEmptyShouldBeOmitZero tailscale.com/ipn.Prefs.NoStatefulFiltering -OmitEmptyShouldBeOmitZero tailscale.com/ipn.Prefs.RelayServerPort -OmitEmptyShouldBeOmitZero tailscale.com/ipn/auditlog.transaction.Action -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.PeerStatus.AllowedIPs -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.PeerStatus.Location -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.PeerStatus.PrimaryRoutes -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.PeerStatus.Tags -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.Status.ExitNodeStatus -OmitEmptyShouldBeOmitZero tailscale.com/ipn/ipnstate.UpdateProgress.Status OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.ConnectorSpec.AppConnector OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.ConnectorSpec.Hostname OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.ConnectorSpec.HostnamePrefix @@ -65,71 +29,9 @@ OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.RecorderPod.A OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.RecorderPod.SecurityContext OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.StatefulSet.Pod OmitEmptyShouldBeOmitZero tailscale.com/k8s-operator/apis/v1alpha1.Storage.S3 -OmitEmptyShouldBeOmitZero tailscale.com/kube/ingressservices.Config.IPv4Mapping -OmitEmptyShouldBeOmitZero tailscale.com/kube/ingressservices.Config.IPv6Mapping -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.APIServerProxyConfig.Enabled -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.APIServerProxyConfig.IssueCerts -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.APIServerProxyConfig.Mode -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.APIServerProxyConfig.ServiceName -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.AcceptRoutes -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.APIServerProxy -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.App -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.AuthKey -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.HealthCheckEnabled -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.Hostname -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.LocalAddr -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.LocalPort -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.LogLevel -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.MetricsEnabled -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.ServerURL -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.ConfigV1Alpha1.State -OmitEmptyShouldBeOmitZero tailscale.com/kube/k8s-proxy/conf.VersionedConfig.V1Alpha1 OmitEmptyShouldBeOmitZero tailscale.com/kube/kubeapi.ObjectMeta.DeletionGracePeriodSeconds OmitEmptyShouldBeOmitZero tailscale.com/kube/kubeapi.Status.Details -OmitEmptyShouldBeOmitZero tailscale.com/kube/kubeclient.JSONPatch.Value -OmitEmptyShouldBeOmitZero tailscale.com/kube/kubetypes.*.Mode -OmitEmptyShouldBeOmitZero tailscale.com/kube/kubetypes.KubernetesCapRule.Impersonate -OmitEmptyShouldBeOmitZero tailscale.com/sessionrecording.CastHeader.Kubernetes -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.AuditLogRequest.Action -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Debug.Exit -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.DERPMap.HomeParams -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.DisplayMessage.PrimaryAction -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.AppConnector -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.Container -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.Desktop -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.Location -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.NetInfo -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.StateEncrypted -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.TPM -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.Userspace -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Hostinfo.UserspaceRouter -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.ClientVersion -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.CollectServices -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.ControlDialPlan -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.Debug -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.DefaultAutoUpdate -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.DERPMap -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.DNSConfig -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.Node -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.PingRequest -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.SSHPolicy -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.MapResponse.TKAInfo -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.NetPortRange.Bits -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Node.Online -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Node.SelfNodeV4MasqAddrForThisPeer -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.Node.SelfNodeV6MasqAddrForThisPeer -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.PeerChange.Online -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.RegisterRequest.Auth -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.RegisterResponseAuth.Oauth2Token -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.SSHAction.OnRecordingFailure -OmitEmptyShouldBeOmitZero tailscale.com/tailcfg.SSHPrincipal.Node OmitEmptyShouldBeOmitZero tailscale.com/tempfork/acme.*.ExternalAccountBinding -OmitEmptyShouldBeOmitZero tailscale.com/tsweb.AccessLogRecord.RequestID -OmitEmptyShouldBeOmitZero tailscale.com/types/opt.*.Unset -OmitEmptyShouldBeOmitZero tailscale.com/types/views.viewStruct.AddrsPtr -OmitEmptyShouldBeOmitZero tailscale.com/types/views.viewStruct.StringsPtr -OmitEmptyShouldBeOmitZero tailscale.com/wgengine/magicsock.EndpointChange.From -OmitEmptyShouldBeOmitZero tailscale.com/wgengine/magicsock.EndpointChange.To OmitEmptyShouldBeOmitZeroButHasIsZero tailscale.com/types/persist.Persist.AttestationKey OmitEmptyUnsupportedInV1 tailscale.com/client/tailscale.KeyCapabilities.Devices OmitEmptyUnsupportedInV1 tailscale.com/client/tailscale/apitype.ExitNodeSuggestionResponse.Location diff --git a/health/state.go b/health/state.go index e6d937b6a..3ce78c653 100644 --- a/health/state.go +++ b/health/state.go @@ -41,7 +41,7 @@ type UnhealthyState struct { Args Args `json:",omitempty"` DependsOn []WarnableCode `json:",omitempty"` ImpactsConnectivity bool `json:",omitempty"` - PrimaryAction *UnhealthyStateAction `json:",omitempty"` + PrimaryAction *UnhealthyStateAction `json:",omitzero"` // ETag identifies a specific version of an UnhealthyState. If the contents // of the other fields of two UnhealthyStates are the same, the ETags will diff --git a/internal/client/tailscale/vip_service.go b/internal/client/tailscale/vip_service.go index 48c59ce45..e47c60654 100644 --- a/internal/client/tailscale/vip_service.go +++ b/internal/client/tailscale/vip_service.go @@ -17,7 +17,7 @@ import ( // VIPService is a Tailscale VIPService with Tailscale API JSON representation. type VIPService struct { // Name is a VIPService name in form svc:. - Name tailcfg.ServiceName `json:"name,omitempty"` + Name tailcfg.ServiceName `json:"name,omitzero"` // Addrs are the IP addresses of the VIP Service. There are two addresses: // the first is IPv4 and the second is IPv6. // When creating a new VIP Service, the IP addresses are optional: if no diff --git a/ipn/auditlog/auditlog.go b/ipn/auditlog/auditlog.go index 0460bc4e2..6fb3e4242 100644 --- a/ipn/auditlog/auditlog.go +++ b/ipn/auditlog/auditlog.go @@ -30,7 +30,7 @@ type transaction struct { Retries int `json:",omitempty"` // Action is the action to be logged. It must correspond to a known action in the control plane. - Action tailcfg.ClientAuditAction `json:",omitempty"` + Action tailcfg.ClientAuditAction `json:",omitzero"` // Details is an opaque string specific to the action being logged. Empty strings may not // be valid depending on the action being logged. Details string `json:",omitempty"` diff --git a/ipn/backend.go b/ipn/backend.go index 91cf81ca5..2b1ee6c78 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -234,7 +234,7 @@ type PartialFile struct { // OutgoingFile represents an in-progress outgoing file transfer. type OutgoingFile struct { ID string `json:",omitempty"` // unique identifier for this transfer (a type 4 UUID) - PeerID tailcfg.StableNodeID `json:",omitempty"` // identifier for the peer to which this is being transferred + PeerID tailcfg.StableNodeID `json:",omitzero"` // identifier for the peer to which this is being transferred Name string `json:",omitempty"` // e.g. "foo.jpg" Started time.Time // time transfer started DeclaredSize int64 // or -1 if unknown diff --git a/ipn/conf.go b/ipn/conf.go index 2c9fb2fd1..f75c8bdfe 100644 --- a/ipn/conf.go +++ b/ipn/conf.go @@ -14,37 +14,37 @@ import ( // ConfigVAlpha is the config file format for the "alpha0" version. type ConfigVAlpha struct { Version string // "alpha0" for now - Locked opt.Bool `json:",omitempty"` // whether the config is locked from being changed by 'tailscale set'; it defaults to true + Locked opt.Bool `json:",omitzero"` // whether the config is locked from being changed by 'tailscale set'; it defaults to true - ServerURL *string `json:",omitempty"` // defaults to https://controlplane.tailscale.com - AuthKey *string `json:",omitempty"` // as needed if NeedsLogin. either key or path to a file (if prefixed with "file:") - Enabled opt.Bool `json:",omitempty"` // wantRunning; empty string defaults to true + ServerURL *string `json:",omitzero"` // defaults to https://controlplane.tailscale.com + AuthKey *string `json:",omitzero"` // as needed if NeedsLogin. either key or path to a file (if prefixed with "file:") + Enabled opt.Bool `json:",omitzero"` // wantRunning; empty string defaults to true - OperatorUser *string `json:",omitempty"` // local user name who is allowed to operate tailscaled without being root or using sudo - Hostname *string `json:",omitempty"` + OperatorUser *string `json:",omitzero"` // local user name who is allowed to operate tailscaled without being root or using sudo + Hostname *string `json:",omitzero"` - AcceptDNS opt.Bool `json:"acceptDNS,omitempty"` // --accept-dns - AcceptRoutes opt.Bool `json:"acceptRoutes,omitempty"` // --accept-routes defaults to true + AcceptDNS opt.Bool `json:"acceptDNS,omitzero"` // --accept-dns + AcceptRoutes opt.Bool `json:"acceptRoutes,omitzero"` // --accept-routes defaults to true - ExitNode *string `json:"exitNode,omitempty"` // IP, StableID, or MagicDNS base name - AllowLANWhileUsingExitNode opt.Bool `json:"allowLANWhileUsingExitNode,omitempty"` + ExitNode *string `json:"exitNode,omitzero"` // IP, StableID, or MagicDNS base name + AllowLANWhileUsingExitNode opt.Bool `json:"allowLANWhileUsingExitNode,omitzero"` AdvertiseRoutes []netip.Prefix `json:",omitempty"` - DisableSNAT opt.Bool `json:",omitempty"` + DisableSNAT opt.Bool `json:",omitzero"` AdvertiseServices []string `json:",omitempty"` - AppConnector *AppConnectorPrefs `json:",omitempty"` // advertise app connector; defaults to false (if nil or explicitly set to false) + AppConnector *AppConnectorPrefs `json:",omitzero"` // advertise app connector; defaults to false (if nil or explicitly set to false) - NetfilterMode *string `json:",omitempty"` // "on", "off", "nodivert" - NoStatefulFiltering opt.Bool `json:",omitempty"` + NetfilterMode *string `json:",omitzero"` // "on", "off", "nodivert" + NoStatefulFiltering opt.Bool `json:",omitzero"` - PostureChecking opt.Bool `json:",omitempty"` - RunSSHServer opt.Bool `json:",omitempty"` // Tailscale SSH - RunWebClient opt.Bool `json:",omitempty"` - ShieldsUp opt.Bool `json:",omitempty"` - AutoUpdate *AutoUpdatePrefs `json:",omitempty"` - ServeConfigTemp *ServeConfig `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this + PostureChecking opt.Bool `json:",omitzero"` + RunSSHServer opt.Bool `json:",omitzero"` // Tailscale SSH + RunWebClient opt.Bool `json:",omitzero"` + ShieldsUp opt.Bool `json:",omitzero"` + AutoUpdate *AutoUpdatePrefs `json:",omitzero"` + ServeConfigTemp *ServeConfig `json:",omitzero"` // TODO(bradfitz,maisem): make separate stable type for this // StaticEndpoints are additional, user-defined endpoints that this node // should advertise amongst its wireguard endpoints. diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index e7ae2d62b..ec9ac6356 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -51,7 +51,7 @@ type Status struct { // ExitNodeStatus describes the current exit node. // If nil, an exit node is not in use. - ExitNodeStatus *ExitNodeStatus `json:"ExitNodeStatus,omitempty"` + ExitNodeStatus *ExitNodeStatus `json:"ExitNodeStatus,omitzero"` // Health contains health check problems. // Empty means everything is good. (or at least that no known @@ -239,16 +239,16 @@ type PeerStatus struct { // TailscaleIPs are the IP addresses assigned to the node. TailscaleIPs []netip.Addr // AllowedIPs are IP addresses allowed to route to this node. - AllowedIPs *views.Slice[netip.Prefix] `json:",omitempty"` + AllowedIPs *views.Slice[netip.Prefix] `json:",omitzero"` // Tags are the list of ACL tags applied to this node. // See tailscale.com/tailcfg#Node.Tags for more information. - Tags *views.Slice[string] `json:",omitempty"` + Tags *views.Slice[string] `json:",omitzero"` // PrimaryRoutes are the routes this node is currently the primary // subnet router for, as determined by the control plane. It does // not include the IPs in TailscaleIPs. - PrimaryRoutes *views.Slice[netip.Prefix] `json:",omitempty"` + PrimaryRoutes *views.Slice[netip.Prefix] `json:",omitzero"` // Endpoints: Addrs []string @@ -327,7 +327,7 @@ type PeerStatus struct { // will expire. KeyExpiry *time.Time `json:",omitempty"` - Location *tailcfg.Location `json:",omitempty"` + Location *tailcfg.Location `json:",omitzero"` } type TaildropTargetStatus int @@ -796,7 +796,7 @@ const ( ) type UpdateProgress struct { - Status SelfUpdateStatus `json:"status,omitempty"` + Status SelfUpdateStatus `json:"status,omitzero"` Message string `json:"message,omitempty"` Version string `json:"version,omitempty"` } diff --git a/ipn/prefs.go b/ipn/prefs.go index 81dd1c1c3..b7e622d6c 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -114,7 +114,7 @@ type Prefs struct { // As of 2025-07-02, the only supported value is [AnyExitNode]. // It's a string rather than a boolean to allow future extensibility // (e.g., AutoExitNode = "mullvad" or AutoExitNode = "geo:us"). - AutoExitNode ExitNodeExpression `json:",omitempty"` + AutoExitNode ExitNodeExpression `json:",omitzero"` // InternalExitNodePrior is the most recently used ExitNodeID in string form. It is set by // the backend on transition from exit node on to off and used by the @@ -231,7 +231,7 @@ type Prefs struct { // removed since then, but the field remains an opt.Bool. // // Linux-only. - NoStatefulFiltering opt.Bool `json:",omitempty"` + NoStatefulFiltering opt.Bool `json:",omitzero"` // NetfilterMode specifies how much to manage netfilter rules for // Tailscale, if at all. @@ -280,7 +280,7 @@ type Prefs struct { // should be disabled. This field is currently experimental, and therefore // no guarantees are made about its current naming and functionality when // non-nil/enabled. - RelayServerPort *int `json:",omitempty"` + RelayServerPort *int `json:",omitzero"` // AllowSingleHosts was a legacy field that was always true // for the past 4.5 years. It controlled whether Tailscale diff --git a/kube/ingressservices/ingressservices.go b/kube/ingressservices/ingressservices.go index f79410761..7aeccc7ed 100644 --- a/kube/ingressservices/ingressservices.go +++ b/kube/ingressservices/ingressservices.go @@ -41,8 +41,8 @@ type Status struct { // Config is an ingress service configuration. type Config struct { - IPv4Mapping *Mapping `json:"IPv4Mapping,omitempty"` - IPv6Mapping *Mapping `json:"IPv6Mapping,omitempty"` + IPv4Mapping *Mapping `json:"IPv4Mapping,omitzero"` + IPv6Mapping *Mapping `json:"IPv6Mapping,omitzero"` } // Mapping describes a rule that forwards traffic from Tailscale Service IP to a diff --git a/kube/k8s-proxy/conf/conf.go b/kube/k8s-proxy/conf/conf.go index 529495243..a607449e8 100644 --- a/kube/k8s-proxy/conf/conf.go +++ b/kube/k8s-proxy/conf/conf.go @@ -44,35 +44,35 @@ type VersionedConfig struct { // Backwards compatibility version(s) of the config. Fields and sub-fields // from here should only be added to, never changed in place. - V1Alpha1 *ConfigV1Alpha1 `json:",omitempty"` + V1Alpha1 *ConfigV1Alpha1 `json:",omitzero"` // V1Beta1 *ConfigV1Beta1 `json:",omitempty"` // Not yet used. } type ConfigV1Alpha1 struct { - AuthKey *string `json:",omitempty"` // Tailscale auth key to use. - State *string `json:",omitempty"` // Path to the Tailscale state. - LogLevel *string `json:",omitempty"` // "debug", "info". Defaults to "info". - App *string `json:",omitempty"` // e.g. kubetypes.AppProxyGroupKubeAPIServer - ServerURL *string `json:",omitempty"` // URL of the Tailscale coordination server. - LocalAddr *string `json:",omitempty"` // The address to use for serving HTTP health checks and metrics (defaults to all interfaces). - LocalPort *uint16 `json:",omitempty"` // The port to use for serving HTTP health checks and metrics (defaults to 9002). - MetricsEnabled opt.Bool `json:",omitempty"` // Serve metrics on :/metrics. - HealthCheckEnabled opt.Bool `json:",omitempty"` // Serve health check on :/metrics. + AuthKey *string `json:",omitzero"` // Tailscale auth key to use. + State *string `json:",omitzero"` // Path to the Tailscale state. + LogLevel *string `json:",omitzero"` // "debug", "info". Defaults to "info". + App *string `json:",omitzero"` // e.g. kubetypes.AppProxyGroupKubeAPIServer + ServerURL *string `json:",omitzero"` // URL of the Tailscale coordination server. + LocalAddr *string `json:",omitzero"` // The address to use for serving HTTP health checks and metrics (defaults to all interfaces). + LocalPort *uint16 `json:",omitzero"` // The port to use for serving HTTP health checks and metrics (defaults to 9002). + MetricsEnabled opt.Bool `json:",omitzero"` // Serve metrics on :/metrics. + HealthCheckEnabled opt.Bool `json:",omitzero"` // Serve health check on :/metrics. // TODO(tomhjp): The remaining fields should all be reloadable during // runtime, but currently missing most of the APIServerProxy fields. - Hostname *string `json:",omitempty"` // Tailscale device hostname. - AcceptRoutes opt.Bool `json:",omitempty"` // Accepts routes advertised by other Tailscale nodes. + Hostname *string `json:",omitzero"` // Tailscale device hostname. + AcceptRoutes opt.Bool `json:",omitzero"` // Accepts routes advertised by other Tailscale nodes. AdvertiseServices []string `json:",omitempty"` // Tailscale Services to advertise. - APIServerProxy *APIServerProxyConfig `json:",omitempty"` // Config specific to the API Server proxy. + APIServerProxy *APIServerProxyConfig `json:",omitzero"` // Config specific to the API Server proxy. StaticEndpoints []netip.AddrPort `json:",omitempty"` // StaticEndpoints are additional, user-defined endpoints that this node should advertise amongst its wireguard endpoints. } type APIServerProxyConfig struct { - Enabled opt.Bool `json:",omitempty"` // Whether to enable the API Server proxy. - Mode *kubetypes.APIServerProxyMode `json:",omitempty"` // "auth" or "noauth" mode. - ServiceName *tailcfg.ServiceName `json:",omitempty"` // Name of the Tailscale Service to advertise. - IssueCerts opt.Bool `json:",omitempty"` // Whether this replica should issue TLS certs for the Tailscale Service. + Enabled opt.Bool `json:",omitzero"` // Whether to enable the API Server proxy. + Mode *kubetypes.APIServerProxyMode `json:",omitzero"` // "auth" or "noauth" mode. + ServiceName *tailcfg.ServiceName `json:",omitzero"` // Name of the Tailscale Service to advertise. + IssueCerts opt.Bool `json:",omitzero"` // Whether this replica should issue TLS certs for the Tailscale Service. } // Load reads and parses the config file at the provided path on disk. diff --git a/kube/kubeclient/client.go b/kube/kubeclient/client.go index 0ed960f4d..89b14da5d 100644 --- a/kube/kubeclient/client.go +++ b/kube/kubeclient/client.go @@ -287,7 +287,7 @@ func (c *client) UpdateSecret(ctx context.Context, s *kubeapi.Secret) error { type JSONPatch struct { Op string `json:"op"` Path string `json:"path"` - Value any `json:"value,omitempty"` + Value any `json:"value,omitzero"` } // JSONPatchResource updates a resource in the Kubernetes API using a JSON patch. diff --git a/kube/kubetypes/grants.go b/kube/kubetypes/grants.go index 4dc278ff1..18642f2dc 100644 --- a/kube/kubetypes/grants.go +++ b/kube/kubetypes/grants.go @@ -14,7 +14,7 @@ import "net/netip" type KubernetesCapRule struct { // Impersonate is a list of rules that specify how to impersonate the caller // when proxying to the Kubernetes API. - Impersonate *ImpersonateRule `json:"impersonate,omitempty"` + Impersonate *ImpersonateRule `json:"impersonate,omitzero"` // Recorders defines a tag of a tsrecorder instance(s) that a recording // of a 'kubectl exec' session, matching `src` of this grant, to an API // server proxy, matching `dst` of this grant, should be sent to. diff --git a/kube/kubetypes/types_test.go b/kube/kubetypes/types_test.go index ea1846b32..9d328e0b2 100644 --- a/kube/kubetypes/types_test.go +++ b/kube/kubetypes/types_test.go @@ -22,7 +22,7 @@ func TestUnmarshalAPIServerProxyMode(t *testing.T) { for _, tc := range tests { var s struct { - Mode *APIServerProxyMode `json:",omitempty"` + Mode *APIServerProxyMode `json:",omitzero"` } err := json.Unmarshal([]byte(tc.data), &s) if tc.expected == "" { diff --git a/sessionrecording/header.go b/sessionrecording/header.go index 220852216..816f54f76 100644 --- a/sessionrecording/header.go +++ b/sessionrecording/header.go @@ -62,7 +62,7 @@ type CastHeader struct { ConnectionID string `json:"connectionID"` // Fields that are only set for Kubernetes API server proxy session recordings: - Kubernetes *Kubernetes `json:"kubernetes,omitempty"` + Kubernetes *Kubernetes `json:"kubernetes,omitzero"` } // Kubernetes contains 'kubectl exec/attach' session specific information for diff --git a/tailcfg/derpmap.go b/tailcfg/derpmap.go index e05559f3e..97c10640b 100644 --- a/tailcfg/derpmap.go +++ b/tailcfg/derpmap.go @@ -15,7 +15,7 @@ type DERPMap struct { // HomeParams, if non-nil, is a change in home parameters. // // The rest of the DEPRMap fields, if zero, means unchanged. - HomeParams *DERPHomeParams `json:",omitempty"` + HomeParams *DERPHomeParams `json:",omitzero"` // Regions is the set of geographic regions running DERP node(s). // diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index a95d0559c..e45f7b8c2 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -412,7 +412,7 @@ type Node struct { // Online is whether the node is currently connected to the // coordination server. A value of nil means unknown, or the // current node doesn't have permission to know. - Online *bool `json:",omitempty"` + Online *bool `json:",omitzero"` MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus @@ -486,7 +486,7 @@ type Node struct { // This only applies to traffic originating from the current node to the // peer or any of its subnets. Traffic originating from subnet routes will // not be masqueraded (e.g. in case of --snat-subnet-routes). - SelfNodeV4MasqAddrForThisPeer *netip.Addr `json:",omitempty"` + SelfNodeV4MasqAddrForThisPeer *netip.Addr `json:",omitzero"` // SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as. // It may be empty if the peer knows the current node by its native @@ -501,7 +501,7 @@ type Node struct { // This only applies to traffic originating from the current node to the // peer or any of its subnets. Traffic originating from subnet routes will // not be masqueraded (e.g. in case of --snat-subnet-routes). - SelfNodeV6MasqAddrForThisPeer *netip.Addr `json:",omitempty"` + SelfNodeV6MasqAddrForThisPeer *netip.Addr `json:",omitzero"` // IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it // is not expected to speak Disco or DERP, and it must have Endpoints in @@ -844,7 +844,7 @@ type Hostinfo struct { // "5.10.0-17-amd64". OSVersion string `json:",omitempty"` - Container opt.Bool `json:",omitempty"` // best-effort whether the client is running in a container + Container opt.Bool `json:",omitzero"` // best-effort whether the client is running in a container Env string `json:",omitempty"` // a hostinfo.EnvType in string form Distro string `json:",omitempty"` // "debian", "ubuntu", "nixos", ... DistroVersion string `json:",omitempty"` // "20.04", ... @@ -853,7 +853,7 @@ type Hostinfo struct { // App is used to disambiguate Tailscale clients that run using tsnet. App string `json:",omitempty"` // "k8s-operator", "golinks", ... - Desktop opt.Bool `json:",omitempty"` // if a desktop was detected on Linux + Desktop opt.Bool `json:",omitzero"` // if a desktop was detected on Linux Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown) DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3") PushDeviceToken string `json:",omitempty"` // macOS/iOS APNs device token for notifications (and Android in the future) @@ -879,27 +879,27 @@ type Hostinfo struct { RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim WoLMACs []string `json:",omitempty"` // MAC address(es) to send Wake-on-LAN packets to wake this node (lowercase hex w/ colons) Services []Service `json:",omitempty"` // services advertised by this machine - NetInfo *NetInfo `json:",omitempty"` + NetInfo *NetInfo `json:",omitzero"` SSH_HostKeys []string `json:"sshHostKeys,omitempty"` // if advertised Cloud string `json:",omitempty"` - Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode - UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode - AppConnector opt.Bool `json:",omitempty"` // if the client is running the app-connector service + Userspace opt.Bool `json:",omitzero"` // if the client is running in userspace (netstack) mode + UserspaceRouter opt.Bool `json:",omitzero"` // if the client's subnet router is running in userspace (netstack) mode + AppConnector opt.Bool `json:",omitzero"` // if the client is running the app-connector service ServicesHash string `json:",omitempty"` // opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n ExitNodeID StableNodeID `json:",omitzero"` // the client’s selected exit node, empty when unselected. // Location represents geographical location data about a // Tailscale host. Location is optional and only set if // explicitly declared by a node. - Location *Location `json:",omitempty"` + Location *Location `json:",omitzero"` - TPM *TPMInfo `json:",omitempty"` // TPM device metadata, if available + TPM *TPMInfo `json:",omitzero"` // TPM device metadata, if available // StateEncrypted reports whether the node state is stored encrypted on // disk. The actual mechanism is platform-specific: // * Apple nodes use the Keychain // * Linux and Windows nodes use the TPM // * Android apps use EncryptedSharedPreferences - StateEncrypted opt.Bool `json:",omitempty"` + StateEncrypted opt.Bool `json:",omitzero"` // NOTE: any new fields containing pointers in this type // require changes to Hostinfo.Equal. @@ -1234,7 +1234,7 @@ type RegisterResponseAuth struct { // At most one of Oauth2Token or AuthKey is set. - Oauth2Token *Oauth2Token `json:",omitempty"` // used by pre-1.66 Android only + Oauth2Token *Oauth2Token `json:",omitzero"` // used by pre-1.66 Android only AuthKey string `json:",omitempty"` } @@ -1256,7 +1256,7 @@ type RegisterRequest struct { NodeKey key.NodePublic OldNodeKey key.NodePublic NLKey key.NLPublic - Auth *RegisterResponseAuth `json:",omitempty"` + Auth *RegisterResponseAuth `json:",omitzero"` // Expiry optionally specifies the requested key expiry. // The server policy may override. // As a special case, if Expiry is in the past and NodeKey is @@ -1501,7 +1501,7 @@ func (pr PortRange) String() string { type NetPortRange struct { _ structs.Incomparable IP string // IP, CIDR, Range, or "*" (same formats as FilterRule.SrcIPs) - Bits *int `json:",omitempty"` // deprecated; the 2020 way to turn IP into a CIDR. See FilterRule.SrcBits. + Bits *int `json:",omitzero"` // deprecated; the 2020 way to turn IP into a CIDR. See FilterRule.SrcBits. Ports PortRange } @@ -1978,7 +1978,7 @@ type MapResponse struct { // provided URL. No auth headers are necessary. // PingRequest may be sent on any MapResponse (ones with // KeepAlive true or false). - PingRequest *PingRequest `json:",omitempty"` + PingRequest *PingRequest `json:",omitzero"` // PopBrowserURL, if non-empty, is a URL for the client to // open to complete an action. The client should dup suppress @@ -1989,11 +1989,11 @@ type MapResponse struct { // Node describes the node making the map request. // Starting with MapRequest.Version 18, nil means unchanged. - Node *Node `json:",omitempty"` + Node *Node `json:",omitzero"` // DERPMap describe the set of DERP servers available. // A nil value means unchanged. - DERPMap *DERPMap `json:",omitempty"` + DERPMap *DERPMap `json:",omitzero"` // Peers, if non-empty, is the complete list of peers. // It will be set in the first MapResponse for a long-polled request/response. @@ -2030,7 +2030,7 @@ type MapResponse struct { // DNSConfig contains the DNS settings for the client to use. // A nil value means no change from an earlier non-nil value. - DNSConfig *DNSConfig `json:",omitempty"` + DNSConfig *DNSConfig `json:",omitzero"` // Domain is the name of the network that this node is // in. It's either of the form "example.com" (for user @@ -2045,7 +2045,7 @@ type MapResponse struct { // requested that info about services be included in HostInfo. // If unset, the most recent non-empty MapResponse value in // the HTTP response stream is used. - CollectServices opt.Bool `json:",omitempty"` + CollectServices opt.Bool `json:",omitzero"` // PacketFilter are the firewall rules. // @@ -2121,7 +2121,7 @@ type MapResponse struct { // SSHPolicy, if non-nil, updates the SSH policy for how incoming // SSH connections should be handled. - SSHPolicy *SSHPolicy `json:",omitempty"` + SSHPolicy *SSHPolicy `json:",omitzero"` // ControlTime, if non-zero, is the current timestamp according to the control server. ControlTime *time.Time `json:",omitempty"` @@ -2134,7 +2134,7 @@ type MapResponse struct { // indicates the control plane believes TKA should be enabled. // A nil TKAInfo in a mapresponse stream (i.e. a 'delta' mapresponse) // indicates no change from the value sent earlier. - TKAInfo *TKAInfo `json:",omitempty"` + TKAInfo *TKAInfo `json:",omitzero"` // DomainDataPlaneAuditLogID, if non-empty, is the per-tailnet log ID to be // used when writing data plane audit logs. @@ -2142,24 +2142,24 @@ type MapResponse struct { // Debug is normally nil, except for when the control server // is setting debug settings on a node. - Debug *Debug `json:",omitempty"` + Debug *Debug `json:",omitzero"` // ControlDialPlan tells the client how to connect to the control // server. An initial nil is equivalent to new(ControlDialPlan). // A subsequent streamed nil means no change. - ControlDialPlan *ControlDialPlan `json:",omitempty"` + ControlDialPlan *ControlDialPlan `json:",omitzero"` // ClientVersion describes the latest client version that's available for // download and whether the client is using it. A nil value means no change // or nothing to report. - ClientVersion *ClientVersion `json:",omitempty"` + ClientVersion *ClientVersion `json:",omitzero"` // DefaultAutoUpdate is the default node auto-update setting for this // tailnet. The node is free to opt-in or out locally regardless of this // value. This value is only used on first MapResponse from control, the // auto-update setting doesn't change if the tailnet admin flips the // default after the node registered. - DefaultAutoUpdate opt.Bool `json:",omitempty"` + DefaultAutoUpdate opt.Bool `json:",omitzero"` } // DisplayMessage represents a health state of the node from the control plane's @@ -2197,7 +2197,7 @@ type DisplayMessage struct { // take when interacting with this message. For example, if the // DisplayMessage is shown via a notification, the action label might be a // button on that notification and clicking the button would open the URL. - PrimaryAction *DisplayMessageAction `json:",omitempty"` + PrimaryAction *DisplayMessageAction `json:",omitzero"` } // DisplayMessageAction represents an action (URL and link) to be presented to @@ -2334,7 +2334,7 @@ type Debug struct { // Exit optionally specifies that the client should os.Exit // with this code. This is a safety measure in case a client is crash // looping or in an unsafe state and we need to remotely shut it down. - Exit *int `json:",omitempty"` + Exit *int `json:",omitzero"` } func (id ID) String() string { return fmt.Sprintf("id:%d", int64(id)) } @@ -2878,7 +2878,7 @@ type SSHPrincipal struct { // Matching any one of the following four field causes a match. // It must also match Certs, if non-empty. - Node StableNodeID `json:"node,omitempty"` + Node StableNodeID `json:"node,omitzero"` NodeIP string `json:"nodeIP,omitempty"` UserLogin string `json:"userLogin,omitempty"` // email-ish: foo@example.com, bar@github Any bool `json:"any,omitempty"` // if true, match any connection @@ -2951,7 +2951,7 @@ type SSHAction struct { // OnRecorderFailure is the action to take if recording fails. // If nil, the default action is to fail open. - OnRecordingFailure *SSHRecorderFailureAction `json:"onRecordingFailure,omitempty"` + OnRecordingFailure *SSHRecorderFailureAction `json:"onRecordingFailure,omitzero"` } // SSHRecorderFailureAction is the action to take if recording fails. @@ -3196,7 +3196,7 @@ type PeerChange struct { DiscoKey *key.DiscoPublic `json:",omitempty"` // Online, if non-nil, means that the NodeID's online status changed. - Online *bool `json:",omitempty"` + Online *bool `json:",omitzero"` // LastSeen, if non-nil, means that the NodeID's online status changed. LastSeen *time.Time `json:",omitempty"` @@ -3284,7 +3284,7 @@ type AuditLogRequest struct { // NodeKey is the client's current node key. NodeKey key.NodePublic `json:",omitzero"` // Action is the action to be logged. It must correspond to a known action in the control plane. - Action ClientAuditAction `json:",omitempty"` + Action ClientAuditAction `json:",omitzero"` // Details is an opaque string, specific to the action being logged. Empty strings may not // be valid depending on the action being logged. Details string `json:",omitempty"` diff --git a/tsweb/log.go b/tsweb/log.go index 51f95e95f..59dc29224 100644 --- a/tsweb/log.go +++ b/tsweb/log.go @@ -50,7 +50,7 @@ type AccessLogRecord struct { // client immediately after the error text, as well as logged here. This // makes it easier to correlate support requests with server logs. If a // RequestID generator is not configured, RequestID will be empty. - RequestID RequestID `json:"request_id,omitempty"` + RequestID RequestID `json:"request_id,omitzero"` } // String returns m as a JSON string. diff --git a/types/opt/bool_test.go b/types/opt/bool_test.go index dddbcfc19..dc2935683 100644 --- a/types/opt/bool_test.go +++ b/types/opt/bool_test.go @@ -44,7 +44,7 @@ func TestBool(t *testing.T) { in: struct { True Bool False Bool - Unset Bool `json:",omitempty"` + Unset Bool `json:",omitzero"` }{ True: "true", False: "false", diff --git a/types/views/views_test.go b/types/views/views_test.go index 5a30c11a1..7e49ea4a1 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -39,8 +39,8 @@ type viewStruct struct { Int int Addrs Slice[netip.Prefix] Strings Slice[string] - AddrsPtr *Slice[netip.Prefix] `json:",omitempty"` - StringsPtr *Slice[string] `json:",omitempty"` + AddrsPtr *Slice[netip.Prefix] `json:",omitzero"` + StringsPtr *Slice[string] `json:",omitzero"` } type noPtrStruct struct { diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index 2010775a1..48982aae7 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -428,8 +428,8 @@ type pongReply struct { type EndpointChange struct { When time.Time // when the change occurred What string // what this change is - From any `json:",omitempty"` // information about the previous state - To any `json:",omitempty"` // information about the new state + From any `json:",omitzero"` // information about the previous state + To any `json:",omitzero"` // information about the new state } // shouldDeleteLocked reports whether we should delete this endpoint.