k8s-operator: use iota-based enums (#14323)

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
chaosinthecrd 2025-04-22 18:19:09 +01:00
parent e2e5914d57
commit 91e923e842
No known key found for this signature in database
GPG Key ID: 87942E75F71EF65D
18 changed files with 121 additions and 118 deletions

View File

@ -30,7 +30,6 @@ import (
const ( const (
dnsRecordsRecocilerFinalizer = "tailscale.com/dns-records-reconciler" dnsRecordsRecocilerFinalizer = "tailscale.com/dns-records-reconciler"
annotationTSMagicDNSName = "tailscale.com/magic-dnsname"
) )
// dnsRecordsReconciler knows how to update dnsrecords ConfigMap with DNS // dnsRecordsReconciler knows how to update dnsrecords ConfigMap with DNS
@ -159,7 +158,7 @@ func (dnsRR *dnsRecordsReconciler) maybeProvision(ctx context.Context, headlessS
// Ensure that headless Service is annotated with the current MagicDNS // Ensure that headless Service is annotated with the current MagicDNS
// name to help with records cleanup when proxy resources are deleted or // name to help with records cleanup when proxy resources are deleted or
// MagicDNS name changes. // MagicDNS name changes.
oldFqdn := headlessSvc.Annotations[annotationTSMagicDNSName] oldFqdn := AnnotationMagicDNSName.GetValue(headlessSvc)
if oldFqdn != "" && oldFqdn != fqdn { // i.e user has changed the value of tailscale.com/tailnet-fqdn annotation if oldFqdn != "" && oldFqdn != fqdn { // i.e user has changed the value of tailscale.com/tailnet-fqdn annotation
logger.Debugf("MagicDNS name has changed, remvoving record for %s", oldFqdn) logger.Debugf("MagicDNS name has changed, remvoving record for %s", oldFqdn)
updateFunc := func(rec *operatorutils.Records) { updateFunc := func(rec *operatorutils.Records) {
@ -169,7 +168,7 @@ func (dnsRR *dnsRecordsReconciler) maybeProvision(ctx context.Context, headlessS
return fmt.Errorf("error removing record for %s: %w", oldFqdn, err) return fmt.Errorf("error removing record for %s: %w", oldFqdn, err)
} }
} }
mak.Set(&headlessSvc.Annotations, annotationTSMagicDNSName, fqdn) mak.Set(&headlessSvc.Annotations, AnnotationMagicDNSName.String(), fqdn)
if !apiequality.Semantic.DeepEqual(oldHeadlessSvc, headlessSvc) { if !apiequality.Semantic.DeepEqual(oldHeadlessSvc, headlessSvc) {
logger.Infof("provisioning DNS record for MagicDNS name: %s", fqdn) // this will be printed exactly once logger.Infof("provisioning DNS record for MagicDNS name: %s", fqdn) // this will be printed exactly once
if err := dnsRR.Update(ctx, headlessSvc); err != nil { if err := dnsRR.Update(ctx, headlessSvc); err != nil {
@ -267,7 +266,7 @@ func (h *dnsRecordsReconciler) maybeCleanup(ctx context.Context, headlessSvc *co
logger.Debug("'dnsrecords' ConfigMap contains no records") logger.Debug("'dnsrecords' ConfigMap contains no records")
return h.removeHeadlessSvcFinalizer(ctx, headlessSvc) return h.removeHeadlessSvcFinalizer(ctx, headlessSvc)
} }
fqdn, _ := headlessSvc.GetAnnotations()[annotationTSMagicDNSName] fqdn := AnnotationMagicDNSName.GetValue(headlessSvc)
if fqdn == "" { if fqdn == "" {
return h.removeHeadlessSvcFinalizer(ctx, headlessSvc) return h.removeHeadlessSvcFinalizer(ctx, headlessSvc)
} }
@ -316,7 +315,7 @@ func (dnsRR *dnsRecordsReconciler) fqdnForDNSRecord(ctx context.Context, headles
} else if err != nil { } else if err != nil {
return "", err return "", err
} }
return svc.Annotations[AnnotationTailnetTargetFQDN], nil return AnnotationTailnetTargetFQDN.GetValue(svc), nil
} }
return "", nil return "", nil
} }
@ -363,6 +362,5 @@ func (dnsRR *dnsRecordsReconciler) isSvcForFQDNEgressProxy(ctx context.Context,
} else if err != nil { } else if err != nil {
return false, err return false, err
} }
annots := parentSvc.Annotations return AnnotationTailnetTargetFQDN.GetValue(parentSvc) != "", nil
return annots != nil && annots[AnnotationTailnetTargetFQDN] != "", nil
} }

View File

@ -81,7 +81,7 @@ func TestDNSRecordsReconciler(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "egress-fqdn", Name: "egress-fqdn",
Namespace: "test", Namespace: "test",
Annotations: map[string]string{"tailscale.com/tailnet-fqdn": "foo.bar.ts.net"}, Annotations: map[string]string{AnnotationTailnetTargetFQDN.String(): "foo.bar.ts.net"},
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
ExternalName: "unused", ExternalName: "unused",
@ -104,7 +104,7 @@ func TestDNSRecordsReconciler(t *testing.T) {
// 2. DNS record is updated if tailscale.com/tailnet-fqdn annotation's // 2. DNS record is updated if tailscale.com/tailnet-fqdn annotation's
// value changes // value changes
mustUpdate(t, fc, "test", "egress-fqdn", func(svc *corev1.Service) { mustUpdate(t, fc, "test", "egress-fqdn", func(svc *corev1.Service) {
svc.Annotations["tailscale.com/tailnet-fqdn"] = "baz.bar.ts.net" svc.Annotations[AnnotationTailnetTargetFQDN.String()] = "baz.bar.ts.net"
}) })
expectReconciled(t, dnsRR, "tailscale", "egress-fqdn") // dns-records-reconciler reconcile the headless Service expectReconciled(t, dnsRR, "tailscale", "egress-fqdn") // dns-records-reconciler reconcile the headless Service
wantHosts = map[string][]string{"baz.bar.ts.net": {"10.9.8.7"}} wantHosts = map[string][]string{"baz.bar.ts.net": {"10.9.8.7"}}

View File

@ -32,8 +32,8 @@ func TestTailscaleEgressEndpointSlices(t *testing.T) {
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: "foo.bar.ts.net", AnnotationTailnetTargetFQDN.String(): "foo.bar.ts.net",
AnnotationProxyGroup: "foo", AnnotationProxyGroup.String(): "foo",
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -135,10 +135,10 @@ func configMapForSvc(t *testing.T, svc *corev1.Service, p uint16) *corev1.Config
cfg := egressservices.Config{ cfg := egressservices.Config{
Ports: ports, Ports: ports,
} }
if fqdn := svc.Annotations[AnnotationTailnetTargetFQDN]; fqdn != "" { if fqdn := AnnotationTailnetTargetFQDN.GetValue(svc); fqdn != "" {
cfg.TailnetTarget = egressservices.TailnetTarget{FQDN: fqdn} cfg.TailnetTarget = egressservices.TailnetTarget{FQDN: fqdn}
} }
if ip := svc.Annotations[AnnotationTailnetTargetIP]; ip != "" { if ip := AnnotationTailnetTargetIP.GetValue(svc); ip != "" {
cfg.TailnetTarget = egressservices.TailnetTarget{IP: ip} cfg.TailnetTarget = egressservices.TailnetTarget{IP: ip}
} }
name := tailnetSvcName(svc) name := tailnetSvcName(svc)
@ -149,7 +149,7 @@ func configMapForSvc(t *testing.T, svc *corev1.Service, p uint16) *corev1.Config
} }
cm := &corev1.ConfigMap{ cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: pgEgressCMName(svc.Annotations[AnnotationProxyGroup]), Name: pgEgressCMName(AnnotationProxyGroup.GetValue(svc)),
Namespace: "operator-ns", Namespace: "operator-ns",
}, },
BinaryData: map[string][]byte{egressservices.KeyEgressServices: bs}, BinaryData: map[string][]byte{egressservices.KeyEgressServices: bs},
@ -164,10 +164,10 @@ func serviceStatusForPodIP(t *testing.T, svc *corev1.Service, ip string, p uint1
ports[egressservices.PortMap{Protocol: string(port.Protocol), MatchPort: p, TargetPort: uint16(port.Port)}] = struct{}{} ports[egressservices.PortMap{Protocol: string(port.Protocol), MatchPort: p, TargetPort: uint16(port.Port)}] = struct{}{}
} }
svcSt := egressservices.ServiceStatus{Ports: ports} svcSt := egressservices.ServiceStatus{Ports: ports}
if fqdn := svc.Annotations[AnnotationTailnetTargetFQDN]; fqdn != "" { if fqdn := AnnotationTailnetTargetFQDN.GetValue(svc); fqdn != "" {
svcSt.TailnetTarget = egressservices.TailnetTarget{FQDN: fqdn} svcSt.TailnetTarget = egressservices.TailnetTarget{FQDN: fqdn}
} }
if ip := svc.Annotations[AnnotationTailnetTargetIP]; ip != "" { if ip := AnnotationTailnetTargetIP.GetValue(svc); ip != "" {
svcSt.TailnetTarget = egressservices.TailnetTarget{IP: ip} svcSt.TailnetTarget = egressservices.TailnetTarget{IP: ip}
} }
svcName := tailnetSvcName(svc) svcName := tailnetSvcName(svc)

