Skip to content

Commit 239294c

Browse files
authored
chore(ci): use safe Kube API error logging (#4103)
Assisted-by: Cursor
1 parent fe3d2c5 commit 239294c

File tree

1 file changed

+84
-17
lines changed

1 file changed

+84
-17
lines changed

e2e-tests/playwright/utils/kube-client.ts

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@ import * as k8s from "@kubernetes/client-node";
22
import { V1ConfigMap } from "@kubernetes/client-node";
33
import * as yaml from "js-yaml";
44

5+
/**
6+
* Interface representing the structure of Kubernetes API errors.
7+
* Used for type-safe error handling without exposing sensitive data.
8+
*/
9+
interface KubeApiError {
10+
body?: { message?: string; reason?: string; code?: number };
11+
statusCode?: number;
12+
message?: string;
13+
response?: { statusCode?: number; statusMessage?: string };
14+
}
15+
16+
/**
17+
* Type guard to check if an unknown error is a KubeApiError.
18+
*/
19+
function isKubeApiError(error: unknown): error is KubeApiError {
20+
return error !== null && typeof error === "object";
21+
}
22+
23+
/**
24+
* Safely extracts error information from Kubernetes API errors without leaking sensitive data.
25+
* The @kubernetes/client-node HttpError contains the full HTTP request/response which includes
26+
* the Authorization header with the bearer token. This function extracts only safe information.
27+
*/
28+
function getKubeApiErrorMessage(error: unknown): string {
29+
if (isKubeApiError(error)) {
30+
const err = error;
31+
32+
// Kubernetes API errors have a body with message, reason, and code
33+
if (err.body?.message) {
34+
const parts = [err.body.message];
35+
if (err.body.reason) parts.push(`reason: ${err.body.reason}`);
36+
if (err.body.code) parts.push(`code: ${err.body.code}`);
37+
return parts.join(", ");
38+
}
39+
40+
// Fallback to statusCode and statusMessage from response
41+
if (err.response?.statusCode) {
42+
return `HTTP ${err.response.statusCode}: ${err.response.statusMessage || "Unknown error"}`;
43+
}
44+
45+
// Fallback to statusCode on error object
46+
if (err.statusCode) {
47+
return `HTTP ${err.statusCode}`;
48+
}
49+
50+
// Fallback to error message (safe as it doesn't contain request details)
51+
if (err.message) {
52+
return err.message;
53+
}
54+
}
55+
56+
return "Unknown Kubernetes API error";
57+
}
58+
559
export class KubeClient {
660
coreV1Api: k8s.CoreV1Api;
761
appsApi: k8s.AppsV1Api;
@@ -37,7 +91,9 @@ export class KubeClient {
3791
this.appsApi = this.kc.makeApiClient(k8s.AppsV1Api);
3892
this.coreV1Api = this.kc.makeApiClient(k8s.CoreV1Api);
3993
} catch (e) {
40-
console.log(e);
94+
console.log(
95+
`Error initializing KubeClient: ${getKubeApiErrorMessage(e)}`,
96+
);
4197
throw e;
4298
}
4399
}
@@ -114,7 +170,9 @@ export class KubeClient {
114170
`No suitable app-config ConfigMap found in namespace ${namespace}`,
115171
);
116172
} catch (error) {
117-
console.error(`Error finding app config ConfigMap: ${error}`);
173+
console.error(
174+
`Error finding app config ConfigMap: ${getKubeApiErrorMessage(error)}`,
175+
);
118176
throw error;
119177
}
120178
}
@@ -217,7 +275,7 @@ export class KubeClient {
217275
options,
218276
);
219277
} catch (e) {
220-
console.log(e.statusCode, e);
278+
console.log(`Error updating configmap: ${getKubeApiErrorMessage(e)}`);
221279
throw e;
222280
}
223281
}
@@ -326,8 +384,12 @@ export class KubeClient {
326384
`ConfigMap '${actualConfigMapName}' updated successfully with new title: '${newTitle}'`,
327385
);
328386
} catch (error) {
329-
console.error("Error updating ConfigMap:", error);
330-
throw new Error(`Failed to update ConfigMap: ${error.message}`);
387+
console.error(
388+
`Error updating ConfigMap: ${getKubeApiErrorMessage(error)}`,
389+
);
390+
throw new Error(
391+
`Failed to update ConfigMap: ${getKubeApiErrorMessage(error)}`,
392+
);
331393
}
332394
}
333395

