|
|
@@ -6,24 +6,15 @@
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
- "context"
|
|
|
"fmt"
|
|
|
- "strings"
|
|
|
- "sync"
|
|
|
"testing"
|
|
|
- "time"
|
|
|
|
|
|
- "github.com/google/go-cmp/cmp"
|
|
|
"go.uber.org/zap"
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
- apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
- "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
- "tailscale.com/client/tailscale"
|
|
|
"tailscale.com/types/ptr"
|
|
|
)
|
|
|
|
|
|
@@ -67,15 +58,18 @@ func TestLoadBalancerClass(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
+ opts := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
+ }
|
|
|
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, opts))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
- expectEqual(t, fc, expectedSTS(o))
|
|
|
+ expectEqual(t, fc, expectedSTS(opts))
|
|
|
|
|
|
// Normally the Tailscale proxy pod would come up here and write its info
|
|
|
// into the secret. Simulate that, then verify reconcile again and verify
|
|
|
@@ -159,6 +153,7 @@ func TestLoadBalancerClass(t *testing.T) {
|
|
|
}
|
|
|
expectEqual(t, fc, want)
|
|
|
}
|
|
|
+
|
|
|
func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
|
|
fc := fake.NewFakeClient()
|
|
|
ft := &fakeTSClient{}
|
|
|
@@ -204,15 +199,17 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
-
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
- expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
tailnetTargetFQDN: tailnetTargetFQDN,
|
|
|
hostname: "default-test",
|
|
|
}
|
|
|
+
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
+ expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
want := &corev1.Service{
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
@@ -235,14 +232,8 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
expectEqual(t, fc, want)
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o = stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- tailnetTargetFQDN: tailnetTargetFQDN,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
// Change the tailscale-target-fqdn annotation which should update the
|
|
|
@@ -272,6 +263,7 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
|
|
expectMissing[corev1.Service](t, fc, "operator-ns", shortName)
|
|
|
expectMissing[corev1.Secret](t, fc, "operator-ns", fullName)
|
|
|
}
|
|
|
+
|
|
|
func TestTailnetTargetIPAnnotation(t *testing.T) {
|
|
|
fc := fake.NewFakeClient()
|
|
|
ft := &fakeTSClient{}
|
|
|
@@ -317,15 +309,17 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
-
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
- expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
tailnetTargetIP: tailnetTargetIP,
|
|
|
hostname: "default-test",
|
|
|
}
|
|
|
+
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
+ expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
want := &corev1.Service{
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
@@ -348,14 +342,8 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
expectEqual(t, fc, want)
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o = stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- tailnetTargetIP: tailnetTargetIP,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
// Change the tailscale-target-ip annotation which should update the
|
|
|
@@ -428,14 +416,17 @@ func TestAnnotations(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
+ }
|
|
|
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
want := &corev1.Service{
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
@@ -533,14 +524,17 @@ func TestAnnotationIntoLB(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
+ }
|
|
|
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
// Normally the Tailscale proxy pod would come up here and write its info
|
|
|
@@ -586,11 +580,6 @@ func TestAnnotationIntoLB(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
// None of the proxy machinery should have changed...
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o = stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
// ... but the service should have a LoadBalancer status.
|
|
|
|
|
|
@@ -666,14 +655,17 @@ func TestLBIntoAnnotation(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
+ }
|
|
|
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
// Normally the Tailscale proxy pod would come up here and write its info
|
|
|
@@ -737,11 +729,6 @@ func TestLBIntoAnnotation(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o = stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
want = &corev1.Service{
|
|
|
@@ -809,14 +796,17 @@ func TestCustomHostname(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "reindeer-flotilla",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
+ }
|
|
|
|
|
|
- expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
+ expectEqual(t, fc, expectedSecret(t, o))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "reindeer-flotilla",
|
|
|
- }
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
want := &corev1.Service{
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
@@ -920,11 +910,14 @@ func TestCustomPriorityClassName(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
hostname: "tailscale-critical",
|
|
|
priorityClassName: "custom-priority-class-name",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
}
|
|
|
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
@@ -971,12 +964,14 @@ func TestDefaultLoadBalancer(t *testing.T) {
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
|
|
|
- // expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
|
|
expectEqual(t, fc, expectedHeadlessService(shortName))
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
}
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
}
|
|
|
@@ -1022,331 +1017,19 @@ func TestProxyFirewallMode(t *testing.T) {
|
|
|
expectReconciled(t, sr, "default", "test")
|
|
|
|
|
|
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
|
|
- o := stsOpts{
|
|
|
- name: shortName,
|
|
|
- secretName: fullName,
|
|
|
- hostname: "default-test",
|
|
|
- firewallMode: "nftables",
|
|
|
+ o := configOpts{
|
|
|
+ stsName: shortName,
|
|
|
+ secretName: fullName,
|
|
|
+ namespace: "default",
|
|
|
+ parentType: "svc",
|
|
|
+ hostname: "default-test",
|
|
|
+ firewallMode: "nftables",
|
|
|
+ clusterTargetIP: "10.20.30.40",
|
|
|
}
|
|
|
expectEqual(t, fc, expectedSTS(o))
|
|
|
|
|
|
}
|
|
|
|
|
|
-func expectedSecret(name, parentNamespace, typ string) *corev1.Secret {
|
|
|
- return &corev1.Secret{
|
|
|
- TypeMeta: metav1.TypeMeta{
|
|
|
- Kind: "Secret",
|
|
|
- APIVersion: "v1",
|
|
|
- },
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: name,
|
|
|
- Namespace: "operator-ns",
|
|
|
- Labels: map[string]string{
|
|
|
- "tailscale.com/managed": "true",
|
|
|
- "tailscale.com/parent-resource": "test",
|
|
|
- "tailscale.com/parent-resource-ns": parentNamespace,
|
|
|
- "tailscale.com/parent-resource-type": typ,
|
|
|
- },
|
|
|
- },
|
|
|
- StringData: map[string]string{
|
|
|
- "authkey": "secret-authkey",
|
|
|
- },
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectedHeadlessService(name string) *corev1.Service {
|
|
|
- return &corev1.Service{
|
|
|
- TypeMeta: metav1.TypeMeta{
|
|
|
- Kind: "Service",
|
|
|
- APIVersion: "v1",
|
|
|
- },
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: name,
|
|
|
- GenerateName: "ts-test-",
|
|
|
- Namespace: "operator-ns",
|
|
|
- Labels: map[string]string{
|
|
|
- "tailscale.com/managed": "true",
|
|
|
- "tailscale.com/parent-resource": "test",
|
|
|
- "tailscale.com/parent-resource-ns": "default",
|
|
|
- "tailscale.com/parent-resource-type": "svc",
|
|
|
- },
|
|
|
- },
|
|
|
- Spec: corev1.ServiceSpec{
|
|
|
- Selector: map[string]string{
|
|
|
- "app": "1234-UID",
|
|
|
- },
|
|
|
- ClusterIP: "None",
|
|
|
- },
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectedSTS(opts stsOpts) *appsv1.StatefulSet {
|
|
|
- containerEnv := []corev1.EnvVar{
|
|
|
- {Name: "TS_USERSPACE", Value: "false"},
|
|
|
- {Name: "TS_AUTH_ONCE", Value: "true"},
|
|
|
- {Name: "TS_KUBE_SECRET", Value: opts.secretName},
|
|
|
- {Name: "TS_HOSTNAME", Value: opts.hostname},
|
|
|
- }
|
|
|
- annots := map[string]string{
|
|
|
- "tailscale.com/operator-last-set-hostname": opts.hostname,
|
|
|
- }
|
|
|
- if opts.tailnetTargetIP != "" {
|
|
|
- annots["tailscale.com/operator-last-set-ts-tailnet-target-ip"] = opts.tailnetTargetIP
|
|
|
- containerEnv = append(containerEnv, corev1.EnvVar{
|
|
|
- Name: "TS_TAILNET_TARGET_IP",
|
|
|
- Value: opts.tailnetTargetIP,
|
|
|
- })
|
|
|
- } else if opts.tailnetTargetFQDN != "" {
|
|
|
- annots["tailscale.com/operator-last-set-ts-tailnet-target-fqdn"] = opts.tailnetTargetFQDN
|
|
|
- containerEnv = append(containerEnv, corev1.EnvVar{
|
|
|
- Name: "TS_TAILNET_TARGET_FQDN",
|
|
|
- Value: opts.tailnetTargetFQDN,
|
|
|
- })
|
|
|
-
|
|
|
- } else {
|
|
|
- containerEnv = append(containerEnv, corev1.EnvVar{
|
|
|
- Name: "TS_DEST_IP",
|
|
|
- Value: "10.20.30.40",
|
|
|
- })
|
|
|
-
|
|
|
- annots["tailscale.com/operator-last-set-cluster-ip"] = "10.20.30.40"
|
|
|
-
|
|
|
- }
|
|
|
- if opts.firewallMode != "" {
|
|
|
- containerEnv = append(containerEnv, corev1.EnvVar{
|
|
|
- Name: "TS_DEBUG_FIREWALL_MODE",
|
|
|
- Value: opts.firewallMode,
|
|
|
- })
|
|
|
- }
|
|
|
- return &appsv1.StatefulSet{
|
|
|
- TypeMeta: metav1.TypeMeta{
|
|
|
- Kind: "StatefulSet",
|
|
|
- APIVersion: "apps/v1",
|
|
|
- },
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: opts.name,
|
|
|
- Namespace: "operator-ns",
|
|
|
- Labels: map[string]string{
|
|
|
- "tailscale.com/managed": "true",
|
|
|
- "tailscale.com/parent-resource": "test",
|
|
|
- "tailscale.com/parent-resource-ns": "default",
|
|
|
- "tailscale.com/parent-resource-type": "svc",
|
|
|
- },
|
|
|
- },
|
|
|
- Spec: appsv1.StatefulSetSpec{
|
|
|
- Replicas: ptr.To[int32](1),
|
|
|
- Selector: &metav1.LabelSelector{
|
|
|
- MatchLabels: map[string]string{"app": "1234-UID"},
|
|
|
- },
|
|
|
- ServiceName: opts.name,
|
|
|
- Template: corev1.PodTemplateSpec{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Annotations: annots,
|
|
|
- DeletionGracePeriodSeconds: ptr.To[int64](10),
|
|
|
- Labels: map[string]string{"app": "1234-UID"},
|
|
|
- },
|
|
|
- Spec: corev1.PodSpec{
|
|
|
- ServiceAccountName: "proxies",
|
|
|
- PriorityClassName: opts.priorityClassName,
|
|
|
- InitContainers: []corev1.Container{
|
|
|
- {
|
|
|
- Name: "sysctler",
|
|
|
- Image: "tailscale/tailscale",
|
|
|
- Command: []string{"/bin/sh"},
|
|
|
- Args: []string{"-c", "sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1"},
|
|
|
- SecurityContext: &corev1.SecurityContext{
|
|
|
- Privileged: ptr.To(true),
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- Containers: []corev1.Container{
|
|
|
- {
|
|
|
- Name: "tailscale",
|
|
|
- Image: "tailscale/tailscale",
|
|
|
- Env: containerEnv,
|
|
|
- SecurityContext: &corev1.SecurityContext{
|
|
|
- Capabilities: &corev1.Capabilities{
|
|
|
- Add: []corev1.Capability{"NET_ADMIN"},
|
|
|
- },
|
|
|
- },
|
|
|
- ImagePullPolicy: "Always",
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func findGenName(t *testing.T, client client.Client, ns, name, typ string) (full, noSuffix string) {
|
|
|
- t.Helper()
|
|
|
- labels := map[string]string{
|
|
|
- LabelManaged: "true",
|
|
|
- LabelParentName: name,
|
|
|
- LabelParentNamespace: ns,
|
|
|
- LabelParentType: typ,
|
|
|
- }
|
|
|
- s, err := getSingleObject[corev1.Secret](context.Background(), client, "operator-ns", labels)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("finding secret for %q: %v", name, err)
|
|
|
- }
|
|
|
- if s == nil {
|
|
|
- t.Fatalf("no secret found for %q %s %+#v", name, ns, labels)
|
|
|
- }
|
|
|
- return s.GetName(), strings.TrimSuffix(s.GetName(), "-0")
|
|
|
-}
|
|
|
-
|
|
|
-func mustCreate(t *testing.T, client client.Client, obj client.Object) {
|
|
|
- t.Helper()
|
|
|
- if err := client.Create(context.Background(), obj); err != nil {
|
|
|
- t.Fatalf("creating %q: %v", obj.GetName(), err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
|
|
- t.Helper()
|
|
|
- obj := O(new(T))
|
|
|
- if err := client.Get(context.Background(), types.NamespacedName{
|
|
|
- Name: name,
|
|
|
- Namespace: ns,
|
|
|
- }, obj); err != nil {
|
|
|
- t.Fatalf("getting %q: %v", name, err)
|
|
|
- }
|
|
|
- update(obj)
|
|
|
- if err := client.Update(context.Background(), obj); err != nil {
|
|
|
- t.Fatalf("updating %q: %v", name, err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
|
|
- t.Helper()
|
|
|
- obj := O(new(T))
|
|
|
- if err := client.Get(context.Background(), types.NamespacedName{
|
|
|
- Name: name,
|
|
|
- Namespace: ns,
|
|
|
- }, obj); err != nil {
|
|
|
- t.Fatalf("getting %q: %v", name, err)
|
|
|
- }
|
|
|
- update(obj)
|
|
|
- if err := client.Status().Update(context.Background(), obj); err != nil {
|
|
|
- t.Fatalf("updating %q: %v", name, err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
|
|
- t.Helper()
|
|
|
- got := O(new(T))
|
|
|
- if err := client.Get(context.Background(), types.NamespacedName{
|
|
|
- Name: want.GetName(),
|
|
|
- Namespace: want.GetNamespace(),
|
|
|
- }, got); err != nil {
|
|
|
- t.Fatalf("getting %q: %v", want.GetName(), err)
|
|
|
- }
|
|
|
- // The resource version changes eagerly whenever the operator does even a
|
|
|
- // no-op update. Asserting a specific value leads to overly brittle tests,
|
|
|
- // so just remove it from both got and want.
|
|
|
- got.SetResourceVersion("")
|
|
|
- want.SetResourceVersion("")
|
|
|
- if diff := cmp.Diff(got, want); diff != "" {
|
|
|
- t.Fatalf("unexpected object (-got +want):\n%s", diff)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectMissing[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string) {
|
|
|
- t.Helper()
|
|
|
- obj := O(new(T))
|
|
|
- if err := client.Get(context.Background(), types.NamespacedName{
|
|
|
- Name: name,
|
|
|
- Namespace: ns,
|
|
|
- }, obj); !apierrors.IsNotFound(err) {
|
|
|
- t.Fatalf("object %s/%s unexpectedly present, wanted missing", ns, name)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectReconciled(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
|
|
- t.Helper()
|
|
|
- req := reconcile.Request{
|
|
|
- NamespacedName: types.NamespacedName{
|
|
|
- Namespace: ns,
|
|
|
- Name: name,
|
|
|
- },
|
|
|
- }
|
|
|
- res, err := sr.Reconcile(context.Background(), req)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("Reconcile: unexpected error: %v", err)
|
|
|
- }
|
|
|
- if res.Requeue {
|
|
|
- t.Fatalf("unexpected immediate requeue")
|
|
|
- }
|
|
|
- if res.RequeueAfter != 0 {
|
|
|
- t.Fatalf("unexpected timed requeue (%v)", res.RequeueAfter)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func expectRequeue(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
|
|
- t.Helper()
|
|
|
- req := reconcile.Request{
|
|
|
- NamespacedName: types.NamespacedName{
|
|
|
- Name: name,
|
|
|
- Namespace: ns,
|
|
|
- },
|
|
|
- }
|
|
|
- res, err := sr.Reconcile(context.Background(), req)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("Reconcile: unexpected error: %v", err)
|
|
|
- }
|
|
|
- if res.RequeueAfter == 0 {
|
|
|
- t.Fatalf("expected timed requeue, got success")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-type stsOpts struct {
|
|
|
- name string
|
|
|
- secretName string
|
|
|
- hostname string
|
|
|
- priorityClassName string
|
|
|
- firewallMode string
|
|
|
- tailnetTargetIP string
|
|
|
- tailnetTargetFQDN string
|
|
|
-}
|
|
|
-
|
|
|
-type fakeTSClient struct {
|
|
|
- sync.Mutex
|
|
|
- keyRequests []tailscale.KeyCapabilities
|
|
|
- deleted []string
|
|
|
-}
|
|
|
-
|
|
|
-func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabilities) (string, *tailscale.Key, error) {
|
|
|
- c.Lock()
|
|
|
- defer c.Unlock()
|
|
|
- c.keyRequests = append(c.keyRequests, caps)
|
|
|
- k := &tailscale.Key{
|
|
|
- ID: "key",
|
|
|
- Created: time.Now(),
|
|
|
- Capabilities: caps,
|
|
|
- }
|
|
|
- return "secret-authkey", k, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (c *fakeTSClient) DeleteDevice(ctx context.Context, deviceID string) error {
|
|
|
- c.Lock()
|
|
|
- defer c.Unlock()
|
|
|
- c.deleted = append(c.deleted, deviceID)
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func (c *fakeTSClient) KeyRequests() []tailscale.KeyCapabilities {
|
|
|
- c.Lock()
|
|
|
- defer c.Unlock()
|
|
|
- return c.keyRequests
|
|
|
-}
|
|
|
-
|
|
|
-func (c *fakeTSClient) Deleted() []string {
|
|
|
- c.Lock()
|
|
|
- defer c.Unlock()
|
|
|
- return c.deleted
|
|
|
-}
|
|
|
-
|
|
|
func Test_isMagicDNSName(t *testing.T) {
|
|
|
tests := []struct {
|
|
|
in string
|