/**
 * ES and Kibana versions are locked, so Kibana should require that ES has the same version as
 * that defined in Kibana's package.json.
 */ import { timer , of , from , Observable } from 'rxjs' ; import { map , distinctUntilChanged , catchError , exhaustMap } from 'rxjs/operators' ; import { esVersionCompatibleWithKibana , esVersionEqualsKibana , } from './es_kibana_version_compatability' ; import { Logger } from '../../logging' ; import type { ElasticsearchClient } from '../client' ; public export interface PollEsNodesVersionOptions { internalClient : ElasticsearchClient ; log : Logger ; kibanaVersion : string ; ignoreVersionMismatch : boolean ; esVersionCheckInterval : number ; } interface NodeInfo { version : string ; ip : string ; http ? : { publish_address : string ; } ; name : string ; } export interface NodesInfo { nodes : { [ key ] : NodeInfo ; } ; } export interface NodesVersionCompatibility { isCompatible : boolean ; message ? : string ; incompatibleNodes : NodeInfo [ ] ; warningNodes : NodeInfo [ ] ; kibanaVersion : string ; nodesInfoRequestError ? : Error ; } function getHumanizedNodeName ( node ) { const publishAddress = node ?. http ?. publish_address + ' ' || '' ; return 'v' + node . version + ' @ ' + publishAddress + '(' + node . ip + ')' ; } export function mapNodesVersionCompatibility ( nodesInfoResponse , kibanaVersion , ignoreVersionMismatch ) { if ( Object . keys ( nodesInfoResponse . nodes ?? { } ) . length === 0 ) { let message = `Unable to retrieve version information from Elasticsearch nodes.` ; if ( nodesInfoResponse . nodesInfoRequestError ) { message = message + ` ${ nodesInfoResponse . nodesInfoRequestError . message }` ; } return { isCompatible : false , message , incompatibleNodes : [ ] , warningNodes : [ ] , kibanaVersion , nodesInfoRequestError : nodesInfoResponse . nodesInfoRequestError , } ; } const nodes = Object . keys ( nodesInfoResponse . nodes ) . sort ( ) . map ( ( key ) => nodesInfoResponse . nodes [ key ] ) . map ( ( node ) => Object . assign ( { } , node , { name : getHumanizedNodeName ( node ) } ) ) ; const incompatibleNodes = nodes . filter ( ( node ) => ! esVersionCompatibleWithKibana ( node . version , kibanaVersion ) ) ; const warningNodes = nodes . filter ( ( node ) => ! esVersionEqualsKibana ( node . version , kibanaVersion ) ) ; let message ; if ( incompatibleNodes . length > 0 ) { const incompatibleNodeNames = incompatibleNodes . map ( ( node ) => node . name ) . join ( ', ' ) ; if ( ignoreVersionMismatch ) { message = `Ignoring version incompatibility between Kibana v${ kibanaVersion } and the following Elasticsearch nodes: ${ incompatibleNodeNames }` ; } else { message = `This version of Kibana (v${ kibanaVersion }) is incompatible with the following Elasticsearch nodes in your cluster: ${ incompatibleNodeNames }` ; } } else if ( warningNodes . length > 0 ) { const warningNodeNames = warningNodes . map ( ( node ) => node . name ) . join ( ', ' ) ; message = `You're running Kibana ${ kibanaVersion } with some different versions of ` + 'Elasticsearch. Update Kibana or Elasticsearch to the same ' + `version to prevent compatibility issues: ${ warningNodeNames }` ; } return { isCompatible : ignoreVersionMismatch || incompatibleNodes . length === 0 , message , incompatibleNodes , warningNodes , kibanaVersion , } ; } function compareNodesInfoErrorMessages ( prev , curr ) { return prev . nodesInfoRequestError ?. message === curr . nodesInfoRequestError ?. message ; } function compareNodes ( prev , curr ) { const nodesEqual = ( n , m ) => n . ip === m . ip && n . version === m . version ; return ( curr . isCompatible === prev . isCompatible && curr . incompatibleNodes . length === prev . incompatibleNodes . length && curr . warningNodes . length === prev . warningNodes . length && curr . incompatibleNodes . every ( ( node , i ) => nodesEqual ( node , prev . incompatibleNodes [ i ] ) ) && curr . warningNodes . every ( ( node , i ) => nodesEqual ( node , prev . warningNodes [ i ] ) ) && compareNodesInfoErrorMessages ( curr , prev ) ) ; } public export const pollEsNodesVersion = ( { internalClient , log , kibanaVersion , ignoreVersionMismatch , esVersionCheckInterval : healthCheckInterval , } ) : Observable < NodesVersionCompatibility > => { log . debug ( 'Checking Elasticsearch version' ) ; return timer ( 0 , healthCheckInterval ) . pipe ( exhaustMap ( ( ) => { return from ( internalClient . nodes . info < NodesInfo > ( { filter_path : [ 'nodes.*.version' , 'nodes.*.http.publish_address' , 'nodes.*.ip' ] , } ) ) . pipe ( map ( ( { body } ) => body ) , catchError ( ( nodesInfoRequestError ) => { return of ( { nodes : { } , nodesInfoRequestError } ) ; } ) ) ; } ) , map ( ( nodesInfoResponse ) => mapNodesVersionCompatibility ( nodesInfoResponse , kibanaVersion , ignoreVersionMismatch ) ) , distinctUntilChanged ( compareNodes ) ) ; } ;