diff --git a/cmd/k8s-operator/connector.go b/cmd/k8s-operator/connector.go
index 26ee0b7c6..770c5e4fc 100644
--- a/cmd/k8s-operator/connector.go
+++ b/cmd/k8s-operator/connector.go
@@ -108,7 +108,7 @@ func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Reque
}
oldCnStatus := cn.Status.DeepCopy()
- setStatus := func(cn *tsapi.Connector, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) {
+ setStatus := func(cn *tsapi.Connector, _ tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) {
tsoperator.SetConnectorCondition(cn, tsapi.ConnectorReady, status, reason, message, cn.Generation, a.clock, logger)
if !apiequality.Semantic.DeepEqual(oldCnStatus, cn.Status) {
// An error encountered here should get returned by the Reconcile function.
@@ -211,7 +211,27 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge
gaugeConnectorResources.Set(int64(connectors.Len()))
_, err := a.ssr.Provision(ctx, logger, sts)
- return err
+ if err != nil {
+ return err
+ }
+
+ _, tsHost, ips, err := a.ssr.DeviceInfo(ctx, crl)
+ if err != nil {
+ return err
+ }
+
+ if tsHost == "" {
+ logger.Debugf("no Tailscale hostname known yet, waiting for connector pod to finish auth")
+ // No hostname yet. Wait for the connector pod to auth.
+ cn.Status.TailnetIPs = nil
+ cn.Status.Hostname = ""
+ return nil
+ }
+
+ cn.Status.TailnetIPs = ips
+ cn.Status.Hostname = tsHost
+
+ return nil
}
func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) {
diff --git a/cmd/k8s-operator/connector_test.go b/cmd/k8s-operator/connector_test.go
index 07f0e2d97..a2a6e0be9 100644
--- a/cmd/k8s-operator/connector_test.go
+++ b/cmd/k8s-operator/connector_test.go
@@ -17,6 +17,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
"tailscale.com/tstest"
+ "tailscale.com/util/mak"
)
func TestConnector(t *testing.T) {
@@ -29,7 +30,7 @@ func TestConnector(t *testing.T) {
},
TypeMeta: metav1.TypeMeta{
Kind: tsapi.ConnectorKind,
- APIVersion: "tailscale.io/v1alpha1",
+ APIVersion: "tailscale.com/v1alpha1",
},
Spec: tsapi.ConnectorSpec{
SubnetRouter: &tsapi.SubnetRouter{
@@ -77,6 +78,23 @@ func TestConnector(t *testing.T) {
expectEqual(t, fc, expectedSecret(t, opts), nil)
expectEqual(t, fc, expectedSTS(t, fc, opts), removeHashAnnotation)
+ // Connector status should get updated with the IP/hostname info when available.
+ const hostname = "foo.tailnetxyz.ts.net"
+ mustUpdate(t, fc, "operator-ns", opts.secretName, func(secret *corev1.Secret) {
+ mak.Set(&secret.Data, "device_id", []byte("1234"))
+ mak.Set(&secret.Data, "device_fqdn", []byte(hostname))
+ mak.Set(&secret.Data, "device_ips", []byte(`["127.0.0.1", "::1"]`))
+ })
+ expectReconciled(t, cr, "", "test")
+ cn.Finalizers = append(cn.Finalizers, "tailscale.com/finalizer")
+ cn.Status.IsExitNode = cn.Spec.ExitNode
+ cn.Status.SubnetRoutes = cn.Spec.SubnetRouter.AdvertiseRoutes.Stringify()
+ cn.Status.Hostname = hostname
+ cn.Status.TailnetIPs = []string{"127.0.0.1", "::1"}
+ expectEqual(t, fc, cn, func(o *tsapi.Connector) {
+ o.Status.Conditions = nil
+ })
+
// Add another route to be advertised.
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
conn.Spec.SubnetRouter.AdvertiseRoutes = []tsapi.Route{"10.40.0.0/14", "10.44.0.0/20"}
diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
index 7811f22f7..2a5d8039c 100644
--- a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
+++ b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
@@ -117,12 +117,20 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
+ hostname:
+ description: Hostname is the fully qualified domain name of the Connector node. If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the node.
+ type: string
isExitNode:
description: IsExitNode is set to true if the Connector acts as an exit node.
type: boolean
subnetRoutes:
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
type: string
+ tailnetIPs:
+ description: TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6) assigned to the Connector node.
+ type: array
+ items:
+ type: string
served: true
storage: true
subresources:
diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml
index abbc6d242..3f5fe4951 100644
--- a/cmd/k8s-operator/deploy/manifests/operator.yaml
+++ b/cmd/k8s-operator/deploy/manifests/operator.yaml
@@ -142,12 +142,20 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
+ hostname:
+ description: Hostname is the fully qualified domain name of the Connector node. If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the node.
+ type: string
isExitNode:
description: IsExitNode is set to true if the Connector acts as an exit node.
type: boolean
subnetRoutes:
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
type: string
+ tailnetIPs:
+ description: TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6) assigned to the Connector node.
+ items:
+ type: string
+ type: array
type: object
required:
- spec
diff --git a/k8s-operator/api.md b/k8s-operator/api.md
index 9ec4641b2..f15bd3577 100644
--- a/k8s-operator/api.md
+++ b/k8s-operator/api.md
@@ -178,6 +178,13 @@ ConnectorStatus describes the status of the Connector. This is set and managed b
List of status conditions to indicate the status of the Connector. Known condition types are `ConnectorReady`.