@@ -57,6 +57,11 @@ export interface DeploymentPodObject extends KubernetesObject {
5757 } ,
5858}
5959
60+ export interface DeploymentPodErrors {
61+ inCrashLoopBackOff : boolean ,
62+ messages : string [ ]
63+ }
64+
6065type PackageJSON = {
6166 version : string ;
6267 bugs : string ;
@@ -270,24 +275,141 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
270275 collapsibleState : TreeItemCollapsibleState . Collapsed
271276 }
272277 }
273- const routeURL = await Oc . Instance . getRouteURL ( element . metadata . name ) ;
274- return {
278+ return this . getDeploymentItem ( element ) ;
279+ }
280+ return {
281+ label : 'Unknown element'
282+ }
283+ }
284+
285+ private getDeploymentIconSuffix ( pods : DeploymentPodObject [ ] ) : string {
286+ // Find all not 'Running' pods
287+ const notRunning = pods . filter ( ( pod ) => pod . status && pod . status . phase !== 'Running' ) ;
288+ if ( notRunning . length === 0 ) {
289+ return '-green' ; // All running - return 'green'
290+ }
291+ // Find any 'Failed' or 'Unknown' pod - if any return error ('red')
292+ const failed = notRunning . find ( ( pod ) => pod . status &&
293+ ( pod . status . phase === 'Failed' || pod . status . phase === 'Unknown' ) ) ;
294+ if ( failed ) {
295+ return '-red' ; // At least one failed or unknown - return 'red'
296+ }
297+ // Find any 'Pending' pod - if any return pending ('yellow')
298+ const pending = notRunning . find ( ( pod ) => pod . status && pod . status . phase === 'Pending' ) ;
299+ return pending ? '-yellow' : '' ;
300+ }
301+
302+ /*
303+ * Search for 'CrashLoopBackOff` in Pod's opbect:
304+ * status:
305+ * containerStatuses:
306+ * state:
307+ * waiting:
308+ * message: |
309+ * container create failed: time="2024-05-28T10:30:28Z" level=error msg="runc create failed: unable to start container process: exec: \"asdf\": executable file not found in $PATH"
310+ * reason: CreateContainerError
311+ */
312+ private detectCrashLoopBackOff ( pods ) : DeploymentPodErrors {
313+ let inCrashLoopBackOff = false ;
314+ const messages : string [ ] = [ ] ;
315+
316+ // Search for Pod continers' errors
317+ pods . forEach ( ( pod ) => {
318+ pod . status ?. containerStatuses &&
319+ pod . status . containerStatuses . forEach ( ( cs ) => {
320+ if ( cs . state ?. waiting ) {
321+ const reason = cs . state . waiting . reason ;
322+ const message = cs . state . waiting . message ;
323+
324+ inCrashLoopBackOff = inCrashLoopBackOff || reason === 'CrashLoopBackOff' ;
325+
326+ const msg = `${ reason } : ${ message ? message . trim ( ) : 'No valuable message' } ` ;
327+ // Skip duplicates and show not more than 10 errors
328+ if ( messages . length <= 10 && ! ( messages . find ( ( m ) => m === msg ) ) ) {
329+ messages . push ( msg ) ;
330+ }
331+ }
332+ } ) ;
333+ } ) ;
334+
335+ return {
336+ inCrashLoopBackOff,
337+ messages
338+ }
339+ }
340+
341+ private collectDeploymentErrors ( deployment ) : string [ ] {
342+ const messages : string [ ] = [ ] ;
343+ deployment . status . conditions . filter ( ( c ) => c . status === 'False' )
344+ . forEach ( ( c ) => {
345+ const message = `${ c . reason } : ${ c . message ? c . message . trim ( ) : 'No valuable message' } ` ;
346+
347+ // Skip duplicates and show not more than 10 errors
348+ if ( messages . length <= 10 && ! ( messages . find ( ( m ) => m === message ) ) ) {
349+ messages . push ( message ) ;
350+ }
351+ } ) ;
352+
353+ return messages ;
354+ }
355+
356+ private async getDeploymentItem ( element ) : Promise < TreeItem > {
357+ const shouldHaveReplicas = element . spec . replicas > 0 ;
358+ const desiredReplicas = element . spec . replicas ? element . spec . replicas : 0 ;
359+ const actualReplicas = element . status . replicas ? element . status . replicas : 0 ;
360+ let pods : DeploymentPodObject [ ] = [ ] ;
361+ if ( shouldHaveReplicas ) {
362+ try {
363+ pods = await this . getPods ( element ) ;
364+ } catch {
365+ // ignore
366+ }
367+ }
368+
369+ // Look into Pod containers' states for any 'CrashLoopBackOff` status
370+ const podErrors = this . detectCrashLoopBackOff ( pods ) ;
371+ let podsMessages = '' ;
372+ podErrors . messages . forEach ( ( m ) => podsMessages = podsMessages . concat ( `\n\t${ m } ` ) ) ;
373+
374+ // We get Deployment's 'CrashLoopBackOff` status and error messages
375+ const deploymentErrors = this . collectDeploymentErrors ( element ) ;
376+
377+ let errorMessages = '' ;
378+ deploymentErrors . forEach ( ( m ) => errorMessages = errorMessages . concat ( `\n\t${ m } ` ) ) ;
379+
380+ // const inCrashLoopBackOff = element.status.conditions.find((condition) => condition.status === 'False' && condition.reason === 'CrashLoopBackOff');
381+ let description = `${ this . makeCaps ( element . kind ) } ` ;
382+ let tooltip = description ;
383+ if ( element . kind === 'Deployment' ) {
384+ description = `${ description } (${ actualReplicas } /${ desiredReplicas } )`
385+ tooltip = `${ tooltip } : ${ element . metadata . name } \n` . concat (
386+ `Desired Replicas: ${ desiredReplicas } \n` ,
387+ `Actual Replicas: ${ actualReplicas } \n` ,
388+ `CrashLoopBackOff detected: ${ podErrors . inCrashLoopBackOff ? 'Yes' : 'No' } \n` ,
389+ podsMessages . length > 0 ? `\nPod Container Failures:${ podsMessages } \n` : '' ,
390+ errorMessages . length > 0 ? `\nDeployment Failures:${ errorMessages } \n` : ''
391+ ) ;
392+ }
393+ const iconSuffix = ! shouldHaveReplicas ? '' :
394+ podErrors . inCrashLoopBackOff ? '-red' : this . getDeploymentIconSuffix ( pods ) ;
395+ const iconPath = element . kind === 'Deployment' || element . kind === 'DeploymentConfig' ?
396+ path . resolve ( __dirname , `../../images/context/component-node${ iconSuffix } .png` )
397+ : undefined ;
398+
399+ const routeURL = await Oc . Instance . getRouteURL ( element . metadata . name ) ;
400+ return {
275401 contextValue : `openshift.k8sObject.${ element . kind } ${ routeURL ? '.route' : '' } ` ,
276402 label : element . metadata . name ,
277- description : `${ element . kind . substring ( 0 , 1 ) . toLocaleUpperCase ( ) } ${ element . kind . substring ( 1 ) } ` ,
403+ description,
404+ tooltip,
278405 collapsibleState : element . kind === 'Deployment' ? TreeItemCollapsibleState . Collapsed : TreeItemCollapsibleState . None ,
279- iconPath : element . kind === 'Deployment' || element . kind === 'DeploymentConfig' ? path . resolve ( __dirname , '../../images/context/component-node.png' ) : undefined ,
406+ iconPath,
280407 command : {
281408 title : 'Load' ,
282409 command : 'openshift.resource.load' ,
283410 arguments : [ element ]
284411 }
285412 } ;
286-
287- }
288- return {
289- label : 'Unknown element'
290- }
291413 }
292414
293415 private makeCaps ( kind : string ) : string {
0 commit comments