View File

@ -86,7 +86,7 @@ func (esrr *egressSvcsReadinessReconciler) Reconcile(ctx context.Context, req re
} }
pg := &tsapi.ProxyGroup{ pg := &tsapi.ProxyGroup{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: svc.Annotations[AnnotationProxyGroup], Name: AnnotationProxyGroup.GetValue(svc),
}, },
} }
err = esrr.Get(ctx, client.ObjectKeyFromObject(pg), pg) err = esrr.Get(ctx, client.ObjectKeyFromObject(pg), pg)

View File

@ -43,8 +43,8 @@ func TestEgressServiceReadiness(t *testing.T) {
Name: "my-app", Name: "my-app",
Namespace: "dev", Namespace: "dev",
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationProxyGroup: "dev", AnnotationProxyGroup.String(): "dev",
AnnotationTailnetTargetFQDN: tailnetFQDN, AnnotationTailnetTargetFQDN.String(): tailnetFQDN,
}, },
}, },
} }

View File

@ -192,7 +192,7 @@ func (esr *egressSvcsReconciler) maybeProvision(ctx context.Context, svc *corev1
upToDate := svcConfigurationUpToDate(svc, l) upToDate := svcConfigurationUpToDate(svc, l)
provisioned := true provisioned := true
if !upToDate { if !upToDate {
if clusterIPSvc, provisioned, err = esr.provision(ctx, svc.Annotations[AnnotationProxyGroup], svc, clusterIPSvc, l); err != nil { if clusterIPSvc, provisioned, err = esr.provision(ctx, AnnotationProxyGroup.GetValue(svc), svc, clusterIPSvc, l); err != nil {
return err return err
} }
} }
@ -403,7 +403,7 @@ func (esr *egressSvcsReconciler) maybeCleanup(ctx context.Context, svc *corev1.S
} }
func (esr *egressSvcsReconciler) maybeCleanupProxyGroupConfig(ctx context.Context, svc *corev1.Service, l *zap.SugaredLogger) error { func (esr *egressSvcsReconciler) maybeCleanupProxyGroupConfig(ctx context.Context, svc *corev1.Service, l *zap.SugaredLogger) error {
wantsProxyGroup := svc.Annotations[AnnotationProxyGroup] wantsProxyGroup := AnnotationProxyGroup.GetValue(svc)
cond := tsoperator.GetServiceCondition(svc, tsapi.EgressSvcConfigured) cond := tsoperator.GetServiceCondition(svc, tsapi.EgressSvcConfigured)
if cond == nil { if cond == nil {
return nil return nil
@ -506,7 +506,7 @@ func (esr *egressSvcsReconciler) ensureEgressSvcCfgDeleted(ctx context.Context,
} }
func (esr *egressSvcsReconciler) validateClusterResources(ctx context.Context, svc *corev1.Service, l *zap.SugaredLogger) (bool, error) { func (esr *egressSvcsReconciler) validateClusterResources(ctx context.Context, svc *corev1.Service, l *zap.SugaredLogger) (bool, error) {
proxyGroupName := svc.Annotations[AnnotationProxyGroup] proxyGroupName := AnnotationProxyGroup.GetValue(svc)
pg := &tsapi.ProxyGroup{ pg := &tsapi.ProxyGroup{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: proxyGroupName, Name: proxyGroupName,
@ -563,7 +563,7 @@ func validateEgressService(svc *corev1.Service, pg *tsapi.ProxyGroup) []string {
violations := validateService(svc) violations := validateService(svc)
// We check that only one of these two is set in the earlier validateService function. // We check that only one of these two is set in the earlier validateService function.
if svc.Annotations[AnnotationTailnetTargetFQDN] == "" && svc.Annotations[AnnotationTailnetTargetIP] == "" { if AnnotationTailnetTargetFQDN.GetValue(svc) == "" && AnnotationTailnetTargetIP.GetValue(svc) == "" {
violations = append(violations, fmt.Sprintf("egress Service for ProxyGroup must have one of %s, %s annotations set", AnnotationTailnetTargetFQDN, AnnotationTailnetTargetIP)) violations = append(violations, fmt.Sprintf("egress Service for ProxyGroup must have one of %s, %s annotations set", AnnotationTailnetTargetFQDN, AnnotationTailnetTargetIP))
} }
if len(svc.Spec.Ports) == 0 { if len(svc.Spec.Ports) == 0 {
@ -618,13 +618,13 @@ func unusedPort(usedPorts sets.Set[int32]) int32 {
// Service must contain exactly one of tailscale.com/tailnet-ip, // Service must contain exactly one of tailscale.com/tailnet-ip,
// tailscale.com/tailnet-fqdn annotations. // tailscale.com/tailnet-fqdn annotations.
func tailnetTargetFromSvc(svc *corev1.Service) egressservices.TailnetTarget { func tailnetTargetFromSvc(svc *corev1.Service) egressservices.TailnetTarget {
if fqdn := svc.Annotations[AnnotationTailnetTargetFQDN]; fqdn != "" { if fqdn := AnnotationTailnetTargetFQDN.GetValue(svc); fqdn != "" {
return egressservices.TailnetTarget{ return egressservices.TailnetTarget{
FQDN: fqdn, FQDN: fqdn,
} }
} }
return egressservices.TailnetTarget{ return egressservices.TailnetTarget{
IP: svc.Annotations[AnnotationTailnetTargetIP], IP: AnnotationTailnetTargetIP.GetValue(svc),
} }
} }
@ -642,8 +642,7 @@ func isEgressSvcForProxyGroup(obj client.Object) bool {
if !ok { if !ok {
return false return false
} }
annots := s.ObjectMeta.Annotations return AnnotationProxyGroup.GetValue(s) != "" && (AnnotationTailnetTargetFQDN.GetValue(s) != "" || AnnotationTailnetTargetIP.GetValue(s) != "")
return annots[AnnotationProxyGroup] != "" && (annots[AnnotationTailnetTargetFQDN] != "" || annots[AnnotationTailnetTargetIP] != "")
} }
// egressSvcConfig returns a ConfigMap that contains egress services configuration for the provided ProxyGroup as well // egressSvcConfig returns a ConfigMap that contains egress services configuration for the provided ProxyGroup as well
@ -684,7 +683,7 @@ func egressSvcChildResourceLabels(svc *corev1.Service) map[string]string {
LabelParentType: "svc", LabelParentType: "svc",
LabelParentName: svc.Name, LabelParentName: svc.Name,
LabelParentNamespace: svc.Namespace, LabelParentNamespace: svc.Namespace,
labelProxyGroup: svc.Annotations[AnnotationProxyGroup], labelProxyGroup: AnnotationProxyGroup.GetValue(svc),
labelSvcType: typeEgress, labelSvcType: typeEgress,
} }
} }
@ -743,12 +742,12 @@ func svcConfiguredReason(svc *corev1.Service, configured bool, l *zap.SugaredLog
} else { } else {
r = fmt.Sprintf("ConfigurationFailed:%s", r) r = fmt.Sprintf("ConfigurationFailed:%s", r)
} }
r += fmt.Sprintf("ProxyGroup:%s", svc.Annotations[AnnotationProxyGroup]) r += fmt.Sprintf("ProxyGroup:%s", AnnotationProxyGroup.GetValue(svc))
tt := tailnetTargetFromSvc(svc) tt := tailnetTargetFromSvc(svc)
s := cfg{ s := cfg{
Ports: svc.Spec.Ports, Ports: svc.Spec.Ports,
TailnetTarget: tt, TailnetTarget: tt,
ProxyGroup: svc.Annotations[AnnotationProxyGroup], ProxyGroup: AnnotationProxyGroup.GetValue(svc),
} }
r += fmt.Sprintf(":Config:%s", cfgHash(s, l)) r += fmt.Sprintf(":Config:%s", cfgHash(s, l))
return r return r

View File

@ -69,8 +69,8 @@ func TestTailscaleEgressServices(t *testing.T) {
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: tailnetTargetFQDN, AnnotationTailnetTargetFQDN.String(): tailnetTargetFQDN,
AnnotationProxyGroup: "foo", AnnotationProxyGroup.String(): "foo",
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{

View File

@ -50,9 +50,6 @@ const (
FinalizerNamePG = "tailscale.com/ingress-pg-finalizer" FinalizerNamePG = "tailscale.com/ingress-pg-finalizer"
indexIngressProxyGroup = ".metadata.annotations.ingress-proxy-group" indexIngressProxyGroup = ".metadata.annotations.ingress-proxy-group"
// annotationHTTPEndpoint can be used to configure the Ingress to expose an HTTP endpoint to tailnet (as
// well as the default HTTPS endpoint).
annotationHTTPEndpoint = "tailscale.com/http-endpoint"
labelDomain = "tailscale.com/domain" labelDomain = "tailscale.com/domain"
) )
@ -147,7 +144,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin
return false, nil return false, nil
} }
// Get and validate ProxyGroup readiness // Get and validate ProxyGroup readiness
pgName := ing.Annotations[AnnotationProxyGroup] pgName := ing.Annotations[AnnotationProxyGroup.String()]
if pgName == "" { if pgName == "" {
logger.Infof("[unexpected] no ProxyGroup annotation, skipping VIPService provisioning") logger.Infof("[unexpected] no ProxyGroup annotation, skipping VIPService provisioning")
return false, nil return false, nil
@ -305,7 +302,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin
// 4. Ensure that the VIPService exists and is up to date. // 4. Ensure that the VIPService exists and is up to date.
tags := r.defaultTags tags := r.defaultTags
if tstr, ok := ing.Annotations[AnnotationTags]; ok { if tstr, ok := ing.Annotations[AnnotationTags.String()]; ok {
tags = strings.Split(tstr, ",") tags = strings.Split(tstr, ",")
} }
@ -498,7 +495,7 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string,
}() }()
// 1. Check if there is a VIPService associated with this Ingress. // 1. Check if there is a VIPService associated with this Ingress.
pg := ing.Annotations[AnnotationProxyGroup] pg := ing.Annotations[AnnotationProxyGroup.String()]
cm, cfg, err := r.proxyGroupServeConfig(ctx, pg) cm, cfg, err := r.proxyGroupServeConfig(ctx, pg)
if err != nil { if err != nil {
return false, fmt.Errorf("error getting ProxyGroup serve config: %w", err) return false, fmt.Errorf("error getting ProxyGroup serve config: %w", err)
@ -610,7 +607,7 @@ func (r *HAIngressReconciler) shouldExpose(ing *networkingv1.Ingress) bool {
isTSIngress := ing != nil && isTSIngress := ing != nil &&
ing.Spec.IngressClassName != nil && ing.Spec.IngressClassName != nil &&
*ing.Spec.IngressClassName == tailscaleIngressClassName *ing.Spec.IngressClassName == tailscaleIngressClassName
pgAnnot := ing.Annotations[AnnotationProxyGroup] pgAnnot := ing.Annotations[AnnotationProxyGroup.String()]
return isTSIngress && pgAnnot != "" return isTSIngress && pgAnnot != ""
} }
@ -624,7 +621,7 @@ func (r *HAIngressReconciler) validateIngress(ctx context.Context, ing *networki
var errs []error var errs []error
// Validate tags if present // Validate tags if present
if tstr, ok := ing.Annotations[AnnotationTags]; ok { if tstr, ok := ing.Annotations[AnnotationTags.String()]; ok {
tags := strings.Split(tstr, ",") tags := strings.Split(tstr, ",")
for _, tag := range tags { for _, tag := range tags {
tag = strings.TrimSpace(tag) tag = strings.TrimSpace(tag)
@ -714,7 +711,7 @@ func (r *HAIngressReconciler) cleanupVIPService(ctx context.Context, name tailcf
if err != nil { if err != nil {
return false, fmt.Errorf("error marshalling updated VIPService owner reference: %w", err) return false, fmt.Errorf("error marshalling updated VIPService owner reference: %w", err)
} }
svc.Annotations[ownerAnnotation] = string(json) svc.Annotations[AnnotationOwnerReferences.String()] = string(json)
return true, r.tsClient.CreateOrUpdateVIPService(ctx, svc) return true, r.tsClient.CreateOrUpdateVIPService(ctx, svc)
} }
@ -723,7 +720,7 @@ func isHTTPEndpointEnabled(ing *networkingv1.Ingress) bool {
if ing == nil { if ing == nil {
return false return false
} }
return ing.Annotations[annotationHTTPEndpoint] == "enabled" return AnnotationHTTPEndpoint.GetValue(ing) == "enabled"
} }
// serviceAdvertisementMode describes the desired state of a VIPService. // serviceAdvertisementMode describes the desired state of a VIPService.
@ -824,8 +821,6 @@ func (a *HAIngressReconciler) numberPodsAdvertising(ctx context.Context, pgName
return count, nil return count, nil
} }
const ownerAnnotation = "tailscale.com/owner-references"
// ownerAnnotationValue is the content of the VIPService.Annotation[ownerAnnotation] field. // ownerAnnotationValue is the content of the VIPService.Annotation[ownerAnnotation] field.
type ownerAnnotationValue struct { type ownerAnnotationValue struct {
// OwnerRefs is a list of owner references that identify all operator // OwnerRefs is a list of owner references that identify all operator
@ -856,7 +851,7 @@ func (r *HAIngressReconciler) ownerAnnotations(svc *tailscale.VIPService) (map[s
return nil, fmt.Errorf("[unexpected] unable to marshal VIPService owner annotation contents: %w, please report this", err) return nil, fmt.Errorf("[unexpected] unable to marshal VIPService owner annotation contents: %w, please report this", err)
} }
return map[string]string{ return map[string]string{
ownerAnnotation: string(json), AnnotationOwnerReferences.String(): string(json),
}, nil }, nil
} }
o, err := parseOwnerAnnotation(svc) o, err := parseOwnerAnnotation(svc)
@ -879,18 +874,18 @@ func (r *HAIngressReconciler) ownerAnnotations(svc *tailscale.VIPService) (map[s
for k, v := range svc.Annotations { for k, v := range svc.Annotations {
newAnnots[k] = v newAnnots[k] = v
} }
newAnnots[ownerAnnotation] = string(json) newAnnots[AnnotationOwnerReferences.String()] = string(json)
return newAnnots, nil return newAnnots, nil
} }
// parseOwnerAnnotation returns nil if no valid owner found. // parseOwnerAnnotation returns nil if no valid owner found.
func parseOwnerAnnotation(vipSvc *tailscale.VIPService) (*ownerAnnotationValue, error) { func parseOwnerAnnotation(vipSvc *tailscale.VIPService) (*ownerAnnotationValue, error) {
if vipSvc.Annotations == nil || vipSvc.Annotations[ownerAnnotation] == "" { if vipSvc.Annotations == nil || vipSvc.Annotations[AnnotationOwnerReferences.String()] == "" {
return nil, nil return nil, nil
} }
o := &ownerAnnotationValue{} o := &ownerAnnotationValue{}
if err := json.Unmarshal([]byte(vipSvc.Annotations[ownerAnnotation]), o); err != nil { if err := json.Unmarshal([]byte(vipSvc.Annotations[AnnotationOwnerReferences.String()]), o); err != nil {
return nil, fmt.Errorf("error parsing VIPService %s annotation %q: %w", ownerAnnotation, vipSvc.Annotations[ownerAnnotation], err) return nil, fmt.Errorf("error parsing VIPService %s annotation %q: %w", AnnotationOwnerReferences.String(), vipSvc.Annotations[AnnotationOwnerReferences.String()], err)
} }
return o, nil return o, nil
} }
@ -898,9 +893,9 @@ func parseOwnerAnnotation(vipSvc *tailscale.VIPService) (*ownerAnnotationValue,
func ownersAreSetAndEqual(a, b *tailscale.VIPService) bool { func ownersAreSetAndEqual(a, b *tailscale.VIPService) bool {
return a != nil && b != nil && return a != nil && b != nil &&
a.Annotations != nil && b.Annotations != nil && a.Annotations != nil && b.Annotations != nil &&
a.Annotations[ownerAnnotation] != "" && a.Annotations[AnnotationOwnerReferences.String()] != "" &&
b.Annotations[ownerAnnotation] != "" && b.Annotations[AnnotationOwnerReferences.String()] != "" &&
strings.EqualFold(a.Annotations[ownerAnnotation], b.Annotations[ownerAnnotation]) strings.EqualFold(a.Annotations[AnnotationOwnerReferences.String()], b.Annotations[AnnotationOwnerReferences.String()])
} }
// ensureCertResources ensures that the TLS Secret for an HA Ingress and RBAC // ensureCertResources ensures that the TLS Secret for an HA Ingress and RBAC

View File

@ -277,7 +277,7 @@ func TestValidateIngress(t *testing.T) {
Name: "test-ingress", Name: "test-ingress",
Namespace: "default", Namespace: "default",
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationProxyGroup: "test-pg", AnnotationProxyGroup.String(): "test-pg",
}, },
}, },
Spec: networkingv1.IngressSpec{ Spec: networkingv1.IngressSpec{
@ -338,7 +338,7 @@ func TestValidateIngress(t *testing.T) {
Name: baseIngress.Name, Name: baseIngress.Name,
Namespace: baseIngress.Namespace, Namespace: baseIngress.Namespace,
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTags: "tag:invalid!", AnnotationTags.String(): "tag:invalid!",
}, },
}, },
}, },
@ -411,7 +411,7 @@ func TestValidateIngress(t *testing.T) {
Name: "existing-ingress", Name: "existing-ingress",
Namespace: "default", Namespace: "default",
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationProxyGroup: "test-pg", AnnotationProxyGroup.String(): "test-pg",
}, },
}, },
Spec: networkingv1.IngressSpec{ Spec: networkingv1.IngressSpec{
@ -759,7 +759,7 @@ func TestIngressPGReconciler_MultiCluster(t *testing.T) {
existingVIPSvc := &tailscale.VIPService{ existingVIPSvc := &tailscale.VIPService{
Name: "svc:my-svc", Name: "svc:my-svc",
Annotations: map[string]string{ Annotations: map[string]string{
ownerAnnotation: `{"ownerrefs":[{"operatorID":"operator-2"}]}`, AnnotationOwnerReferences.String(): `{"ownerrefs":[{"operatorID":"operator-2"}]}`,
}, },
} }
ft.vipServices = map[tailcfg.ServiceName]*tailscale.VIPService{ ft.vipServices = map[tailcfg.ServiceName]*tailscale.VIPService{

View File

@ -179,7 +179,7 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
}, },
}, },
} }
if opt.Bool(ing.Annotations[AnnotationFunnel]).EqualBool(true) { if opt.Bool(ing.Annotations[AnnotationFunnel.String()]).EqualBool(true) {
sc.AllowFunnel = map[ipn.HostPort]bool{ sc.AllowFunnel = map[ipn.HostPort]bool{
magic443: true, magic443: true,
} }
@ -204,7 +204,7 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
crl := childResourceLabels(ing.Name, ing.Namespace, "ingress") crl := childResourceLabels(ing.Name, ing.Namespace, "ingress")
var tags []string var tags []string
if tstr, ok := ing.Annotations[AnnotationTags]; ok { if tstr, ok := ing.Annotations[AnnotationTags.String()]; ok {
tags = strings.Split(tstr, ",") tags = strings.Split(tstr, ",")
} }
hostname := hostnameForIngress(ing) hostname := hostnameForIngress(ing)
@ -220,7 +220,7 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
proxyType: proxyTypeIngressResource, proxyType: proxyTypeIngressResource,
} }
if val := ing.GetAnnotations()[AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy]; val == "true" { if val := ing.GetAnnotations()[AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy.String()]; val == "true" {
sts.ForwardClusterTrafficViaL7IngressProxy = true sts.ForwardClusterTrafficViaL7IngressProxy = true
} }
@ -264,7 +264,7 @@ func (a *IngressReconciler) shouldExpose(ing *networkingv1.Ingress) bool {
return ing != nil && return ing != nil &&
ing.Spec.IngressClassName != nil && ing.Spec.IngressClassName != nil &&
*ing.Spec.IngressClassName == tailscaleIngressClassName && *ing.Spec.IngressClassName == tailscaleIngressClassName &&
ing.Annotations[AnnotationProxyGroup] == "" ing.Annotations[AnnotationProxyGroup.String()] == ""
} }
// validateIngressClass attempts to validate that 'tailscale' IngressClass // validateIngressClass attempts to validate that 'tailscale' IngressClass

