import crypto from 'crypto' ; import { cloneDeep , mapValues } from 'lodash' ; import { IndexMapping , SavedObjectsMappingProperties , SavedObjectsTypeMappingDefinitions , } from './../../mappings' ; param typeDefinitions export function buildActiveMappings ( typeDefinitions ) { const mapping = defaultMapping ( ) ; const mergedProperties = validateAndMerge ( mapping . properties , typeDefinitions ) ; return cloneDeep ( { ... mapping , properties : mergedProperties , _meta : { migrationMappingPropertyHashes : md5Values ( mergedProperties ) , } , } ) ; } /**
 * Diffs the actual vs expected mappings. The properties are compared using md5 hashes stored in _meta, because
 * actual and expected mappings *can* differ, but if the md5 hashes stored in actual._meta.migrationMappingPropertyHashes
 * match our expectations, we don't require a migration. This allows OpenSearch to tack on additional mappings that OpenSearchDashboards
 * doesn't know about or expect, without triggering continual migrations.
 */ export function diffMappings ( actual , expected ) { if ( actual . dynamic !== expected . dynamic ) { return { changedProp : 'dynamic' } ; } if ( ! actual . _meta || ! actual . _meta . migrationMappingPropertyHashes ) { return { changedProp : '_meta' } ; } const changedProp = findChangedProp ( actual . _meta . migrationMappingPropertyHashes , expected . _meta ! . migrationMappingPropertyHashes ) ; return changedProp ? { changedProp : `properties.${ changedProp }` } : undefined ; } function md5Object ( obj ) { return crypto . createHash ( 'md5' ) . update ( canonicalStringify ( obj ) ) . digest ( 'hex' ) ; } function canonicalStringify ( obj ) { if ( Array . isArray ( obj ) ) { return `[${ obj . map ( canonicalStringify ) }]` ; } if ( ! obj || typeof obj !== 'object' ) { return JSON . stringify ( obj ) ; } const keys = Object . keys ( obj ) ; if ( ! keys . length ) { return JSON . stringify ( obj ) ; } const sortedObj = keys . sort ( ( a , b ) => a . localeCompare ( b ) ) . map ( ( k ) => `${ k }: ${ canonicalStringify ( obj [ k ] ) }` ) ; return `{${ sortedObj }}` ; } function md5Values ( obj ) { return mapValues ( obj , md5Object ) ; } function findChangedProp ( actual , expected ) { return Object . keys ( expected ) . find ( ( k ) => actual [ k ] !== expected [ k ] ) ; } returns { IndexMapping } function defaultMapping ( ) { return { dynamic : 'strict' , properties : { migrationVersion : { dynamic : 'true' , type : 'object' , } , type : { type : 'keyword' , } , namespace : { type : 'keyword' , } , namespaces : { type : 'keyword' , } , originId : { type : 'keyword' , } , updated_at : { type : 'date' , } , references : { type : 'nested' , properties : { name : { type : 'keyword' , } , type : { type : 'keyword' , } , id : { type : 'keyword' , } , } , } , } , } ; } function validateAndMerge ( dest , source ) { Object . keys ( source ) . forEach ( ( k ) => { if ( k . startsWith ( '_' ) ) { throw new Error ( `Invalid mapping "${ k }". Mappings cannot start with _.` ) ; } if ( dest . hasOwnProperty ( k ) ) { throw new Error ( `Cannot redefine core mapping "${ k }".` ) ; } } ) ; return Object . assign ( dest , source ) ; }