summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRaj Singh <raj@tailscale.com>2026-03-31 13:00:41 -0500
committerRaj Singh <raj@tailscale.com>2026-03-31 13:00:48 -0500
commit0843986a0dc90b233b353d956109fb3c369570ab (patch)
tree09964c557bd43810cc9b6550237ddc9f320ebc45
parent4334dfa7d5ccbee1daf5acf30b33557bbca66525 (diff)
downloadtailscale-fix/connector-appconnector-empty-routes.tar.xz
tailscale-fix/connector-appconnector-empty-routes.zip
k8s-operator: remove minItems=1 from appConnector routesfix/connector-appconnector-empty-routes
AppConnector.Routes is documented as optional (routes are discovered dynamically when not set), but the shared Routes type enforces MinItems=1 via kubebuilder validation. This makes appConnector: {} fail CRD validation through server-side apply, breaking GitOps deployments (Flux, ArgoCD with SSA). Decouple AppConnector.Routes from the shared Routes type so the MinItems constraint only applies to SubnetRouter.AdvertiseRoutes where it's actually needed. Fixes #19195 Signed-off-by: Raj Singh <raj@tailscale.com>
-rw-r--r--cmd/k8s-operator/connector.go2
-rw-r--r--cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml1
-rw-r--r--cmd/k8s-operator/deploy/manifests/operator.yaml1
-rw-r--r--k8s-operator/api.md4
-rw-r--r--k8s-operator/apis/v1alpha1/types_connector.go2
-rw-r--r--k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go2
6 files changed, 5 insertions, 7 deletions
diff --git a/cmd/k8s-operator/connector.go b/cmd/k8s-operator/connector.go
index 0c2d32482..4c35b442b 100644
--- a/cmd/k8s-operator/connector.go
+++ b/cmd/k8s-operator/connector.go
@@ -218,7 +218,7 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge
if cn.Spec.AppConnector != nil {
sts.Connector.isAppConnector = true
if len(cn.Spec.AppConnector.Routes) != 0 {
- sts.Connector.routes = cn.Spec.AppConnector.Routes.Stringify()
+ sts.Connector.routes = tsapi.Routes(cn.Spec.AppConnector.Routes).Stringify()
}
}
diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
index 03c51c755..91e81ae71 100644
--- a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
+++ b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
@@ -99,7 +99,6 @@ spec:
also dynamically discover other routes.
https://tailscale.com/kb/1332/apps-best-practices#preconfiguration
type: array
- minItems: 1
items:
type: string
format: cidr
diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml
index 597641bde..14df9fbb8 100644
--- a/cmd/k8s-operator/deploy/manifests/operator.yaml
+++ b/cmd/k8s-operator/deploy/manifests/operator.yaml
@@ -125,7 +125,6 @@ spec:
items:
format: cidr
type: string
- minItems: 1
type: array
type: object
exitNode:
diff --git a/k8s-operator/api.md b/k8s-operator/api.md
index 5a60f66e0..762c1b424 100644
--- a/k8s-operator/api.md
+++ b/k8s-operator/api.md
@@ -53,7 +53,7 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
-| `routes` _[Routes](#routes)_ | Routes are optional preconfigured routes for the domains routed via the app connector.<br />If not set, routes for the domains will be discovered dynamically.<br />If set, the app connector will immediately be able to route traffic using the preconfigured routes, but may<br />also dynamically discover other routes.<br />https://tailscale.com/kb/1332/apps-best-practices#preconfiguration | | Format: cidr <br />MinItems: 1 <br />Type: string <br /> |
+| `routes` _[Route](#route) array_ | Routes are optional preconfigured routes for the domains routed via the app connector.<br />If not set, routes for the domains will be discovered dynamically.<br />If set, the app connector will immediately be able to route traffic using the preconfigured routes, but may<br />also dynamically discover other routes.<br />https://tailscale.com/kb/1332/apps-best-practices#preconfiguration | | Format: cidr <br />Type: string <br /> |
@@ -1049,6 +1049,7 @@ _Validation:_
- Type: string
_Appears in:_
+- [AppConnector](#appconnector)
- [Routes](#routes)
@@ -1065,7 +1066,6 @@ _Validation:_
- Type: string
_Appears in:_
-- [AppConnector](#appconnector)
- [SubnetRouter](#subnetrouter)
diff --git a/k8s-operator/apis/v1alpha1/types_connector.go b/k8s-operator/apis/v1alpha1/types_connector.go
index af2df58af..4b18fb7fc 100644
--- a/k8s-operator/apis/v1alpha1/types_connector.go
+++ b/k8s-operator/apis/v1alpha1/types_connector.go
@@ -159,7 +159,7 @@ type AppConnector struct {
// also dynamically discover other routes.
// https://tailscale.com/kb/1332/apps-best-practices#preconfiguration
// +optional
- Routes Routes `json:"routes"`
+ Routes []Route `json:"routes,omitempty"`
}
type Tags []Tag
diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go
index 2528c89f3..a29aacae6 100644
--- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go
@@ -18,7 +18,7 @@ func (in *AppConnector) DeepCopyInto(out *AppConnector) {
*out = *in
if in.Routes != nil {
in, out := &in.Routes, &out.Routes
- *out = make(Routes, len(*in))
+ *out = make([]Route, len(*in))
copy(*out, *in)
}
}