@@ -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,140 @@ 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 === undefined ) {
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+ const messages : string [ ] = [ ] ;
314+
315+ // Search for 'CrashLoopBackOff' Pod containers' state
316+ const podsInCLBO = pods . filter ( ( pod ) => pod . status ?. containerStatuses ?. state ?. waiting ?. reason === 'CrashLoopBackOff' ) ;
317+
318+ // Search for Pod continers' errors
319+ pods . forEach ( ( pod ) => {
320+ pod . status ?. containerStatuses &&
321+ pod . status . containerStatuses . forEach ( ( cs ) => {
322+ if ( cs . state ?. waiting ) {
323+ const reason = cs . state . waiting . reason ;
324+ const message = cs . state . waiting . message ;
325+ const msg = `${ reason } : ${ message ? message . trim ( ) : 'No any valuable message' } ` ;
326+ // Skip duplicates and show not more than 10 errors
327+ if ( messages . length <= 10 && ! ( messages . find ( ( m ) => m === msg ) ) ) {
328+ messages . push ( msg ) ;
329+ }
330+ }
331+ } ) ;
332+ } ) ;
333+
334+ return {
335+ inCrashLoopBackOff : ( podsInCLBO . length > 0 ) ,
336+ messages
337+ }
338+ }
339+
340+ private collectDeploymentErrors ( deployment ) : string [ ] {
341+ const messages : string [ ] = [ ] ;
342+ deployment . status . conditions . filter ( ( c ) => c . status === 'False' )
343+ . forEach ( ( c ) => {
344+ const message = `${ c . reason } : ${ c . message ? c . message . trim ( ) : 'No any valuable message' } ` ;
345+
346+ // Skip duplicates and show not more than 10 errors
347+ if ( messages . length <= 10 && ! ( messages . find ( ( m ) => m === message ) ) ) {
348+ messages . push ( message ) ;
349+ }
350+ } ) ;
351+
352+ return messages ;
353+ }
354+
355+ private async getDeploymentItem ( element ) : Promise < TreeItem > {
356+ const shouldHaveReplicas = element . spec . replicas > 0 ;
357+ const availableReplicas = element . status . availableReplicas ? element . status . availableReplicas :
358+ element . spec . replicas ? element . spec . replicas : 0 ;
359+ const replicas = 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 clboMessages = '' ;
372+ podErrors . messages . forEach ( ( m ) => clboMessages = clboMessages . 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 } (${ replicas } /${ availableReplicas } )`
385+ tooltip = `${ tooltip } : ${ element . metadata . name } \n` . concat (
386+ `Available replicas: ${ availableReplicas } \n` ,
387+ `Replicas: ${ replicas } \n` ,
388+ `CrashLoopBackOff detected: ${ podErrors . inCrashLoopBackOff ? 'Yes' : 'No' } \n` ,
389+ clboMessages . length > 0 ? `\nPod Container Failures:${ clboMessages } \n` : '' ,
390+ errorMessages . length > 0 ? `\nDeployment Failures:${ errorMessages } \n` : ''
391+ ) ;
392+ }
393+ const iconSuffix = ! shouldHaveReplicas ? '' : this . getDeploymentIconSuffix ( pods ) ;
394+ const iconPath = element . kind === 'Deployment' || element . kind === 'DeploymentConfig' ?
395+ path . resolve ( __dirname , `../../images/context/component-node${ iconSuffix } .png` )
396+ : undefined ;
397+
398+ const routeURL = await Oc . Instance . getRouteURL ( element . metadata . name ) ;
399+ return {
275400 contextValue : `openshift.k8sObject.${ element . kind } ${ routeURL ? '.route' : '' } ` ,
276401 label : element . metadata . name ,
277- description : `${ element . kind . substring ( 0 , 1 ) . toLocaleUpperCase ( ) } ${ element . kind . substring ( 1 ) } ` ,
402+ description,
403+ tooltip,
278404 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 ,
405+ iconPath,
280406 command : {
281407 title : 'Load' ,
282408 command : 'openshift.resource.load' ,
283409 arguments : [ element ]
284410 }
285411 } ;
286-
287- }
288- return {
289- label : 'Unknown element'
290- }
291412 }
292413
293414 private makeCaps ( kind : string ) : string {
0 commit comments