@@ -2,6 +2,60 @@ import * as k8s from "@kubernetes/client-node";
22import { V1ConfigMap } from "@kubernetes/client-node" ;
33import * 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+
559export 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