k8s-operator: remove minItems=1 from appConnector 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>
This commit is contained in:
Raj Singh 2026-03-31 13:00:41 -05:00
parent 4334dfa7d5
commit 0843986a0d
6 changed files with 5 additions and 7 deletions

View File

@ -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()
}
}

View File

@ -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

View File

@ -125,7 +125,6 @@ spec:
items:
format: cidr
type: string
minItems: 1
type: array
type: object
exitNode:

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}