@@ -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,142 @@ 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 any 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 any 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 availableReplicas = element . status . availableReplicas ? element . status . availableReplicas :
359+ element . spec . replicas ? element . spec . replicas : 0 ;
360+ const replicas = element . status . replicas ? element . status . replicas : 0 ;
361+ let pods : DeploymentPodObject [ ] = [ ] ;
362+ if ( shouldHaveReplicas ) {
363+ try {
364+ pods = await this . getPods ( element ) ;
365+ } catch {
366+ // ignore
367+ }
368+ }
369+
370+ // Look into Pod containers' states for any 'CrashLoopBackOff` status
371+ const podErrors = this . detectCrashLoopBackOff ( pods ) ;
372+ let podsMessages = '' ;
373+ podErrors . messages . forEach ( ( m ) => podsMessages = podsMessages . concat ( `\n\t${ m } ` ) ) ;
374+
375+ // We get Deployment's 'CrashLoopBackOff` status and error messages
376+ const deploymentErrors = this . collectDeploymentErrors ( element ) ;
377+
378+ let errorMessages = '' ;
379+ deploymentErrors . forEach ( ( m ) => errorMessages = errorMessages . concat ( `\n\t${ m } ` ) ) ;
380+
381+ // const inCrashLoopBackOff = element.status.conditions.find((condition) => condition.status === 'False' && condition.reason === 'CrashLoopBackOff');
382+ let description = `${ this . makeCaps ( element . kind ) } ` ;
383+ let tooltip = description ;
384+ if ( element . kind === 'Deployment' ) {
385+ description = `${ description } (${ replicas } /${ availableReplicas } )`
386+ tooltip = `${ tooltip } : ${ element . metadata . name } \n` . concat (
387+ `Available replicas: ${ availableReplicas } \n` ,
388+ `Replicas: ${ replicas } \n` ,
389+ `CrashLoopBackOff detected: ${ podErrors . inCrashLoopBackOff ? 'Yes' : 'No' } \n` ,
390+ podsMessages . length > 0 ? `\nPod Container Failures:${ podsMessages } \n` : '' ,
391+ errorMessages . length > 0 ? `\nDeployment Failures:${ errorMessages } \n` : ''
392+ ) ;
393+ }
394+ const iconSuffix = ! shouldHaveReplicas ? '' :
395+ podErrors . inCrashLoopBackOff ? '-red' : this . getDeploymentIconSuffix ( pods ) ;
396+ const iconPath = element . kind === 'Deployment' || element . kind === 'DeploymentConfig' ?
397+ path . resolve ( __dirname , `../../images/context/component-node${ iconSuffix } .png` )
398+ : undefined ;
399+
400+ const routeURL = await Oc . Instance . getRouteURL ( element . metadata . name ) ;
401+ return {
275402 contextValue : `openshift.k8sObject.${ element . kind } ${ routeURL ? '.route' : '' } ` ,
276403 label : element . metadata . name ,
277- description : `${ element . kind . substring ( 0 , 1 ) . toLocaleUpperCase ( ) } ${ element . kind . substring ( 1 ) } ` ,
404+ description,
405+ tooltip,
278406 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 ,
407+ iconPath,
280408 command : {
281409 title : 'Load' ,
282410 command : 'openshift.resource.load' ,
283411 arguments : [ element ]
284412 }
285413 } ;
286-
287- }
288- return {
289- label : 'Unknown element'
290- }
291414 }
292415
293416 private makeCaps ( kind : string ) : string {
0 commit comments