Skip to content

Commit e87ab46

Browse files
committed
fix tollkeeper refresh flow from cursor review
- Pod refresh: Deployment pods get new random suffixes on delete, so the name-based poll in refreshK8sResources times out. Restart via `kubectl rollout restart` + `rollout status` instead. - includesChain: `-o jsonpath='{.data}'` renders a Go map (`map[K:V ...]`) and throws in JSON.parse. Query the specific RPC_URL_<CHAIN> key directly so jsonpath returns just its base64 value.
1 parent 6a4cbbc commit e87ab46

2 files changed

Lines changed: 37 additions & 14 deletions

File tree

typescript/infra/src/tollkeeper/helm.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@ export class TollkeeperHelmManager extends HelmManager {
2929
);
3030
}
3131

32-
// Tollkeeper is a Deployment (chart labels pods with `app=<release>`), so the
33-
// base StatefulSet-only pod discovery returns nothing. Filter on ReplicaSet
34-
// ownership to get Deployment-managed pods.
32+
// Pod restarts go through `kubectl rollout restart` (see restartDeployment)
33+
// rather than the shared pod-delete flow: Deployment pods get new names on
34+
// delete, which breaks the name-based wait in refreshK8sResources.
3535
async getManagedK8sPods(): Promise<string[]> {
36-
const [output] = await execCmd(
37-
`kubectl get pods --selector=app=${this.helmReleaseName} -o jsonpath='{range .items[?(@.metadata.ownerReferences[0].kind=="ReplicaSet")]}{.metadata.name}{"\\n"}{end}' -n ${this.namespace}`,
38-
);
39-
return output.split('\n').filter(Boolean);
36+
return [];
4037
}
4138

4239
// The tollkeeper chart doesn't use the standard `app.kubernetes.io/instance`
@@ -52,13 +49,29 @@ export class TollkeeperHelmManager extends HelmManager {
5249
const secrets = await this.getExistingK8sSecrets();
5350
if (secrets.length === 0) return false;
5451

52+
// Query the specific RPC_URL_<CHAIN> key. `-o jsonpath='{.data}'` would
53+
// render the map in Go format (`map[K:V ...]`), not JSON — parsing fails.
54+
// Targeting the key directly returns its base64 value if present, else
55+
// empty string.
56+
const needle = `${RPC_ENV_PREFIX}${chain.toUpperCase().replaceAll('-', '_')}`;
5557
const [output] = await execCmd(
56-
`kubectl get secret ${secrets[0]} -n ${this.namespace} --ignore-not-found -o jsonpath='{.data}'`,
58+
`kubectl get secret ${secrets[0]} -n ${this.namespace} --ignore-not-found -o jsonpath='{.data.${needle}}'`,
5759
);
60+
return output.trim().length > 0;
61+
}
5862

59-
const needle = `${RPC_ENV_PREFIX}${chain.toUpperCase().replaceAll('-', '_')}`;
60-
const keys = Object.keys(JSON.parse(output || '{}'));
61-
return keys.includes(needle);
63+
// Deployment-aware restart: rolls pods without name-based polling.
64+
async restartDeployment(): Promise<void> {
65+
console.log(
66+
`🔄 Restarting deployment ${this.helmReleaseName} in ${this.namespace}...`,
67+
);
68+
await execCmd(
69+
`kubectl rollout restart deployment/${this.helmReleaseName} -n ${this.namespace}`,
70+
);
71+
await execCmd(
72+
`kubectl rollout status deployment/${this.helmReleaseName} -n ${this.namespace} --timeout=180s`,
73+
);
74+
console.log(`✅ ${this.helmReleaseName} rollout complete`);
6275
}
6376

6477
static async getManagersForChain(

typescript/infra/src/utils/rpcUrls.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,22 @@ async function refreshDependentK8sResourcesInteractive(
297297
const tollkeeperManagers = await selectTollkeeper(environment, chain);
298298
const cronjobManagers = await selectCronJobs(environment);
299299

300-
// Services get both secret and pod refresh
300+
// StatefulSet-backed services: both secret and pod refresh through the
301+
// shared (name-based) flow.
301302
const serviceManagers = [
302303
...coreManagers,
303304
...warpManagers,
304305
...rebalancerManagers,
306+
];
307+
// Tollkeeper gets secret refresh through the shared flow, but pod refresh
308+
// via `kubectl rollout restart` — its Deployment pods get new names on
309+
// delete, which would break name-based polling in refreshK8sResources.
310+
// CronJobs only get secret refresh (pods pick up new secrets on next run).
311+
const allManagersForSecrets = [
312+
...serviceManagers,
305313
...tollkeeperManagers,
314+
...cronjobManagers,
306315
];
307-
// CronJobs only get secret refresh (they pick up new secrets on next scheduled run)
308-
const allManagersForSecrets = [...serviceManagers, ...cronjobManagers];
309316

310317
if (allManagersForSecrets.length > 0) {
311318
await refreshK8sResources(
@@ -321,6 +328,9 @@ async function refreshDependentK8sResourcesInteractive(
321328
environment,
322329
);
323330
}
331+
for (const manager of tollkeeperManagers) {
332+
await manager.restartDeployment();
333+
}
324334
}
325335

326336
async function selectCoreInfrastructure(

0 commit comments

Comments
 (0)