@@ -397,7 +459,9 @@ export class KubeClient {
397459
);
398460
});
399461
} catch (err) {
400-
console.log("Error deleting or waiting for namespace deletion:", err);
462+
console.log(
463+
`Error deleting or waiting for namespace deletion: ${getKubeApiErrorMessage(err)}`,
464+
);
401465
throw err;
402466
}
403467
}
@@ -410,7 +474,9 @@ export class KubeClient {
410474
try {
411475
await this.deleteNamespaceAndWait(namespace);
412476
} catch (err) {
413-
console.log(err);
477+
console.log(
478+
`Error deleting namespace ${namespace}: ${getKubeApiErrorMessage(err)}`,
479+
);
414480
throw err;
415481
}
416482
}
@@ -541,7 +607,9 @@ export class KubeClient {
541607

542608
return null; // No failure states detected
543609
} catch (error) {
544-
console.error(`Error checking pod failure states: ${error}`);
610+
console.error(
611+
`Error checking pod failure states: ${getKubeApiErrorMessage(error)}`,
612+
);
545613
return null; // Don't fail the check if we can't retrieve pod info
546614
}
547615
}
@@ -605,7 +673,9 @@ export class KubeClient {
605673
`Waiting for ${deploymentName} to reach ${expectedReplicas} replicas, currently has ${availableReplicas} available, ${readyReplicas} ready.`,
606674
);
607675
} catch (error) {
608-
console.error(`Error checking deployment status: ${error}`);
676+
console.error(
677+
`Error checking deployment status: ${getKubeApiErrorMessage(error)}`,
678+
);
609679
// If we threw an error about pod failure, re-throw it
610680
if (error.message?.includes("failed to start")) {
611681
throw error;
@@ -650,13 +720,12 @@ export class KubeClient {
650720
);
651721
} catch (error) {
652722
console.error(
653-
`Error during deployment restart: Deployment '${deploymentName}' in namespace '${namespace}'.`,
654-
error,
723+
`Error during deployment restart: Deployment '${deploymentName}' in namespace '${namespace}': ${getKubeApiErrorMessage(error)}`,
655724
);
656725
await this.logPodConditions(namespace);
657726
await this.logDeploymentEvents(deploymentName, namespace);
658727
throw new Error(
659-
`Failed to restart deployment '${deploymentName}' in namespace '${namespace}': ${error.message}`,
728+
`Failed to restart deployment '${deploymentName}' in namespace '${namespace}': ${getKubeApiErrorMessage(error)}`,
660729
);
661730
}
662731
}
@@ -689,8 +758,7 @@ export class KubeClient {
689758
}
690759
} catch (error) {
691760
console.error(
692-
`Error while retrieving pod conditions for selector '${selector}':`,
693-
error,
761+
`Error while retrieving pod conditions for selector '${selector}': ${getKubeApiErrorMessage(error)}`,
694762
);
695763
}
696764
}
@@ -718,7 +786,7 @@ export class KubeClient {
718786
);
719787
} catch (error) {
720788
console.error(
721-
`Error retrieving events for deployment ${deploymentName}: ${error}`,
789+
`Error retrieving events for deployment ${deploymentName}: ${getKubeApiErrorMessage(error)}`,
722790
);
723791
}
724792
}
@@ -739,8 +807,7 @@ export class KubeClient {
739807
return response.body.items;
740808
} catch (error) {
741809
console.error(
742-
`Error fetching services with label ${labelSelector}:`,
743-
error,
810+
`Error fetching services with label ${labelSelector}: ${getKubeApiErrorMessage(error)}`,
744811
);
745812
throw error;
746813
}

0 commit comments

Comments
 (0)