View File

@ -94,7 +94,7 @@ func TestTailscaleIngress(t *testing.T) {
// 3. Resources get created for Ingress that should allow forwarding // 3. Resources get created for Ingress that should allow forwarding
// cluster traffic // cluster traffic
mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) { mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) {
mak.Set(&ing.ObjectMeta.Annotations, AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy, "true") mak.Set(&ing.ObjectMeta.Annotations, AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy.String(), "true")
}) })
opts.shouldEnableForwardingClusterTrafficViaIngress = true opts.shouldEnableForwardingClusterTrafficViaIngress = true
expectReconciled(t, ingR, "default", "test") expectReconciled(t, ingR, "default", "test")
@ -230,7 +230,8 @@ func TestTailscaleIngressWithProxyClass(t *testing.T) {
Spec: tsapi.ProxyClassSpec{StatefulSet: &tsapi.StatefulSet{ Spec: tsapi.ProxyClassSpec{StatefulSet: &tsapi.StatefulSet{
Labels: tsapi.Labels{"foo": "bar"}, Labels: tsapi.Labels{"foo": "bar"},
Annotations: map[string]string{"bar.io/foo": "some-val"}, Annotations: map[string]string{"bar.io/foo": "some-val"},
Pod: &tsapi.Pod{Annotations: map[string]string{"foo.io/bar": "some-val"}}}}, Pod: &tsapi.Pod{Annotations: map[string]string{"foo.io/bar": "some-val"}},
}},
} }
fc := fake.NewClientBuilder(). fc := fake.NewClientBuilder().
WithScheme(tsapi.GlobalScheme). WithScheme(tsapi.GlobalScheme).
@ -285,7 +286,7 @@ func TestTailscaleIngressWithProxyClass(t *testing.T) {
// 2. Ingress is updated to specify a ProxyClass, ProxyClass is not yet // 2. Ingress is updated to specify a ProxyClass, ProxyClass is not yet
// ready, so proxy resource configuration does not change. // ready, so proxy resource configuration does not change.
mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) { mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) {
mak.Set(&ing.ObjectMeta.Labels, LabelProxyClass, "custom-metadata") mak.Set(&ing.ObjectMeta.Labels, LabelAnnotationProxyClass, "custom-metadata")
}) })
expectReconciled(t, ingR, "default", "test") expectReconciled(t, ingR, "default", "test")
expectEqual(t, fc, expectedSTSUserspace(t, fc, opts), removeHashAnnotation, removeResourceReqs) expectEqual(t, fc, expectedSTSUserspace(t, fc, opts), removeHashAnnotation, removeResourceReqs)
@ -309,7 +310,7 @@ func TestTailscaleIngressWithProxyClass(t *testing.T) {
// Ingress gets reconciled and the custom ProxyClass configuration is // Ingress gets reconciled and the custom ProxyClass configuration is
// removed from the proxy resources. // removed from the proxy resources.
mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) { mustUpdate(t, fc, "default", "test", func(ing *networkingv1.Ingress) {
delete(ing.ObjectMeta.Labels, LabelProxyClass) delete(ing.ObjectMeta.Labels, LabelAnnotationProxyClass)
}) })
expectReconciled(t, ingR, "default", "test") expectReconciled(t, ingR, "default", "test")
opts.proxyClass = "" opts.proxyClass = ""
@ -325,14 +326,15 @@ func TestTailscaleIngressWithServiceMonitor(t *testing.T) {
Status: metav1.ConditionTrue, Status: metav1.ConditionTrue,
Type: string(tsapi.ProxyClassReady), Type: string(tsapi.ProxyClassReady),
ObservedGeneration: 1, ObservedGeneration: 1,
}}}, }},
},
} }
crd := &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: serviceMonitorCRD}} crd := &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: serviceMonitorCRD}}
// Create fake client with ProxyClass, IngressClass, Ingress with metrics ProxyClass, and Service // Create fake client with ProxyClass, IngressClass, Ingress with metrics ProxyClass, and Service
ing := ingress() ing := ingress()
ing.Labels = map[string]string{ ing.Labels = map[string]string{
LabelProxyClass: "metrics", LabelAnnotationProxyClass: "metrics",
} }
fc := fake.NewClientBuilder(). fc := fake.NewClientBuilder().
WithScheme(tsapi.GlobalScheme). WithScheme(tsapi.GlobalScheme).
@ -452,7 +454,7 @@ func TestIngressLetsEncryptStaging(t *testing.T) {
ing := ingress() ing := ingress()
if tt.proxyClassPerResource != "" { if tt.proxyClassPerResource != "" {
ing.Labels = map[string]string{ ing.Labels = map[string]string{
LabelProxyClass: tt.proxyClassPerResource, LabelAnnotationProxyClass: tt.proxyClassPerResource,
} }
} }
mustCreate(t, fc, ing) mustCreate(t, fc, ing)

View File

@ -753,7 +753,7 @@ func proxyClassHandlerForSvc(cl client.Client, logger *zap.SugaredLogger) handle
return func(ctx context.Context, o client.Object) []reconcile.Request { return func(ctx context.Context, o client.Object) []reconcile.Request {
svcList := new(corev1.ServiceList) svcList := new(corev1.ServiceList)
labels := map[string]string{ labels := map[string]string{
LabelProxyClass: o.GetName(), LabelAnnotationProxyClass: o.GetName(),
} }
if err := cl.List(ctx, svcList, client.MatchingLabels(labels)); err != nil { if err := cl.List(ctx, svcList, client.MatchingLabels(labels)); err != nil {
logger.Debugf("error listing Services for ProxyClass: %v", err) logger.Debugf("error listing Services for ProxyClass: %v", err)
@ -774,7 +774,7 @@ func proxyClassHandlerForIngress(cl client.Client, logger *zap.SugaredLogger) ha
return func(ctx context.Context, o client.Object) []reconcile.Request { return func(ctx context.Context, o client.Object) []reconcile.Request {
ingList := new(networkingv1.IngressList) ingList := new(networkingv1.IngressList)
labels := map[string]string{ labels := map[string]string{
LabelProxyClass: o.GetName(), LabelAnnotationProxyClass: o.GetName(),
} }
if err := cl.List(ctx, ingList, client.MatchingLabels(labels)); err != nil { if err := cl.List(ctx, ingList, client.MatchingLabels(labels)); err != nil {
logger.Debugf("error listing Ingresses for ProxyClass: %v", err) logger.Debugf("error listing Ingresses for ProxyClass: %v", err)
@ -875,7 +875,7 @@ func serviceHandlerForIngress(cl client.Client, logger *zap.SugaredLogger) handl
} }
func serviceHandler(_ context.Context, o client.Object) []reconcile.Request { func serviceHandler(_ context.Context, o client.Object) []reconcile.Request {
if _, ok := o.GetAnnotations()[AnnotationProxyGroup]; ok { if _, ok := o.GetAnnotations()[AnnotationProxyGroup.String()]; ok {
// Do not reconcile Services for ProxyGroup. // Do not reconcile Services for ProxyGroup.
return nil return nil
} }
@ -1275,7 +1275,7 @@ func indexEgressServices(o client.Object) []string {
if !isEgressSvcForProxyGroup(o) { if !isEgressSvcForProxyGroup(o) {
return nil return nil
} }
return []string{o.GetAnnotations()[AnnotationProxyGroup]} return []string{o.GetAnnotations()[AnnotationProxyGroup.String()]}
} }
// indexPGIngresses adds a local index to a cached Tailscale Ingresses meant to be exposed on a ProxyGroup. The index is // indexPGIngresses adds a local index to a cached Tailscale Ingresses meant to be exposed on a ProxyGroup. The index is
@ -1284,7 +1284,7 @@ func indexPGIngresses(o client.Object) []string {
if !hasProxyGroupAnnotation(o) { if !hasProxyGroupAnnotation(o) {
return nil return nil
} }
return []string{o.GetAnnotations()[AnnotationProxyGroup]} return []string{o.GetAnnotations()[AnnotationProxyGroup.String()]}
} }
// serviceHandlerForIngressPG returns a handler for Service events that ensures that if the Service // serviceHandlerForIngressPG returns a handler for Service events that ensures that if the Service
@ -1325,7 +1325,7 @@ func serviceHandlerForIngressPG(cl client.Client, logger *zap.SugaredLogger) han
func hasProxyGroupAnnotation(obj client.Object) bool { func hasProxyGroupAnnotation(obj client.Object) bool {
ing := obj.(*networkingv1.Ingress) ing := obj.(*networkingv1.Ingress)
return ing.Annotations[AnnotationProxyGroup] != "" return ing.Annotations[AnnotationProxyGroup.String()] != ""
} }
func id(ctx context.Context, lc *local.Client) (string, error) { func id(ctx context.Context, lc *local.Client) (string, error) {

View File

@ -65,7 +65,7 @@ func TestLoadBalancerClass(t *testing.T) {
// on it being set. // on it being set.
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: "invalid.example.com", AnnotationTailnetTargetFQDN.String(): "invalid.example.com",
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -88,7 +88,7 @@ func TestLoadBalancerClass(t *testing.T) {
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: "invalid.example.com", AnnotationTailnetTargetFQDN.String(): "invalid.example.com",
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -242,7 +242,7 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
// on it being set. // on it being set.
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: tailnetTargetFQDN, AnnotationTailnetTargetFQDN.String(): tailnetTargetFQDN,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -276,7 +276,7 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
Finalizers: []string{"tailscale.com/finalizer"}, Finalizers: []string{"tailscale.com/finalizer"},
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetFQDN: tailnetTargetFQDN, AnnotationTailnetTargetFQDN.String(): tailnetTargetFQDN,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -298,7 +298,7 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
tailnetTargetFQDN = "bar.baz.ts.net" tailnetTargetFQDN = "bar.baz.ts.net"
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) { mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
s.ObjectMeta.Annotations = map[string]string{ s.ObjectMeta.Annotations = map[string]string{
AnnotationTailnetTargetFQDN: tailnetTargetFQDN, AnnotationTailnetTargetFQDN.String(): tailnetTargetFQDN,
} }
}) })
@ -354,7 +354,7 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
// on it being set. // on it being set.
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -388,7 +388,7 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
Finalizers: []string{"tailscale.com/finalizer"}, Finalizers: []string{"tailscale.com/finalizer"},
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -410,7 +410,7 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
tailnetTargetIP = "100.77.77.77" tailnetTargetIP = "100.77.77.77"
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) { mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
s.ObjectMeta.Annotations = map[string]string{ s.ObjectMeta.Annotations = map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
} }
}) })
@ -462,7 +462,7 @@ func TestTailnetTargetIPAnnotation_IPCouldNotBeParsed(t *testing.T) {
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -482,7 +482,7 @@ func TestTailnetTargetIPAnnotation_IPCouldNotBeParsed(t *testing.T) {
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -533,7 +533,7 @@ func TestTailnetTargetIPAnnotation_InvalidIP(t *testing.T) {
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -553,7 +553,7 @@ func TestTailnetTargetIPAnnotation_InvalidIP(t *testing.T) {
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationTailnetTargetIP: tailnetTargetIP, AnnotationTailnetTargetIP.String(): tailnetTargetIP,
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -1132,7 +1132,9 @@ func TestProxyClassForService(t *testing.T) {
StatefulSet: &tsapi.StatefulSet{ StatefulSet: &tsapi.StatefulSet{
Labels: tsapi.Labels{"foo": "bar"}, Labels: tsapi.Labels{"foo": "bar"},
Annotations: map[string]string{"bar.io/foo": "some-val"}, Annotations: map[string]string{"bar.io/foo": "some-val"},
Pod: &tsapi.Pod{Annotations: map[string]string{"foo.io/bar": "some-val"}}}}, Pod: &tsapi.Pod{Annotations: map[string]string{"foo.io/bar": "some-val"}},
},
},
} }
fc := fake.NewClientBuilder(). fc := fake.NewClientBuilder().
WithScheme(tsapi.GlobalScheme). WithScheme(tsapi.GlobalScheme).
@ -1194,7 +1196,7 @@ func TestProxyClassForService(t *testing.T) {
// pointing at the 'custom-metadata' ProxyClass. The ProxyClass is not // pointing at the 'custom-metadata' ProxyClass. The ProxyClass is not
// yet ready, so no changes are actually applied to the proxy resources. // yet ready, so no changes are actually applied to the proxy resources.
mustUpdate(t, fc, "default", "test", func(svc *corev1.Service) { mustUpdate(t, fc, "default", "test", func(svc *corev1.Service) {
mak.Set(&svc.Labels, LabelProxyClass, "custom-metadata") mak.Set(&svc.Labels, LabelAnnotationProxyClass, "custom-metadata")
}) })
expectReconciled(t, sr, "default", "test") expectReconciled(t, sr, "default", "test")
expectEqual(t, fc, expectedSTS(t, fc, opts), removeHashAnnotation, removeResourceReqs) expectEqual(t, fc, expectedSTS(t, fc, opts), removeHashAnnotation, removeResourceReqs)
@ -1220,7 +1222,7 @@ func TestProxyClassForService(t *testing.T) {
// configuration from the ProxyClass is removed from the cluster // configuration from the ProxyClass is removed from the cluster
// resources. // resources.
mustUpdate(t, fc, "default", "test", func(svc *corev1.Service) { mustUpdate(t, fc, "default", "test", func(svc *corev1.Service) {
delete(svc.Labels, LabelProxyClass) delete(svc.Labels, LabelAnnotationProxyClass)
}) })
opts.proxyClass = "" opts.proxyClass = ""
expectReconciled(t, sr, "default", "test") expectReconciled(t, sr, "default", "test")
@ -1667,7 +1669,7 @@ func Test_externalNameService(t *testing.T) {
// on it being set. // on it being set.
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Annotations: map[string]string{ Annotations: map[string]string{
AnnotationExpose: "true", AnnotationExpose.String(): "true",
}, },
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
@ -1711,14 +1713,15 @@ func Test_metricsResourceCreation(t *testing.T) {
Status: metav1.ConditionTrue, Status: metav1.ConditionTrue,
Type: string(tsapi.ProxyClassReady), Type: string(tsapi.ProxyClassReady),
ObservedGeneration: 1, ObservedGeneration: 1,
}}}, }},
},
} }
svc := &corev1.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test", Name: "test",
Namespace: "default", Namespace: "default",
UID: types.UID("1234-UID"), UID: types.UID("1234-UID"),
Labels: map[string]string{LabelProxyClass: "metrics"}, Labels: map[string]string{LabelAnnotationProxyClass: "metrics"},
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40", ClusterIP: "10.20.30.40",

