mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +00:00
k8s-operator: switch annotations to use iota-based enums (#14323)
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
parent
7090f7fffc
commit
e2e5914d57
@ -40,6 +40,179 @@ import (
|
|||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Annotation identifies an annotation utilized by the tailscale kubernetes operator.
|
||||||
|
type Annotation uint
|
||||||
|
|
||||||
|
// AnnotationScope identifies which resources it should be expected on
|
||||||
|
type AnnotationScope uint
|
||||||
|
|
||||||
|
var annotationByValue = map[string]Annotation{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for a := Annotation(0); a <= PodAnnotationLastSetConfigFileHash; a++ {
|
||||||
|
if _, ok := annotationScopes[a]; !ok {
|
||||||
|
panic("missing scope for Annotation: " + strconv.Itoa(int(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
val := a.String()
|
||||||
|
if val != "" && val != "unknown annotation value "+strconv.Itoa(int(a)) {
|
||||||
|
annotationByValue[val] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeUnknown AnnotationScope = iota
|
||||||
|
ScopeService
|
||||||
|
ScopeIngress
|
||||||
|
ScopeGeneral
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnotationProxyClass Annotation = iota
|
||||||
|
AnnotationFinalizer
|
||||||
|
AnnotationExpose
|
||||||
|
AnnotationTags
|
||||||
|
AnnotationHostname
|
||||||
|
AnnotationTailnetTargetIPOld
|
||||||
|
AnnotationTailnetTargetIP
|
||||||
|
AnnotationTailnetTargetFQDN
|
||||||
|
AnnotationProxyGroup
|
||||||
|
AnnotationHTTPEndpoint
|
||||||
|
AnnotationOwnerReferences
|
||||||
|
AnnotationFunnel
|
||||||
|
AnnotationMagicDNSName
|
||||||
|
AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy
|
||||||
|
PodAnnotationLastSetClusterIP
|
||||||
|
PodAnnotationLastSetClusterDNSName
|
||||||
|
PodAnnotationLastSetTailnetTargetIP
|
||||||
|
PodAnnotationLastSetTailnetTargetFQDN
|
||||||
|
PodAnnotationLastSetConfigFileHash
|
||||||
|
)
|
||||||
|
|
||||||
|
var annotationScopes = map[Annotation][]AnnotationScope{
|
||||||
|
AnnotationProxyClass: {ScopeGeneral},
|
||||||
|
AnnotationFinalizer: {ScopeGeneral},
|
||||||
|
AnnotationExpose: {ScopeService},
|
||||||
|
AnnotationTags: {ScopeService},
|
||||||
|
AnnotationHostname: {ScopeService},
|
||||||
|
AnnotationTailnetTargetIPOld: {ScopeService},
|
||||||
|
AnnotationTailnetTargetIP: {ScopeService},
|
||||||
|
AnnotationTailnetTargetFQDN: {ScopeService},
|
||||||
|
AnnotationProxyGroup: {ScopeService},
|
||||||
|
AnnotationHTTPEndpoint: {ScopeIngress},
|
||||||
|
AnnotationOwnerReferences: {ScopeIngress},
|
||||||
|
AnnotationFunnel: {ScopeIngress},
|
||||||
|
AnnotationMagicDNSName: {ScopeService},
|
||||||
|
AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy: {ScopeIngress},
|
||||||
|
PodAnnotationLastSetClusterIP: {ScopeService, ScopeIngress},
|
||||||
|
PodAnnotationLastSetClusterDNSName: {ScopeService, ScopeIngress},
|
||||||
|
PodAnnotationLastSetTailnetTargetIP: {ScopeService, ScopeIngress},
|
||||||
|
PodAnnotationLastSetTailnetTargetFQDN: {ScopeService, ScopeIngress},
|
||||||
|
PodAnnotationLastSetConfigFileHash: {ScopeService, ScopeIngress},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Annotation) String() string {
|
||||||
|
switch a {
|
||||||
|
case AnnotationProxyClass:
|
||||||
|
return "tailscale.com/proxy-class"
|
||||||
|
case AnnotationFinalizer:
|
||||||
|
return "tailscale.com/finalizer"
|
||||||
|
case AnnotationExpose:
|
||||||
|
return "tailscale.com/expose"
|
||||||
|
case AnnotationTags:
|
||||||
|
return "tailscale.com/tags"
|
||||||
|
case AnnotationHostname:
|
||||||
|
return "tailscale.com/hostname"
|
||||||
|
case AnnotationTailnetTargetIPOld:
|
||||||
|
return "tailscale.com/ts-tailnet-target-ip"
|
||||||
|
case AnnotationTailnetTargetIP:
|
||||||
|
return "tailscale.com/tailnet-ip"
|
||||||
|
case AnnotationTailnetTargetFQDN:
|
||||||
|
return "tailscale.com/tailnet-fqdn"
|
||||||
|
case AnnotationProxyGroup:
|
||||||
|
return "tailscale.com/proxy-group"
|
||||||
|
case AnnotationHTTPEndpoint:
|
||||||
|
return "tailscale.com/http-endpoint"
|
||||||
|
case AnnotationOwnerReferences:
|
||||||
|
return "tailscale.com/owner-references"
|
||||||
|
case AnnotationFunnel:
|
||||||
|
return "tailscale.com/funnel"
|
||||||
|
case AnnotationMagicDNSName:
|
||||||
|
return "tailscale.com/magic-dnsname"
|
||||||
|
case AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy:
|
||||||
|
return "tailscale.com/experimental-forward-cluster-traffic-via-ingress"
|
||||||
|
case PodAnnotationLastSetClusterIP:
|
||||||
|
return "tailscale.com/operator-last-set-cluster-ip"
|
||||||
|
case PodAnnotationLastSetClusterDNSName:
|
||||||
|
return "tailscale.com/operator-last-set-cluster-dns-name"
|
||||||
|
case PodAnnotationLastSetTailnetTargetIP:
|
||||||
|
return "tailscale.com/operator-last-set-ts-tailnet-target-ip"
|
||||||
|
case PodAnnotationLastSetTailnetTargetFQDN:
|
||||||
|
return "tailscale.com/operator-last-set-ts-tailnet-target-fqdn"
|
||||||
|
case PodAnnotationLastSetConfigFileHash:
|
||||||
|
return "tailscale.com/operator-last-set-config-file-hash"
|
||||||
|
default:
|
||||||
|
return "unknown annotation value " + strconv.Itoa(int(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAnnotation(s string) (Annotation, bool) {
|
||||||
|
a, ok := annotationByValue[s]
|
||||||
|
return a, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateAnnotations(o client.Object) error {
|
||||||
|
annotations := o.GetAnnotations()
|
||||||
|
var scope AnnotationScope
|
||||||
|
switch k := o.GetObjectKind().GroupVersionKind().Kind; k {
|
||||||
|
case "Ingress":
|
||||||
|
scope = ScopeIngress
|
||||||
|
case "Service":
|
||||||
|
scope = ScopeService
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations:
|
||||||
|
for a := range annotations {
|
||||||
|
if !strings.HasPrefix(a, "tailscale.com/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ann, ok := ParseAnnotation(a)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to parse annotation '%s'", a)
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes, ok := annotationScopes[ann]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to get annotation scopes '%s'", a)
|
||||||
|
}
|
||||||
|
for _, s := range scopes {
|
||||||
|
if s == ScopeGeneral || s == scope {
|
||||||
|
break annotations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unexpected annotation '%s'", a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Annotation) GetValue(o client.Object) string {
|
||||||
|
annots := o.GetAnnotations()
|
||||||
|
if annots == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := annots[a.String()]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Labels that the operator sets on StatefulSets and Pods. If you add a
|
// Labels that the operator sets on StatefulSets and Pods. If you add a
|
||||||
// new label here, do also add it to tailscaleManagedLabels var to
|
// new label here, do also add it to tailscaleManagedLabels var to
|
||||||
@ -48,51 +221,20 @@ const (
|
|||||||
LabelParentName = "tailscale.com/parent-resource"
|
LabelParentName = "tailscale.com/parent-resource"
|
||||||
LabelParentNamespace = "tailscale.com/parent-resource-ns"
|
LabelParentNamespace = "tailscale.com/parent-resource-ns"
|
||||||
|
|
||||||
// LabelProxyClass can be set by users on tailscale Ingresses and Services that define cluster ingress or
|
// LabelAnnotationProxyClass can be set (on annotation or label) by users on tailscale Ingresses and Services
|
||||||
// cluster egress, to specify that configuration in this ProxyClass should be applied to resources created for
|
// that define cluster ingress or cluster egress, to specify that configuration in this ProxyClass should be
|
||||||
// the Ingress or Service.
|
// applied to resources created for the Ingress or Service. If this is set as both an annotation and a
|
||||||
LabelProxyClass = "tailscale.com/proxy-class"
|
// label, the value set on the label will be used.
|
||||||
|
LabelAnnotationProxyClass = "tailscale.com/proxy-class"
|
||||||
|
|
||||||
FinalizerName = "tailscale.com/finalizer"
|
FinalizerName = "tailscale.com/finalizer"
|
||||||
|
|
||||||
// Annotations settable by users on services.
|
// podAnnotationLastSetClusterIP = "tailscale.com/operator-last-set-cluster-ip"
|
||||||
AnnotationExpose = "tailscale.com/expose"
|
// podAnnotationLastSetClusterDNSName = "tailscale.com/operator-last-set-cluster-dns-name"
|
||||||
AnnotationTags = "tailscale.com/tags"
|
// podAnnotationLastSetTailnetTargetIP = "tailscale.com/operator-last-set-ts-tailnet-target-ip"
|
||||||
AnnotationHostname = "tailscale.com/hostname"
|
// podAnnotationLastSetTailnetTargetFQDN = "tailscale.com/operator-last-set-ts-tailnet-target-fqdn"
|
||||||
annotationTailnetTargetIPOld = "tailscale.com/ts-tailnet-target-ip"
|
// // podAnnotationLastSetConfigFileHash is sha256 hash of the current tailscaled configuration contents.
|
||||||
AnnotationTailnetTargetIP = "tailscale.com/tailnet-ip"
|
// podAnnotationLastSetConfigFileHash = "tailscale.com/operator-last-set-config-file-hash"
|
||||||
//MagicDNS name of tailnet node.
|
|
||||||
AnnotationTailnetTargetFQDN = "tailscale.com/tailnet-fqdn"
|
|
||||||
|
|
||||||
AnnotationProxyGroup = "tailscale.com/proxy-group"
|
|
||||||
|
|
||||||
// Annotations settable by users on ingresses.
|
|
||||||
AnnotationFunnel = "tailscale.com/funnel"
|
|
||||||
|
|
||||||
// If set to true, set up iptables/nftables rules in the proxy forward
|
|
||||||
// cluster traffic to the tailnet IP of that proxy. This can only be set
|
|
||||||
// on an Ingress. This is useful in cases where a cluster target needs
|
|
||||||
// to be able to reach a cluster workload exposed to tailnet via Ingress
|
|
||||||
// using the same hostname as a tailnet workload (in this case, the
|
|
||||||
// MagicDNS name of the ingress proxy). This annotation is experimental.
|
|
||||||
// If it is set to true, the proxy set up for Ingress, will run
|
|
||||||
// tailscale in non-userspace, with NET_ADMIN cap for tailscale
|
|
||||||
// container and will also run a privileged init container that enables
|
|
||||||
// forwarding.
|
|
||||||
// Eventually this behaviour might become the default.
|
|
||||||
AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy = "tailscale.com/experimental-forward-cluster-traffic-via-ingress"
|
|
||||||
|
|
||||||
// Annotations set by the operator on pods to trigger restarts when the
|
|
||||||
// hostname, IP, FQDN or tailscaled config changes. If you add a new
|
|
||||||
// annotation here, also add it to tailscaleManagedAnnotations var to
|
|
||||||
// ensure that it does not get removed when a ProxyClass configuration
|
|
||||||
// is applied.
|
|
||||||
podAnnotationLastSetClusterIP = "tailscale.com/operator-last-set-cluster-ip"
|
|
||||||
podAnnotationLastSetClusterDNSName = "tailscale.com/operator-last-set-cluster-dns-name"
|
|
||||||
podAnnotationLastSetTailnetTargetIP = "tailscale.com/operator-last-set-ts-tailnet-target-ip"
|
|
||||||
podAnnotationLastSetTailnetTargetFQDN = "tailscale.com/operator-last-set-ts-tailnet-target-fqdn"
|
|
||||||
// podAnnotationLastSetConfigFileHash is sha256 hash of the current tailscaled configuration contents.
|
|
||||||
podAnnotationLastSetConfigFileHash = "tailscale.com/operator-last-set-config-file-hash"
|
|
||||||
|
|
||||||
proxyTypeEgress = "egress_service"
|
proxyTypeEgress = "egress_service"
|
||||||
proxyTypeIngressService = "ingress_service"
|
proxyTypeIngressService = "ingress_service"
|
||||||
@ -109,8 +251,47 @@ const (
|
|||||||
var (
|
var (
|
||||||
// tailscaleManagedLabels are label keys that tailscale operator sets on StatefulSets and Pods.
|
// tailscaleManagedLabels are label keys that tailscale operator sets on StatefulSets and Pods.
|
||||||
tailscaleManagedLabels = []string{kubetypes.LabelManaged, LabelParentType, LabelParentName, LabelParentNamespace, "app"}
|
tailscaleManagedLabels = []string{kubetypes.LabelManaged, LabelParentType, LabelParentName, LabelParentNamespace, "app"}
|
||||||
|
|
||||||
|
// taiscaleLabels are all the tailscale labels (managed and unmanaged)
|
||||||
|
tailscaleLabels = append(
|
||||||
|
[]string{},
|
||||||
|
append(tailscaleManagedLabels, LabelAnnotationProxyClass)...,
|
||||||
|
)
|
||||||
|
|
||||||
// tailscaleManagedAnnotations are annotation keys that tailscale operator sets on StatefulSets and Pods.
|
// tailscaleManagedAnnotations are annotation keys that tailscale operator sets on StatefulSets and Pods.
|
||||||
tailscaleManagedAnnotations = []string{podAnnotationLastSetClusterIP, podAnnotationLastSetTailnetTargetIP, podAnnotationLastSetTailnetTargetFQDN, podAnnotationLastSetConfigFileHash}
|
tailscaleManagedAnnotations = []string{PodAnnotationLastSetClusterIP.String(), PodAnnotationLastSetTailnetTargetIP.String(), PodAnnotationLastSetTailnetTargetFQDN.String(), PodAnnotationLastSetConfigFileHash.String()}
|
||||||
|
|
||||||
|
// // taiscaleAnnotations are all the tailscale labels (managed and unmanaged)
|
||||||
|
// tailscaleAnnotations = append(
|
||||||
|
// []string{},
|
||||||
|
// append(
|
||||||
|
// tailscaleManagedAnnotations,
|
||||||
|
// AnnotationExpose,
|
||||||
|
// AnnotationTags,
|
||||||
|
// AnnotationHostname,
|
||||||
|
// annotationTailnetTargetIPOld,
|
||||||
|
// AnnotationTailnetTargetIP,
|
||||||
|
// AnnotationTailnetTargetFQDN,
|
||||||
|
// AnnotationProxyGroup,
|
||||||
|
// AnnotationFunnel,
|
||||||
|
// AnnotationExperimentalForwardClusterTrafficViaL7IngresProxy,
|
||||||
|
// podAnnotationLastSetClusterDNSName, // this one wasn't in managed list
|
||||||
|
// )...,
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// taiscaleAnnotations are all the tailscale labels (managed and unmanaged) that are expected on a service resource
|
||||||
|
// tailscaleSvcAnnotations = append(
|
||||||
|
// []string{
|
||||||
|
// AnnotationProxyGroup,
|
||||||
|
// AnnotationExpose,
|
||||||
|
// AnnotationTags,
|
||||||
|
// AnnotationHostname,
|
||||||
|
// annotationTailnetTargetIPOld,
|
||||||
|
// AnnotationTailnetTargetIP,
|
||||||
|
// AnnotationTailnetTargetFQDN,
|
||||||
|
// LabelAnnotationProxyClass,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
)
|
)
|
||||||
|
|
||||||
type tailscaleSTSConfig struct {
|
type tailscaleSTSConfig struct {
|
||||||
@ -622,25 +803,25 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
|||||||
Name: "TS_DEST_IP",
|
Name: "TS_DEST_IP",
|
||||||
Value: sts.ClusterTargetIP,
|
Value: sts.ClusterTargetIP,
|
||||||
})
|
})
|
||||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetClusterIP, sts.ClusterTargetIP)
|
mak.Set(&ss.Spec.Template.Annotations, PodAnnotationLastSetClusterIP.String(), sts.ClusterTargetIP)
|
||||||
} else if sts.ClusterTargetDNSName != "" {
|
} else if sts.ClusterTargetDNSName != "" {
|
||||||
container.Env = append(container.Env, corev1.EnvVar{
|
container.Env = append(container.Env, corev1.EnvVar{
|
||||||
Name: "TS_EXPERIMENTAL_DEST_DNS_NAME",
|
Name: "TS_EXPERIMENTAL_DEST_DNS_NAME",
|
||||||
Value: sts.ClusterTargetDNSName,
|
Value: sts.ClusterTargetDNSName,
|
||||||
})
|
})
|
||||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetClusterDNSName, sts.ClusterTargetDNSName)
|
mak.Set(&ss.Spec.Template.Annotations, PodAnnotationLastSetClusterDNSName.String(), sts.ClusterTargetDNSName)
|
||||||
} else if sts.TailnetTargetIP != "" {
|
} else if sts.TailnetTargetIP != "" {
|
||||||
container.Env = append(container.Env, corev1.EnvVar{
|
container.Env = append(container.Env, corev1.EnvVar{
|
||||||
Name: "TS_TAILNET_TARGET_IP",
|
Name: "TS_TAILNET_TARGET_IP",
|
||||||
Value: sts.TailnetTargetIP,
|
Value: sts.TailnetTargetIP,
|
||||||
})
|
})
|
||||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetTailnetTargetIP, sts.TailnetTargetIP)
|
mak.Set(&ss.Spec.Template.Annotations, PodAnnotationLastSetTailnetTargetIP.String(), sts.TailnetTargetIP)
|
||||||
} else if sts.TailnetTargetFQDN != "" {
|
} else if sts.TailnetTargetFQDN != "" {
|
||||||
container.Env = append(container.Env, corev1.EnvVar{
|
container.Env = append(container.Env, corev1.EnvVar{
|
||||||
Name: "TS_TAILNET_TARGET_FQDN",
|
Name: "TS_TAILNET_TARGET_FQDN",
|
||||||
Value: sts.TailnetTargetFQDN,
|
Value: sts.TailnetTargetFQDN,
|
||||||
})
|
})
|
||||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetTailnetTargetFQDN, sts.TailnetTargetFQDN)
|
mak.Set(&ss.Spec.Template.Annotations, PodAnnotationLastSetTailnetTargetFQDN.String(), sts.TailnetTargetFQDN)
|
||||||
} else if sts.ServeConfig != nil {
|
} else if sts.ServeConfig != nil {
|
||||||
container.Env = append(container.Env, corev1.EnvVar{
|
container.Env = append(container.Env, corev1.EnvVar{
|
||||||
Name: "TS_SERVE_CONFIG",
|
Name: "TS_SERVE_CONFIG",
|
||||||
@ -698,11 +879,11 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
|||||||
// TODO(irbekrm): remove this in 1.84.
|
// TODO(irbekrm): remove this in 1.84.
|
||||||
hash := tsConfigHash
|
hash := tsConfigHash
|
||||||
if dev == nil || dev.capver >= 110 {
|
if dev == nil || dev.capver >= 110 {
|
||||||
hash = s.Spec.Template.GetAnnotations()[podAnnotationLastSetConfigFileHash]
|
hash = PodAnnotationLastSetConfigFileHash.GetValue(s)
|
||||||
}
|
}
|
||||||
s.Spec = ss.Spec
|
s.Spec = ss.Spec
|
||||||
if hash != "" {
|
if hash != "" {
|
||||||
mak.Set(&s.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash, hash)
|
mak.Set(&s.Spec.Template.Annotations, PodAnnotationLastSetConfigFileHash.String(), hash)
|
||||||
}
|
}
|
||||||
s.ObjectMeta.Labels = ss.Labels
|
s.ObjectMeta.Labels = ss.Labels
|
||||||
s.ObjectMeta.Annotations = ss.Annotations
|
s.ObjectMeta.Annotations = ss.Annotations
|
||||||
@ -1143,9 +1324,10 @@ func defaultEnv(envName, defVal string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nameForService(svc *corev1.Service) string {
|
func nameForService(svc *corev1.Service) string {
|
||||||
if h, ok := svc.Annotations[AnnotationHostname]; ok {
|
if h := AnnotationHostname.GetValue(svc); h != "" {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.Namespace + "-" + svc.Name
|
return svc.Namespace + "-" + svc.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user