@@ -60,6 +60,58 @@ export class KubeClient {
6060 }
6161 }
6262
63+ async listConfigMaps ( namespace : string ) {
64+ try {
65+ LOGGER . info ( `Listing configmaps in namespace ${ namespace } ` ) ;
66+ return await this . coreV1Api . listNamespacedConfigMap ( namespace ) ;
67+ } catch ( e ) {
68+ LOGGER . error ( e . body ?. message ) ;
69+ throw e ;
70+ }
71+ }
72+
73+ // Define possible ConfigMap base names as a constant
74+ private readonly appConfigNames = [
75+ 'app-config-rhdh' ,
76+ 'app-config' ,
77+ 'backstage-app-config' ,
78+ 'rhdh-app-config'
79+ ] ;
80+
81+ async findAppConfigMap ( namespace : string ) : Promise < string > {
82+ try {
83+ const configMapsResponse = await this . listConfigMaps ( namespace ) ;
84+ const configMaps = configMapsResponse . body . items ;
85+
86+ LOGGER . info ( `Found ${ configMaps . length } ConfigMaps in namespace ${ namespace } ` ) ;
87+ configMaps . forEach ( cm => {
88+ LOGGER . info ( `ConfigMap: ${ cm . metadata ?. name } ` ) ;
89+ } ) ;
90+
91+ for ( const name of this . appConfigNames ) {
92+ const found = configMaps . find ( cm => cm . metadata ?. name === name ) ;
93+ if ( found ) {
94+ LOGGER . info ( `Found app config ConfigMap: ${ name } ` ) ;
95+ return name ;
96+ }
97+ }
98+
99+ // If none of the expected names found, look for ConfigMaps containing app-config data
100+ for ( const cm of configMaps ) {
101+ if ( cm . data && Object . keys ( cm . data ) . some ( key =>
102+ key . includes ( 'app-config' ) && key . endsWith ( '.yaml' ) ) ) {
103+ LOGGER . info ( `Found ConfigMap with app-config data: ${ cm . metadata ?. name } ` ) ;
104+ return cm . metadata ?. name || '' ;
105+ }
106+ }
107+
108+ throw new Error ( `No suitable app-config ConfigMap found in namespace ${ namespace } ` ) ;
109+ } catch ( error ) {
110+ LOGGER . error ( `Error finding app config ConfigMap: ${ error } ` ) ;
111+ throw error ;
112+ }
113+ }
114+
63115 async getNamespaceByName ( name : string ) : Promise < k8s . V1Namespace | null > {
64116 try {
65117 LOGGER . debug ( `Getting namespace ${ name } .` ) ;
@@ -144,30 +196,94 @@ export class KubeClient {
144196 newTitle : string ,
145197 ) {
146198 try {
199+ // If the provided configMapName doesn't exist, try to find the correct one dynamically
200+ let actualConfigMapName = configMapName ;
201+ try {
202+ await this . getConfigMap ( configMapName , namespace ) ;
203+ LOGGER . info ( `Using provided ConfigMap name: ${ configMapName } ` ) ;
204+ } catch ( error ) {
205+ if ( error . response ?. statusCode === 404 ) {
206+ LOGGER . info ( `ConfigMap ${ configMapName } not found, searching for alternatives...` ) ;
207+ actualConfigMapName = await this . findAppConfigMap ( namespace ) ;
208+ } else {
209+ throw error ;
210+ }
211+ }
212+
147213 const configMapResponse = await this . getConfigMap (
148- configMapName ,
214+ actualConfigMapName ,
149215 namespace ,
150216 ) ;
151217 const configMap = configMapResponse . body ;
152218
153- const appConfigYaml = configMap . data [ `${ configMapName } .yaml` ] ;
219+ LOGGER . info ( `Using ConfigMap: ${ actualConfigMapName } ` ) ;
220+ LOGGER . info ( `Available data keys: ${ Object . keys ( configMap . data || { } ) . join ( ', ' ) } ` ) ;
221+
222+ // Find the correct data key dynamically
223+ let dataKey : string | undefined ;
224+ const dataKeys = Object . keys ( configMap . data || { } ) ;
225+
226+ // Generate key patterns from the possible names + the actual ConfigMap name
227+ const keyPatterns = [
228+ `${ actualConfigMapName } .yaml` ,
229+ ...this . appConfigNames . map ( name => `${ name } .yaml` )
230+ ] ;
231+
232+ for ( const pattern of keyPatterns ) {
233+ if ( dataKeys . includes ( pattern ) ) {
234+ dataKey = pattern ;
235+ break ;
236+ }
237+ }
238+
239+ // If none of the patterns match, look for any .yaml file containing app-config
240+ if ( ! dataKey ) {
241+ dataKey = dataKeys . find ( key =>
242+ key . endsWith ( '.yaml' ) && key . includes ( 'app-config' )
243+ ) ;
244+ }
245+
246+ // Last resort: use any .yaml file
247+ if ( ! dataKey ) {
248+ dataKey = dataKeys . find ( key => key . endsWith ( '.yaml' ) ) ;
249+ }
250+
251+ if ( ! dataKey ) {
252+ throw new Error ( `No suitable YAML data key found in ConfigMap '${ actualConfigMapName } '. Available keys: ${ dataKeys . join ( ', ' ) } ` ) ;
253+ }
254+
255+ LOGGER . info ( `Using data key: ${ dataKey } ` ) ;
256+ const appConfigYaml = configMap . data [ dataKey ] ;
257+
258+ if ( ! appConfigYaml ) {
259+ throw new Error ( `Data key '${ dataKey } ' is empty in ConfigMap '${ actualConfigMapName } '` ) ;
260+ }
261+
154262 // eslint-disable-next-line @typescript-eslint/no-explicit-any
155263 const appConfigObj = yaml . load ( appConfigYaml ) as any ;
156264
265+ if ( ! appConfigObj || ! appConfigObj . app ) {
266+ throw new Error ( `Invalid app-config structure in ConfigMap '${ actualConfigMapName } '. Expected 'app' section not found.` ) ;
267+ }
268+
269+ LOGGER . info ( `Current title: ${ appConfigObj . app . title } ` ) ;
157270 appConfigObj . app . title = newTitle ;
158- configMap . data [ `${ configMapName } .yaml` ] = yaml . dump ( appConfigObj ) ;
271+ LOGGER . info ( `New title: ${ newTitle } ` ) ;
272+
273+ configMap . data [ dataKey ] = yaml . dump ( appConfigObj ) ;
159274
160275 delete configMap . metadata . creationTimestamp ;
276+ delete configMap . metadata . resourceVersion ;
161277
162278 await this . coreV1Api . replaceNamespacedConfigMap (
163- configMapName ,
279+ actualConfigMapName ,
164280 namespace ,
165281 configMap ,
166282 ) ;
167- console . log ( " ConfigMap updated successfully." ) ;
283+ console . log ( ` ConfigMap ' ${ actualConfigMapName } ' updated successfully with new title: ' ${ newTitle } '` ) ;
168284 } catch ( error ) {
169285 console . error ( "Error updating ConfigMap:" , error ) ;
170- throw new Error ( " Failed to update ConfigMap" ) ;
286+ throw new Error ( ` Failed to update ConfigMap: ${ error . message } ` ) ;
171287 }
172288 }
173289
@@ -287,19 +403,18 @@ export class KubeClient {
287403 timeout : number = 300000 , // 5 minutes
288404 checkInterval : number = 10000 , // 10 seconds
289405 ) {
290- const start = Date . now ( ) ;
406+ const endTime = Date . now ( ) + timeout ;
291407 const labelSelector =
292408 "app.kubernetes.io/component=backstage,app.kubernetes.io/instance=rhdh,app.kubernetes.io/name=backstage" ;
293409
294- while ( Date . now ( ) - start < timeout ) {
410+ while ( Date . now ( ) < endTime ) {
295411 try {
296- // Check deployment status
297412 const response = await this . appsApi . readNamespacedDeployment (
298413 deploymentName ,
299414 namespace ,
300415 ) ;
301-
302416 const availableReplicas = response . body . status ?. availableReplicas || 0 ;
417+ const readyReplicas = response . body . status ?. readyReplicas || 0 ;
303418 const conditions = response . body . status ?. conditions || [ ] ;
304419
305420 console . log ( `Available replicas: ${ availableReplicas } ` ) ;
@@ -320,7 +435,7 @@ export class KubeClient {
320435 }
321436
322437 console . log (
323- `Waiting for ${ deploymentName } to reach ${ expectedReplicas } replicas, currently has ${ availableReplicas } .` ,
438+ `Waiting for ${ deploymentName } to reach ${ expectedReplicas } replicas, currently has ${ availableReplicas } available, ${ readyReplicas } ready .` ,
324439 ) ;
325440 } catch ( error ) {
326441 console . error ( `Error checking deployment status: ${ error } ` ) ;
@@ -330,35 +445,45 @@ export class KubeClient {
330445 }
331446
332447 throw new Error (
333- `Deployment ${ deploymentName } did not become ready in time.` ,
448+ `Deployment ${ deploymentName } did not become ready in time (timeout: ${ timeout / 1000 } s) .` ,
334449 ) ;
335450 }
336451
337452 async restartDeployment ( deploymentName : string , namespace : string ) {
338453 try {
454+ console . log (
455+ `Starting deployment restart for ${ deploymentName } in namespace ${ namespace } ` ,
456+ ) ;
457+
458+ // Scale down deployment to 0 replicas
339459 console . log ( `Scaling down deployment ${ deploymentName } to 0 replicas.` ) ;
340460 console . log ( `Deployment: ${ deploymentName } , Namespace: ${ namespace } ` ) ;
341461 await this . logPodConditions ( namespace ) ;
342462 await this . scaleDeployment ( deploymentName , namespace , 0 ) ;
463+ await this . waitForDeploymentReady ( deploymentName , namespace , 0 , 300000 ) ; // 5 minutes for scale down
343464
344- await this . waitForDeploymentReady ( deploymentName , namespace , 0 ) ;
465+ // Wait a bit for pods to be fully terminated
466+ console . log ( "Waiting for pods to be fully terminated..." ) ;
467+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10000 ) ) ; // 10 seconds
345468
469+ // Scale up deployment to 1 replica
346470 console . log ( `Scaling up deployment ${ deploymentName } to 1 replica.` ) ;
347471 await this . scaleDeployment ( deploymentName , namespace , 1 ) ;
348472
349- await this . waitForDeploymentReady ( deploymentName , namespace , 1 ) ;
473+ await this . waitForDeploymentReady ( deploymentName , namespace , 1 , 600000 ) ; // 10 minutes for scale up
350474
351475 console . log (
352476 `Restart of deployment ${ deploymentName } completed successfully.` ,
353477 ) ;
354478 } catch ( error ) {
355479 console . error (
356480 `Error during deployment restart: Deployment '${ deploymentName } ' in namespace '${ namespace } '.` ,
481+ error ,
357482 ) ;
358483 await this . logPodConditions ( namespace ) ;
359484 await this . logDeploymentEvents ( deploymentName , namespace ) ;
360485 throw new Error (
361- `Failed to restart deployment '${ deploymentName } ' in namespace '${ namespace } '. ` ,
486+ `Failed to restart deployment '${ deploymentName } ' in namespace '${ namespace } ': ${ error . message } ` ,
362487 ) ;
363488 }
364489 }
0 commit comments