Skip to content

Commit 9f95697

Browse files
committed
feat(compile-genesis): enhance waitForGenesisConfig function for improved configMap retrieval
- Introduced a new function `waitForGenesisConfig` to handle the asynchronous retrieval of Kubernetes ConfigMaps with retry logic. - Added configurable timeout and interval settings for waiting on ConfigMap availability. - Improved error handling to provide clearer feedback on ConfigMap retrieval attempts.
1 parent ac8360f commit 9f95697

File tree

2 files changed

+126
-2
lines changed

2 files changed

+126
-2
lines changed

src/cli/commands/compile-genesis/compile-genesis.command.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,38 @@ import type {
1010
} from "../../../genesis/besu-genesis.service.ts";
1111
import {
1212
createKubernetesClient,
13+
type KubernetesClient,
1314
readConfigMap,
1415
toAllocationConfigMapName,
1516
} from "../../integrations/kubernetes/kubernetes.client.ts";
1617

1718
const GENESIS_DATA_KEY = "genesis.json";
1819
const ALLOCATION_DATA_KEY = "alloc.json";
1920
const DEFAULT_OUTPUT_PATH = "/data/atk-genesis.json";
21+
const DEFAULT_GENESIS_WAIT_TIMEOUT_MS = 120_000;
22+
const DEFAULT_GENESIS_WAIT_INTERVAL_MS = 2000;
23+
const MIN_GENESIS_WAIT_INTERVAL_MS = 200;
24+
const MILLISECONDS_IN_SECOND = 1000;
25+
26+
const parseDuration = (raw: string | undefined, fallback: number): number => {
27+
if (!raw) {
28+
return fallback;
29+
}
30+
const parsed = Number.parseInt(raw, 10);
31+
if (Number.isFinite(parsed) && parsed >= 0) {
32+
return parsed;
33+
}
34+
return fallback;
35+
};
36+
37+
const GENESIS_WAIT_TIMEOUT_MS = parseDuration(
38+
Bun.env.GENESIS_WAIT_TIMEOUT_MS,
39+
DEFAULT_GENESIS_WAIT_TIMEOUT_MS
40+
);
41+
const GENESIS_WAIT_INTERVAL_MS = parseDuration(
42+
Bun.env.GENESIS_WAIT_INTERVAL_MS,
43+
DEFAULT_GENESIS_WAIT_INTERVAL_MS
44+
);
2045

2146
type CompileGenesisOptions = {
2247
genesisConfigMapName: string;
@@ -115,17 +140,72 @@ const parseAllocationEntry = (
115140
return parsed;
116141
};
117142

143+
const waitForGenesisConfig = async (
144+
context: KubernetesClient,
145+
name: string
146+
) => {
147+
const timeout = GENESIS_WAIT_TIMEOUT_MS;
148+
const interval = Math.max(
149+
GENESIS_WAIT_INTERVAL_MS,
150+
MIN_GENESIS_WAIT_INTERVAL_MS
151+
);
152+
const start = Date.now();
153+
let attempts = 0;
154+
155+
while (attempts === 0 || Date.now() - start < timeout) {
156+
attempts += 1;
157+
const config = await readConfigMap(context, name);
158+
if (config) {
159+
if (attempts > 1) {
160+
const elapsedSeconds = (
161+
(Date.now() - start) /
162+
MILLISECONDS_IN_SECOND
163+
).toFixed(1);
164+
process.stdout.write(
165+
`ConfigMap ${name} became available after ${elapsedSeconds}s (attempt ${attempts}).\n`
166+
);
167+
}
168+
return config;
169+
}
170+
171+
if (timeout === 0 || Date.now() - start >= timeout) {
172+
return;
173+
}
174+
175+
if (attempts === 1) {
176+
const seconds = Math.round(timeout / MILLISECONDS_IN_SECOND);
177+
process.stdout.write(
178+
`ConfigMap ${name} not found; waiting up to ${seconds}s for it to become available.\n`
179+
);
180+
} else {
181+
const delaySeconds = Math.round(interval / MILLISECONDS_IN_SECOND);
182+
process.stdout.write(
183+
`ConfigMap ${name} still unavailable; retrying in ${delaySeconds}s (attempt ${attempts}).\n`
184+
);
185+
}
186+
187+
await Bun.sleep(interval);
188+
}
189+
190+
return;
191+
};
192+
118193
const compileGenesis = async ({
119194
genesisConfigMapName,
120195
outputPath,
121196
}: CompileGenesisOptions): Promise<void> => {
122197
const context = await createKubernetesClient();
123198
const { namespace } = context;
124199

125-
const genesisConfig = await readConfigMap(context, genesisConfigMapName);
200+
const genesisConfig = await waitForGenesisConfig(
201+
context,
202+
genesisConfigMapName
203+
);
126204
if (!genesisConfig) {
127205
throw new Error(
128-
`ConfigMap ${genesisConfigMapName} not found in namespace ${namespace}.`
206+
`ConfigMap ${genesisConfigMapName} not found in namespace ${namespace} after waiting up to ${Math.round(
207+
GENESIS_WAIT_TIMEOUT_MS / MILLISECONDS_IN_SECOND
208+
)} seconds.`
129209
);
130210
}
131211

src/cli/integrations/kubernetes/kubernetes.client.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const NAMESPACE_PATH =
1010
const HEX_PREFIX = "0x";
1111
const HTTP_CONFLICT_STATUS = 409;
1212
const HTTP_NOT_FOUND_STATUS = 404;
13+
const STATUS_CODE_PATTERN = /\b(\d{3})\b/u;
14+
const HTTP_CODE_MESSAGE_PATTERN = /HTTP-Code:\s*(\d{3})/u;
1315

1416
type KubernetesClient = {
1517
client: CoreV1Api;
@@ -79,6 +81,48 @@ const getStatusCode = (error: unknown): number | undefined => {
7981
return (fromResponse as { status?: number }).status;
8082
}
8183

84+
const body = (
85+
error as {
86+
body?: {
87+
status?: number;
88+
statusCode?: number;
89+
code?: number;
90+
message?: string;
91+
};
92+
}
93+
).body;
94+
if (body) {
95+
if (typeof body.status === "number") {
96+
return body.status;
97+
}
98+
if (typeof body.statusCode === "number") {
99+
return body.statusCode;
100+
}
101+
if (typeof body.code === "number") {
102+
return body.code;
103+
}
104+
if (typeof body.message === "string") {
105+
const match = body.message.match(STATUS_CODE_PATTERN);
106+
if (match) {
107+
const parsed = Number.parseInt(match[1] ?? "", 10);
108+
if (!Number.isNaN(parsed)) {
109+
return parsed;
110+
}
111+
}
112+
}
113+
}
114+
115+
const message = (error as { message?: string }).message;
116+
if (typeof message === "string") {
117+
const match = message.match(HTTP_CODE_MESSAGE_PATTERN);
118+
if (match) {
119+
const parsed = Number.parseInt(match[1] ?? "", 10);
120+
if (!Number.isNaN(parsed)) {
121+
return parsed;
122+
}
123+
}
124+
}
125+
82126
return;
83127
};
84128

0 commit comments

Comments
 (0)