View File

@ -327,11 +327,11 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.Pro
// TODO(irbekrm): remove this in 1.84. // TODO(irbekrm): remove this in 1.84.
hash := cfgHash hash := cfgHash
if capver >= 110 { if capver >= 110 {
hash = s.Spec.Template.GetAnnotations()[podAnnotationLastSetConfigFileHash] hash = PodAnnotationLastSetConfigFileHash.GetValue(s)
} }
s.Spec = ss.Spec s.Spec = ss.Spec
if hash != "" && pg.Spec.Type == tsapi.ProxyGroupTypeEgress { if hash != "" && pg.Spec.Type == tsapi.ProxyGroupTypeEgress {
mak.Set(&s.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash, hash) mak.Set(&s.Spec.Template.Annotations, PodAnnotationLastSetConfigFileHash.String(), hash)
} }
s.ObjectMeta.Labels = ss.ObjectMeta.Labels s.ObjectMeta.Labels = ss.ObjectMeta.Labels

View File

@ -617,7 +617,7 @@ func expectProxyGroupResources(t *testing.T, fc client.WithWatch, pg *tsapi.Prox
} }
statefulSet.Annotations = defaultProxyClassAnnotations statefulSet.Annotations = defaultProxyClassAnnotations
if cfgHash != "" { if cfgHash != "" {
mak.Set(&statefulSet.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash, cfgHash) mak.Set(&statefulSet.Spec.Template.Annotations, PodAnnotationLastSetConfigFileHash.String(), cfgHash)
} }
if shouldExist { if shouldExist {

View File

@ -161,7 +161,7 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) {
LabelParentName: "foo", LabelParentName: "foo",
} }
annots := map[string]string{ annots := map[string]string{
podAnnotationLastSetClusterIP: "1.2.3.4", PodAnnotationLastSetClusterIP.String(): "1.2.3.4",
} }
env := []corev1.EnvVar{{Name: "TS_HOSTNAME", Value: "nginx"}} env := []corev1.EnvVar{{Name: "TS_HOSTNAME", Value: "nginx"}}
userspaceProxySS.Labels = labels userspaceProxySS.Labels = labels
@ -341,28 +341,28 @@ func Test_mergeStatefulSetLabelsOrAnnots(t *testing.T) {
}, },
{ {
name: "no custom annots specified and none present in current annots, return current annots", name: "no custom annots specified and none present in current annots, return current annots",
current: map[string]string{podAnnotationLastSetClusterIP: "1.2.3.4"}, current: map[string]string{PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
want: map[string]string{podAnnotationLastSetClusterIP: "1.2.3.4"}, want: map[string]string{PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
managed: tailscaleManagedAnnotations, managed: tailscaleManagedAnnotations,
}, },
{ {
name: "no custom annots specified, but some present in current annots, return tailscale managed annots only from the current annots", name: "no custom annots specified, but some present in current annots, return tailscale managed annots only from the current annots",
current: map[string]string{"foo": "bar", "something.io/foo": "bar", podAnnotationLastSetClusterIP: "1.2.3.4"}, current: map[string]string{"foo": "bar", "something.io/foo": "bar", PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
want: map[string]string{podAnnotationLastSetClusterIP: "1.2.3.4"}, want: map[string]string{PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
managed: tailscaleManagedAnnotations, managed: tailscaleManagedAnnotations,
}, },
{ {
name: "custom annots specified, current annots only contain tailscale managed annots, return a union of both", name: "custom annots specified, current annots only contain tailscale managed annots, return a union of both",
current: map[string]string{podAnnotationLastSetClusterIP: "1.2.3.4"}, current: map[string]string{PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
custom: map[string]string{"foo": "bar", "something.io/foo": "bar"}, custom: map[string]string{"foo": "bar", "something.io/foo": "bar"},
want: map[string]string{"foo": "bar", "something.io/foo": "bar", podAnnotationLastSetClusterIP: "1.2.3.4"}, want: map[string]string{"foo": "bar", "something.io/foo": "bar", PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
managed: tailscaleManagedAnnotations, managed: tailscaleManagedAnnotations,
}, },
{ {
name: "custom annots specified, current annots contain tailscale managed annots and custom annots, some of which are not present in the new custom annots, return a union of managed annots and the desired custom annots", name: "custom annots specified, current annots contain tailscale managed annots and custom annots, some of which are not present in the new custom annots, return a union of managed annots and the desired custom annots",
current: map[string]string{"foo": "bar", "something.io/foo": "bar", podAnnotationLastSetClusterIP: "1.2.3.4"}, current: map[string]string{"foo": "bar", "something.io/foo": "bar", PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
custom: map[string]string{"something.io/foo": "bar"}, custom: map[string]string{"something.io/foo": "bar"},
want: map[string]string{"something.io/foo": "bar", podAnnotationLastSetClusterIP: "1.2.3.4"}, want: map[string]string{"something.io/foo": "bar", PodAnnotationLastSetClusterIP.String(): "1.2.3.4"},
managed: tailscaleManagedAnnotations, managed: tailscaleManagedAnnotations,
}, },
{ {

View File

@ -93,7 +93,7 @@ func childResourceLabels(name, ns, typ string) map[string]string {
func (a *ServiceReconciler) isTailscaleService(svc *corev1.Service) bool { func (a *ServiceReconciler) isTailscaleService(svc *corev1.Service) bool {
targetIP := tailnetTargetAnnotation(svc) targetIP := tailnetTargetAnnotation(svc)
targetFQDN := svc.Annotations[AnnotationTailnetTargetFQDN] targetFQDN := AnnotationTailnetTargetFQDN.GetValue(svc)
return a.shouldExpose(svc) || targetIP != "" || targetFQDN != "" return a.shouldExpose(svc) || targetIP != "" || targetFQDN != ""
} }
@ -112,7 +112,7 @@ func (a *ServiceReconciler) Reconcile(ctx context.Context, req reconcile.Request
return reconcile.Result{}, fmt.Errorf("failed to get svc: %w", err) return reconcile.Result{}, fmt.Errorf("failed to get svc: %w", err)
} }
if _, ok := svc.Annotations[AnnotationProxyGroup]; ok { if AnnotationProxyGroup.GetValue(svc) != "" {
return reconcile.Result{}, nil // this reconciler should not look at Services for ProxyGroup return reconcile.Result{}, nil // this reconciler should not look at Services for ProxyGroup
} }
@ -257,7 +257,7 @@ func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
} }
crl := childResourceLabels(svc.Name, svc.Namespace, "svc") crl := childResourceLabels(svc.Name, svc.Namespace, "svc")
var tags []string var tags []string
if tstr, ok := svc.Annotations[AnnotationTags]; ok { if tstr := AnnotationTags.GetValue(svc); tstr != "" {
tags = strings.Split(tstr, ",") tags = strings.Split(tstr, ",")
} }
@ -287,8 +287,9 @@ func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
sts.TailnetTargetIP = ip sts.TailnetTargetIP = ip
a.managedEgressProxies.Add(svc.UID) a.managedEgressProxies.Add(svc.UID)
gaugeEgressProxies.Set(int64(a.managedEgressProxies.Len())) gaugeEgressProxies.Set(int64(a.managedEgressProxies.Len()))
} else if fqdn := svc.Annotations[AnnotationTailnetTargetFQDN]; fqdn != "" { } else if fqdn := AnnotationTailnetTargetFQDN.GetValue(svc); fqdn != "" {
fqdn := svc.Annotations[AnnotationTailnetTargetFQDN] // NOTE (TOM): Do we need this? fqdn should already be set...
fqdn := AnnotationTailnetTargetFQDN.GetValue(svc)
if !strings.HasSuffix(fqdn, ".") { if !strings.HasSuffix(fqdn, ".") {
fqdn = fqdn + "." fqdn = fqdn + "."
} }
@ -367,15 +368,15 @@ func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
func validateService(svc *corev1.Service) []string { func validateService(svc *corev1.Service) []string {
violations := make([]string, 0) violations := make([]string, 0)
if svc.Annotations[AnnotationTailnetTargetFQDN] != "" && svc.Annotations[AnnotationTailnetTargetIP] != "" { if AnnotationTailnetTargetFQDN.GetValue(svc) != "" && AnnotationTailnetTargetIP.GetValue(svc) != "" {
violations = append(violations, fmt.Sprintf("only one of annotations %s and %s can be set", AnnotationTailnetTargetIP, AnnotationTailnetTargetFQDN)) violations = append(violations, fmt.Sprintf("only one of annotations %s and %s can be set", AnnotationTailnetTargetIP, AnnotationTailnetTargetFQDN))
} }
if fqdn := svc.Annotations[AnnotationTailnetTargetFQDN]; fqdn != "" { if fqdn := AnnotationTailnetTargetFQDN.GetValue(svc); fqdn != "" {
if !isMagicDNSName(fqdn) { if !isMagicDNSName(fqdn) {
violations = append(violations, fmt.Sprintf("invalid value of annotation %s: %q does not appear to be a valid MagicDNS name", AnnotationTailnetTargetFQDN, fqdn)) violations = append(violations, fmt.Sprintf("invalid value of annotation %s: %q does not appear to be a valid MagicDNS name", AnnotationTailnetTargetFQDN, fqdn))
} }
} }
if ipStr := svc.Annotations[AnnotationTailnetTargetIP]; ipStr != "" { if ipStr := AnnotationTailnetTargetIP.GetValue(svc); ipStr != "" {
ip, err := netip.ParseAddr(ipStr) ip, err := netip.ParseAddr(ipStr)
if err != nil { if err != nil {
violations = append(violations, fmt.Sprintf("invalid value of annotation %s: %q could not be parsed as a valid IP Address, error: %s", AnnotationTailnetTargetIP, ipStr, err)) violations = append(violations, fmt.Sprintf("invalid value of annotation %s: %q could not be parsed as a valid IP Address, error: %s", AnnotationTailnetTargetIP, ipStr, err))
@ -386,7 +387,7 @@ func validateService(svc *corev1.Service) []string {
svcName := nameForService(svc) svcName := nameForService(svc)
if err := dnsname.ValidLabel(svcName); err != nil { if err := dnsname.ValidLabel(svcName); err != nil {
if _, ok := svc.Annotations[AnnotationHostname]; ok { if AnnotationHostname.GetValue(svc) != "" {
violations = append(violations, fmt.Sprintf("invalid Tailscale hostname specified %q: %s", svcName, err)) violations = append(violations, fmt.Sprintf("invalid Tailscale hostname specified %q: %s", svcName, err))
} else { } else {
violations = append(violations, fmt.Sprintf("invalid Tailscale hostname %q, use %q annotation to override: %s", svcName, AnnotationHostname, err)) violations = append(violations, fmt.Sprintf("invalid Tailscale hostname %q, use %q annotation to override: %s", svcName, AnnotationHostname, err))
@ -420,7 +421,7 @@ func isTailscaleLoadBalancerService(svc *corev1.Service, isDefaultLoadBalancer b
// hasExposeAnnotation reports whether Service has the tailscale.com/expose // hasExposeAnnotation reports whether Service has the tailscale.com/expose
// annotation set // annotation set
func hasExposeAnnotation(svc *corev1.Service) bool { func hasExposeAnnotation(svc *corev1.Service) bool {
return svc != nil && svc.Annotations[AnnotationExpose] == "true" return svc != nil && AnnotationExpose.GetValue(svc) == "true"
} }
// tailnetTargetAnnotation returns the value of tailscale.com/tailnet-ip // tailnetTargetAnnotation returns the value of tailscale.com/tailnet-ip
@ -431,19 +432,24 @@ func tailnetTargetAnnotation(svc *corev1.Service) string {
if svc == nil { if svc == nil {
return "" return ""
} }
if ip := svc.Annotations[AnnotationTailnetTargetIP]; ip != "" { if ip := AnnotationTailnetTargetIP.GetValue(svc); ip != "" {
return ip return ip
} }
return svc.Annotations[annotationTailnetTargetIPOld]
return AnnotationTailnetTargetIPOld.GetValue(svc)
} }
// proxyClassForObject returns the proxy class for the given object. If the // proxyClassForObject returns the proxy class for the given object. If the
// object does not have a proxy class label, it returns the default proxy class // object does not have a proxy class label, it returns the default proxy class
func proxyClassForObject(o client.Object, proxyDefaultClass string) string { func proxyClassForObject(o client.Object, proxyDefaultClass string) string {
proxyClass, exists := o.GetLabels()[LabelProxyClass] proxyClass, exists := o.GetLabels()[LabelAnnotationProxyClass]
if !exists { if !exists {
proxyClass = proxyDefaultClass proxyClass, exists = o.GetAnnotations()[LabelAnnotationProxyClass]
if !exists {
proxyClass = proxyDefaultClass
}
} }
return proxyClass return proxyClass
} }

View File

@ -826,7 +826,7 @@ func (c *fakeTSClient) Deleted() []string {
// that we don't have to change the annotation in each test case after any // that we don't have to change the annotation in each test case after any
// change to the configfile contents). // change to the configfile contents).
func removeHashAnnotation(sts *appsv1.StatefulSet) { func removeHashAnnotation(sts *appsv1.StatefulSet) {
delete(sts.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash) delete(sts.Spec.Template.Annotations, PodAnnotationLastSetConfigFileHash.String())
if len(sts.Spec.Template.Annotations) == 0 { if len(sts.Spec.Template.Annotations) == 0 {
sts.Spec.Template.Annotations = nil sts.Spec.Template.Annotations = nil
} }