@@ -45,30 +45,6 @@ def startup_fn(logger, memo, settings, **kwargs):
4545# ---------------------------------------------------------------------------
4646
4747
48- def _ensure_resource (read_fn , create_fn , replace_fn , * args , ** kwargs ):
49- """Generic create-or-update pattern."""
50- resource = kwargs .pop ("resource" )
51- try :
52- read_fn (* args )
53- replace_fn (* args , resource )
54- except ApiException as exc :
55- if exc .status == 404 :
56- # For create, the name is part of the resource body, not a positional arg
57- # create_fn signature varies: (namespace, body) or just (body)
58- create_fn (resource )
59- else :
60- raise
61-
62-
63- def _delete_if_exists (fn , * args , logger = None ):
64- try :
65- fn (* args )
66- except ApiException as exc :
67- if exc .status != 404 :
68- if logger :
69- logger .warning (f"Failed to delete { args } : { exc } " )
70-
71-
7248TAILNET_API = {
7349 "group" : "tailscale.com" ,
7450 "version" : "v1alpha1" ,
@@ -92,11 +68,27 @@ def _ensure_tailnet(custom_api, crd_name, secret_name, labels, logger):
9268 },
9369 }
9470 try :
95- custom_api .get_cluster_custom_object (
71+ existing = custom_api .get_cluster_custom_object (
9672 TAILNET_API ["group" ], TAILNET_API ["version" ],
9773 TAILNET_API ["plural" ], crd_name ,
9874 )
99- logger .info (f"Tailnet { crd_name } already exists" )
75+ existing_secret = existing .get ("spec" , {}).get ("credentials" , {}).get ("secretName" , "" )
76+ if existing_secret != secret_name :
77+ logger .info (
78+ f"Tailnet { crd_name } has secretName={ existing_secret !r} , "
79+ f"expected { secret_name !r} — recreating"
80+ )
81+ custom_api .delete_cluster_custom_object (
82+ TAILNET_API ["group" ], TAILNET_API ["version" ],
83+ TAILNET_API ["plural" ], crd_name ,
84+ )
85+ custom_api .create_cluster_custom_object (
86+ TAILNET_API ["group" ], TAILNET_API ["version" ],
87+ TAILNET_API ["plural" ], body ,
88+ )
89+ logger .info (f"Recreated Tailnet { crd_name } " )
90+ else :
91+ logger .info (f"Tailnet { crd_name } already exists" )
10092 except ApiException as exc :
10193 if exc .status == 404 :
10294 custom_api .create_cluster_custom_object (
@@ -119,6 +111,7 @@ def _delete_tailnet(custom_api, crd_name, logger):
119111 except ApiException as exc :
120112 if exc .status != 404 :
121113 logger .warning (f"Failed to delete Tailnet { crd_name } : { exc } " )
114+ raise
122115
123116
124117def _ensure_proxy_group (custom_api , crd_name , labels , default_tags , logger ):
@@ -200,6 +193,7 @@ def _delete_proxy_group(custom_api, crd_name, logger):
200193 except ApiException as exc :
201194 if exc .status != 404 :
202195 logger .warning (f"Failed to delete ProxyGroup { pg_name } : { exc } " )
196+ raise
203197
204198
205199# ---------------------------------------------------------------------------
@@ -212,30 +206,26 @@ def _delete_proxy_group(custom_api, crd_name, logger):
212206def reconcile_operator (spec , name , namespace , memo , logger , retry , ** kwargs ):
213207 if not spec and retry < 5 :
214208 raise kopf .TemporaryError ("spec is not yet populated" , delay = 1 )
209+ if not spec :
210+ raise kopf .PermanentError ("spec is empty after 5 retries, giving up" )
215211
216- client_id = spec ["clientId" ]
217- operator_image = spec ["operatorImage" ]
212+ default_tags = spec .get ("defaultTags" , "" )
218213 default_tags = spec .get ("defaultTags" , "" )
219214 org_slug = spec ["organizationSlug" ]
220215
221216 labels = _labels (org_slug )
222217 tailnet_secret_name = f"tailscale-tailnet-{ name } "
223- # The single operator runs in the cabotage namespace
224- operator_namespace = "tailscale"
225218
226219 custom_api = kubernetes .client .CustomObjectsApi ()
227220
228221 # 1. Tailnet CRD (cluster-scoped) — references the credential Secret
229222 # (cabotage-app creates the Secret before enqueuing the CRD)
230223 _ensure_tailnet (custom_api , name , tailnet_secret_name , labels , logger )
231224
232- # 3 . ProxyGroup (cluster-scoped) — references the Tailnet
225+ # 2 . ProxyGroup (cluster-scoped) — references the Tailnet
233226 _ensure_proxy_group (custom_api , name , labels , default_tags , logger )
234227
235- # Extract version from operator image tag
236- version = operator_image .rsplit (":" , 1 )[- 1 ] if ":" in operator_image else "unknown"
237-
238- return {"state" : "deployed" , "operatorVersion" : version }
228+ return {"state" : "deployed" }
239229
240230
241231@kopf .on .delete ("cabotagetailscaleoperatorconfigs" )
0 commit comments