diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e3df86ae78c4..670f58bfc1b7 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -307,8 +307,6 @@ export { SavedObjectsRepository, SavedObjectsDeleteByNamespaceOptions, SavedObjectsIncrementCounterOptions, - SavedObjectsComplexFieldMapping, - SavedObjectsCoreFieldMapping, SavedObjectsFieldMapping, SavedObjectsTypeMappingDefinition, SavedObjectsMappingProperties, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 6a8ab84e86f6..2ccee2333e99 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -65,8 +65,6 @@ export { } from './service/lib/repository'; export { - SavedObjectsCoreFieldMapping, - SavedObjectsComplexFieldMapping, SavedObjectsFieldMapping, SavedObjectsMappingProperties, SavedObjectsTypeMappingDefinition, diff --git a/src/core/server/saved_objects/mappings/index.ts b/src/core/server/saved_objects/mappings/index.ts index fe6c38fda5ca..f5400384bbe3 100644 --- a/src/core/server/saved_objects/mappings/index.ts +++ b/src/core/server/saved_objects/mappings/index.ts @@ -31,8 +31,6 @@ */ export { getTypes, getProperty, getRootProperties, getRootPropertiesObjects } from './lib'; export { - SavedObjectsComplexFieldMapping, - SavedObjectsCoreFieldMapping, SavedObjectsTypeMappingDefinition, SavedObjectsTypeMappingDefinitions, SavedObjectsMappingProperties, diff --git a/src/core/server/saved_objects/mappings/lib/get_property.test.ts b/src/core/server/saved_objects/mappings/lib/get_property.test.ts index a05d671f1a9e..d23d64648b20 100644 --- a/src/core/server/saved_objects/mappings/lib/get_property.test.ts +++ b/src/core/server/saved_objects/mappings/lib/get_property.test.ts @@ -53,7 +53,7 @@ const MAPPINGS = { }, }, }, -}; +} as const; function runTest(key: string | string[], mapping: IndexMapping | SavedObjectsFieldMapping) { expect(typeof key === 'string' || Array.isArray(key)).toBeTruthy(); diff --git a/src/core/server/saved_objects/mappings/lib/get_property.ts b/src/core/server/saved_objects/mappings/lib/get_property.ts index 012907dd4d81..03a9ef1a02a5 100644 --- a/src/core/server/saved_objects/mappings/lib/get_property.ts +++ b/src/core/server/saved_objects/mappings/lib/get_property.ts @@ -26,7 +26,7 @@ */ import { toPath } from 'lodash'; -import { SavedObjectsCoreFieldMapping, SavedObjectsFieldMapping, IndexMapping } from '../types'; +import { SavedObjectsFieldMapping, IndexMapping } from '../types'; function getPropertyMappingFromObjectMapping( mapping: IndexMapping | SavedObjectsFieldMapping, @@ -34,7 +34,7 @@ function getPropertyMappingFromObjectMapping( ): SavedObjectsFieldMapping | undefined { const props = (mapping && (mapping as IndexMapping).properties) || - (mapping && (mapping as SavedObjectsCoreFieldMapping).fields); + (mapping && (mapping as SavedObjectsFieldMapping).fields); if (!props) { return undefined; diff --git a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.test.ts b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.test.ts index fc4a76164778..00ca1110df2a 100644 --- a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.test.ts +++ b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.test.ts @@ -34,7 +34,7 @@ test(`returns single object with properties`, () => { properties: {}, }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -51,7 +51,7 @@ test(`returns single object with type === 'object'`, () => { type: 'object', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -71,7 +71,7 @@ test(`returns two objects with properties`, () => { properties: {}, }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -94,7 +94,7 @@ test(`returns two objects with type === 'object'`, () => { type: 'object', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -114,7 +114,7 @@ test(`excludes objects without properties and type of keyword`, () => { type: 'keyword', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({}); @@ -130,7 +130,7 @@ test(`excludes two objects without properties and type of keyword`, () => { type: 'keyword', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({}); @@ -146,7 +146,7 @@ test(`includes one object with properties and excludes one object without proper type: 'keyword', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -166,7 +166,7 @@ test(`includes one object with type === 'object' and excludes one object without type: 'keyword', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ @@ -189,7 +189,7 @@ test('excludes references and migrationVersion which are part of the blacklist', type: 'object', }, }, - }; + } as const; const result = getRootPropertiesObjects(mappings); expect(result).toEqual({ foo: { diff --git a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts index 30e82c80e92d..c0b8adc5a5ae 100644 --- a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts +++ b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts @@ -25,11 +25,7 @@ * under the License. */ -import { - SavedObjectsComplexFieldMapping, - IndexMapping, - SavedObjectsMappingProperties, -} from '../types'; +import { SavedObjectsFieldMapping, IndexMapping, SavedObjectsMappingProperties } from '../types'; import { getRootProperties } from './get_root_properties'; /** @@ -55,7 +51,7 @@ export function getRootPropertiesObjects(mappings: IndexMapping) { // we consider the existence of the properties or type of object to designate that this is an object datatype if ( !omittedRootProps.includes(key) && - ((value as SavedObjectsComplexFieldMapping).properties || value.type === 'object') + ((value as SavedObjectsFieldMapping).properties || value.type === 'object') ) { acc[key] = value; } diff --git a/src/core/server/saved_objects/mappings/types.ts b/src/core/server/saved_objects/mappings/types.ts index b5fbac25c470..e14b3febb709 100644 --- a/src/core/server/saved_objects/mappings/types.ts +++ b/src/core/server/saved_objects/mappings/types.ts @@ -30,6 +30,8 @@ * GitHub history for details. */ +import type { opensearchtypes } from '@opensearch-project/opensearch'; + /** * Describe a saved object type mapping. * @@ -117,13 +119,21 @@ export interface SavedObjectsMappingProperties { * * @public */ -export type SavedObjectsFieldMapping = - | SavedObjectsCoreFieldMapping - | SavedObjectsComplexFieldMapping; +export type SavedObjectsFieldMapping = opensearchtypes.MappingProperty & { + /** + * The dynamic property of the mapping, either `false` or `'strict'`. If + * unspecified `dynamic: 'strict'` will be inherited from the top-level + * index mappings. + * + * Note: To limit the number of mapping fields Saved Object types should + * *never* use `dynamic: true`. + */ + dynamic?: false | 'strict'; +}; /** @internal */ export interface IndexMapping { - dynamic?: string; + dynamic?: boolean | 'strict'; properties: SavedObjectsMappingProperties; _meta?: IndexMappingMeta; } @@ -135,42 +145,3 @@ export interface IndexMappingMeta { // the md5 hash of that mapping's value when the index was created. migrationMappingPropertyHashes?: { [k: string]: string }; } - -/** - * See {@link SavedObjectsFieldMapping} for documentation. - * - * @public - */ -export interface SavedObjectsCoreFieldMapping { - type: string; - null_value?: number | boolean | string; - index?: boolean; - doc_values?: boolean; - fields?: { - [subfield: string]: { - type: string; - ignore_above?: number; - }; - }; -} - -/** - * See {@link SavedObjectsFieldMapping} for documentation. - * - * @public - */ -export interface SavedObjectsComplexFieldMapping { - /** - * The dynamic property of the mapping, either `false` or `'strict'`. If - * unspecified `dynamic: 'strict'` will be inherited from the top-level - * index mappings. - * - * Note: To limit the number of mapping fields Saved Object types should - * *never* use `dynamic: true`. - */ - dynamic?: false | 'strict'; - enabled?: boolean; - doc_values?: boolean; - type?: string; - properties: SavedObjectsMappingProperties; -} diff --git a/src/core/server/saved_objects/migrations/core/__snapshots__/opensearch_index.test.ts.snap b/src/core/server/saved_objects/migrations/core/__snapshots__/opensearch_index.test.ts.snap index 6bd567be204d..a9fa5ed34ed5 100644 --- a/src/core/server/saved_objects/migrations/core/__snapshots__/opensearch_index.test.ts.snap +++ b/src/core/server/saved_objects/migrations/core/__snapshots__/opensearch_index.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ElasticIndex write writes documents in bulk to the index 1`] = ` +exports[`OpenSearchIndex write writes documents in bulk to the index 1`] = ` Array [ Object { "body": Array [ diff --git a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts index 81aa395bd702..17f64b3e488e 100644 --- a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts +++ b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts @@ -38,19 +38,19 @@ describe('buildActiveMappings', () => { const properties = { aaa: { type: 'text' }, bbb: { type: 'long' }, - }; + } as const; expect(buildActiveMappings(properties)).toMatchSnapshot(); }); test('disallows duplicate mappings', () => { - const properties = { type: { type: 'long' } }; + const properties = { type: { type: 'long' } } as const; expect(() => buildActiveMappings(properties)).toThrow(/Cannot redefine core mapping \"type\"/); }); test('disallows mappings with leading underscore', () => { - const properties = { _hm: { type: 'keyword' } }; + const properties = { _hm: { type: 'keyword' } } as const; expect(() => buildActiveMappings(properties)).toThrow( /Invalid mapping \"_hm\"\. Mappings cannot start with _/ @@ -79,7 +79,7 @@ describe('buildActiveMappings', () => { aaa: { type: 'keyword', fields: { a: { type: 'keyword' }, b: { type: 'text' } } }, bbb: { fields: { b: { type: 'text' }, a: { type: 'keyword' } }, type: 'keyword' }, ccc: { fields: { b: { type: 'text' }, a: { type: 'text' } }, type: 'keyword' }, - }; + } as const; const mappings = buildActiveMappings(properties); const hashes = mappings._meta!.migrationMappingPropertyHashes!; @@ -183,6 +183,7 @@ describe('diffMappings', () => { _meta: { migrationMappingPropertyHashes: { foo: 'bar' }, }, + // @ts-expect-error dynamic: 'abcde', properties: {}, }; diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts index 64fc54d3640e..eb46066f1864 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts @@ -55,7 +55,7 @@ test('mappings without index pattern goes to default index', () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -67,7 +67,7 @@ test('mappings without index pattern goes to default index', () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -88,7 +88,7 @@ test(`mappings with custom index pattern doesn't go to default index`, () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -100,7 +100,7 @@ test(`mappings with custom index pattern doesn't go to default index`, () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -122,7 +122,7 @@ test('creating a script gets added to the index pattern', () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -135,7 +135,7 @@ test('creating a script gets added to the index pattern', () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, @@ -163,18 +163,18 @@ test('throws when two scripts are defined for an index pattern', () => { type1: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, type2: { properties: { field1: { - type: 'string', + type: 'text', }, }, }, - }; + } as const; expect(() => createIndexMap({ opensearchDashboardsIndexName: defaultIndex, diff --git a/src/core/server/saved_objects/migrations/core/call_cluster.ts b/src/core/server/saved_objects/migrations/core/call_cluster.ts index f896da60c005..c3e6a72f937a 100644 --- a/src/core/server/saved_objects/migrations/core/call_cluster.ts +++ b/src/core/server/saved_objects/migrations/core/call_cluster.ts @@ -31,11 +31,15 @@ * funcationality contained here. */ +import { opensearchtypes } from '@opensearch-project/opensearch'; import { IndexMapping } from '../../mappings'; export interface CallCluster { (path: 'bulk', opts: { body: object[] }): Promise; - (path: 'count', opts: CountOpts): Promise<{ count: number; _shards: ShardsInfo }>; + (path: 'count', opts: CountOpts): Promise<{ + count: number; + _shards: opensearchtypes.ShardStatistics; + }>; (path: 'clearScroll', opts: { scrollId: string }): Promise; (path: 'indices.create' | 'indices.delete', opts: IndexCreationOpts): Promise; (path: 'indices.exists', opts: IndexOpts): Promise; @@ -172,14 +176,7 @@ export interface SearchResults { hits: RawDoc[]; }; _scroll_id?: string; - _shards: ShardsInfo; -} - -export interface ShardsInfo { - total: number; - successful: number; - skipped: number; - failed: number; + _shards: opensearchtypes.ShardStatistics; } export interface ErrorResponse { diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index 8058f20ce3d2..2fefbe04133e 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -26,6 +26,7 @@ */ import _ from 'lodash'; +import type { opensearchtypes } from '@opensearch-project/opensearch'; import { opensearchClientMock } from '../../../opensearch/client/mocks'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; @@ -454,23 +455,28 @@ function withIndex( opensearchClientMock.createSuccessTransportRequestPromise({ task: 'zeid', _shards: { successful: 1, total: 1 }, - }) + } as opensearchtypes.ReindexResponse) ); client.tasks.get.mockReturnValue( - opensearchClientMock.createSuccessTransportRequestPromise({ completed: true }) + opensearchClientMock.createSuccessTransportRequestPromise({ + completed: true, + } as opensearchtypes.TaskGetResponse) ); client.search.mockReturnValue( - opensearchClientMock.createSuccessTransportRequestPromise(searchResult(0)) + opensearchClientMock.createSuccessTransportRequestPromise(searchResult(0) as any) ); client.bulk.mockReturnValue( - opensearchClientMock.createSuccessTransportRequestPromise({ items: [] }) + opensearchClientMock.createSuccessTransportRequestPromise({ + items: [] as any[], + } as opensearchtypes.BulkResponse) ); client.count.mockReturnValue( opensearchClientMock.createSuccessTransportRequestPromise({ count: numOutOfDate, _shards: { successful: 1, total: 1 }, - }) + } as opensearchtypes.CountResponse) ); + // @ts-expect-error client.scroll.mockImplementation(() => { if (scrollCallCounter <= docs.length) { const result = searchResult(scrollCallCounter); diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.ts b/src/core/server/saved_objects/migrations/core/index_migrator.ts index 5c6a01246991..17ac3f752917 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.ts @@ -169,7 +169,7 @@ async function deleteIndexTemplates({ client, log, obsoleteIndexTemplatePattern log.info(`Removing index templates: ${templateNames}`); - return Promise.all(templateNames.map((name) => client.indices.deleteTemplate({ name }))); + return Promise.all(templateNames.map((name) => client.indices.deleteTemplate({ name: name! }))); } /** @@ -207,6 +207,7 @@ async function migrateSourceToDest(context: Context) { await Index.write( client, dest.indexName, + // @ts-expect-error @opensearch-project/opensearch _source is optional await migrateRawDocs(serializer, documentMigrator.migrate, docs, log) ); } diff --git a/src/core/server/saved_objects/migrations/core/migration_context.test.ts b/src/core/server/saved_objects/migrations/core/migration_context.test.ts index 43a8ec983533..ce1324183cbe 100644 --- a/src/core/server/saved_objects/migrations/core/migration_context.test.ts +++ b/src/core/server/saved_objects/migrations/core/migration_context.test.ts @@ -50,7 +50,7 @@ describe('disableUnknownTypeMappingFields', () => { }, }, }, - }; + } as const; const activeMappings = { _meta: { migrationMappingPropertyHashes: { @@ -65,7 +65,7 @@ describe('disableUnknownTypeMappingFields', () => { }, }, }, - }; + } as const; const targetMappings = disableUnknownTypeMappingFields(activeMappings, sourceMappings); it('disables complex field mappings from unknown types in the source mappings', () => { diff --git a/src/core/server/saved_objects/migrations/core/opensearch_index.test.ts b/src/core/server/saved_objects/migrations/core/opensearch_index.test.ts index c5c0473fe6b5..4edbbfd9b1f9 100644 --- a/src/core/server/saved_objects/migrations/core/opensearch_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/opensearch_index.test.ts @@ -25,11 +25,12 @@ * under the License. */ +import type { opensearchtypes } from '@opensearch-project/opensearch'; import _ from 'lodash'; import { opensearchClientMock } from '../../../opensearch/client/mocks'; import * as Index from './opensearch_index'; -describe('ElasticIndex', () => { +describe('OpenSearchIndex', () => { let client: ReturnType; beforeEach(() => { @@ -61,9 +62,11 @@ describe('ElasticIndex', () => { return opensearchClientMock.createSuccessTransportRequestPromise({ [index]: { aliases: { foo: index }, - mappings: { spock: { dynamic: 'strict', properties: { a: 'b' } } }, + // @ts-expect-error + mappings: { spock: { dynamic: 'strict', properties: { a: 'b' } as any } }, + settings: {}, }, - }); + } as opensearchtypes.IndicesGetResponse); }); await expect(Index.fetchInfo(client, '.baz')).rejects.toThrow( @@ -78,11 +81,13 @@ describe('ElasticIndex', () => { [index]: { aliases: { foo: index }, mappings: { - doc: { dynamic: 'strict', properties: { a: 'b' } }, - doctor: { dynamic: 'strict', properties: { a: 'b' } }, + // @ts-expect-error + doc: { dynamic: 'strict', properties: { a: 'b' } as any }, + doctor: { dynamic: 'strict', properties: { a: 'b' } as any }, }, + settings: {}, }, - }); + } as opensearchtypes.IndicesGetResponse); }); await expect(Index.fetchInfo(client, '.baz')).rejects.toThrow( @@ -96,9 +101,10 @@ describe('ElasticIndex', () => { return opensearchClientMock.createSuccessTransportRequestPromise({ [index]: { aliases: { foo: index }, - mappings: { dynamic: 'strict', properties: { a: 'b' } }, + mappings: { dynamic: 'strict', properties: { a: 'b' } as any }, + settings: {}, }, - }); + } as opensearchtypes.IndicesGetResponse); }); const info = await Index.fetchInfo(client, '.baz'); @@ -107,6 +113,7 @@ describe('ElasticIndex', () => { mappings: { dynamic: 'strict', properties: { a: 'b' } }, exists: true, indexName: '.baz', + settings: {}, }); }); }); @@ -167,7 +174,7 @@ describe('ElasticIndex', () => { test('removes existing alias', async () => { client.indices.getAlias.mockResolvedValue( opensearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); @@ -190,7 +197,7 @@ describe('ElasticIndex', () => { test('allows custom alias actions', async () => { client.indices.getAlias.mockResolvedValue( opensearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); @@ -218,14 +225,18 @@ describe('ElasticIndex', () => { test('it creates the destination index, then reindexes to it', async () => { client.indices.getAlias.mockResolvedValue( opensearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); client.reindex.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise({ task: 'abc' }) + opensearchClientMock.createSuccessTransportRequestPromise({ + task: 'abc', + } as opensearchtypes.ReindexResponse) ); client.tasks.get.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise({ completed: true }) + opensearchClientMock.createSuccessTransportRequestPromise({ + completed: true, + } as opensearchtypes.TaskGetResponse) ); const info = { @@ -233,10 +244,10 @@ describe('ElasticIndex', () => { exists: true, indexName: '.ze-index', mappings: { - dynamic: 'strict', + dynamic: 'strict' as const, properties: { foo: { type: 'keyword' } }, }, - }; + } as const; await Index.convertToAlias( client, @@ -292,13 +303,16 @@ describe('ElasticIndex', () => { test('throws error if re-index task fails', async () => { client.indices.getAlias.mockResolvedValue( opensearchClientMock.createSuccessTransportRequestPromise({ - '.my-fanci-index': '.muchacha', + '.my-fanci-index': { aliases: { '.muchacha': {} } }, }) ); client.reindex.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise({ task: 'abc' }) + opensearchClientMock.createSuccessTransportRequestPromise({ + task: 'abc', + } as opensearchtypes.ReindexResponse) ); client.tasks.get.mockResolvedValue( + // @ts-expect-error @opensearch-project/opensearch GetTaskResponse requires a `task` property even on errors opensearchClientMock.createSuccessTransportRequestPromise({ completed: true, error: { @@ -306,7 +320,7 @@ describe('ElasticIndex', () => { reason: 'all shards failed', failed_shards: [], }, - }) + } as opensearchtypes.TaskGetResponse) ); const info = { @@ -319,6 +333,7 @@ describe('ElasticIndex', () => { }, }; + // @ts-expect-error await expect(Index.convertToAlias(client, info, '.muchacha', 10)).rejects.toThrow( /Re-index failed \[search_phase_execution_exception\] all shards failed/ ); @@ -352,7 +367,9 @@ describe('ElasticIndex', () => { describe('write', () => { test('writes documents in bulk to the index', async () => { client.bulk.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise({ items: [] }) + opensearchClientMock.createSuccessTransportRequestPromise({ + items: [] as any[], + } as opensearchtypes.BulkResponse) ); const index = '.myalias'; @@ -389,7 +406,7 @@ describe('ElasticIndex', () => { client.bulk.mockResolvedValue( opensearchClientMock.createSuccessTransportRequestPromise({ items: [{ index: { error: { type: 'shazm', reason: 'dern' } } }], - }) + } as opensearchtypes.BulkResponse) ); const index = '.myalias'; diff --git a/src/core/server/saved_objects/migrations/core/opensearch_index.ts b/src/core/server/saved_objects/migrations/core/opensearch_index.ts index aca47217d68d..dc5d8b507c5b 100644 --- a/src/core/server/saved_objects/migrations/core/opensearch_index.ts +++ b/src/core/server/saved_objects/migrations/core/opensearch_index.ts @@ -31,11 +31,12 @@ */ import _ from 'lodash'; +import { opensearchtypes } from '@opensearch-project/opensearch'; import { MigrationOpenSearchClient } from './migration_opensearch_client'; -import { CountResponse, SearchResponse } from '../../../opensearch'; +import { CountResponse } from '../../../opensearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsMigrationVersion } from '../../types'; -import { AliasAction, RawDoc, ShardsInfo } from './call_cluster'; +import { AliasAction, RawDoc } from './call_cluster'; import { SavedObjectsRawDocSource } from '../../serialization'; const settings = { number_of_shards: 1, auto_expand_replicas: '0-1' }; @@ -68,6 +69,7 @@ export async function fetchInfo( const [indexName, indexInfo] = Object.entries(body)[0]; + // @ts-expect-error @opensearch-project/opensearch IndexState.alias and IndexState.mappings should be required return assertIsSupportedIndex({ ...indexInfo, exists: true, indexName }); } @@ -91,11 +93,11 @@ export function reader( const nextBatch = () => scrollId !== undefined - ? client.scroll>({ + ? client.scroll({ scroll, scroll_id: scrollId, }) - : client.search>({ + : client.search({ body: { size: batchSize }, index, scroll, @@ -147,7 +149,7 @@ export async function write(client: MigrationOpenSearchClient, index: string, do return; } - const exception: any = new Error(err.index.error!.reason); + const exception: any = new Error(err.index!.error!.reason); exception.detail = err; throw exception; } @@ -319,7 +321,7 @@ function assertIsSupportedIndex(indexInfo: FullIndexInfo) { * Object indices should only ever have a single shard. This is more to handle * instances where customers manually expand the shards of an index. */ -function assertResponseIncludeAllShards({ _shards }: { _shards: ShardsInfo }) { +function assertResponseIncludeAllShards({ _shards }: { _shards: opensearchtypes.ShardStatistics }) { if (!_.has(_shards, 'total') || !_.has(_shards, 'successful')) { return; } @@ -372,7 +374,7 @@ async function reindex( await new Promise((r) => setTimeout(r, pollInterval)); const { body } = await client.tasks.get({ - task_id: task, + task_id: String(task), }); if (body.error) { diff --git a/src/core/server/saved_objects/migrations/opensearch_dashboards/opensearch_dashboards_migrator.test.ts b/src/core/server/saved_objects/migrations/opensearch_dashboards/opensearch_dashboards_migrator.test.ts index e3daad27d6fc..6dcf531880fc 100644 --- a/src/core/server/saved_objects/migrations/opensearch_dashboards/opensearch_dashboards_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/opensearch_dashboards/opensearch_dashboards_migrator.test.ts @@ -81,7 +81,8 @@ describe('OpenSearchDashboardsMigrator', () => { options.client.cat.templates.mockReturnValue( opensearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, + // @ts-expect-error + { templates: [] } as CatTemplatesResponse, { statusCode: 404 } ) ); @@ -105,7 +106,8 @@ describe('OpenSearchDashboardsMigrator', () => { options.client.cat.templates.mockReturnValue( opensearchClientMock.createSuccessTransportRequestPromise( - { templates: [] }, + // @ts-expect-error + { templates: [] } as CatTemplatesResponse, { statusCode: 404 } ) ); diff --git a/src/core/server/saved_objects/saved_objects_type_registry.ts b/src/core/server/saved_objects/saved_objects_type_registry.ts index 599f9eb0cfa6..57a3f9aedb3a 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.ts @@ -56,7 +56,7 @@ export class SavedObjectTypeRegistry { if (this.types.has(type.name)) { throw new Error(`Type '${type.name}' is already registered`); } - this.types.set(type.name, deepFreeze(type)); + this.types.set(type.name, deepFreeze(type) as SavedObjectsType); } /** diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts index 4e1245e37212..397bac7e37be 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts @@ -48,7 +48,7 @@ const mockMappings = { type: 'text', }, bytes: { - type: 'number', + type: 'integer', }, }, }, @@ -92,7 +92,7 @@ const mockMappings = { }, }, }, -}; +} as const; describe('Filter Utils', () => { describe('#validateConvertFilterToKueryNode', () => { diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index b2ebc43db362..6c47a91e315e 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -3352,6 +3352,7 @@ describe('SavedObjectsRepository', () => { id: '6.0.0-alpha1', ...mockTimestampFields, version: mockVersion, + references: [], attributes: { buildNum: 8468, defaultIndex: 'logstash-*', diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 01ef8fdb1fd5..123def8d3c31 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -31,13 +31,10 @@ */ import { omit } from 'lodash'; +import type { opensearchtypes } from '@opensearch-project/opensearch'; import uuid from 'uuid'; -import { - OpenSearchClient, - DeleteDocumentResponse, - GetResponse, - SearchResponse, -} from '../../../opensearch/'; +import type { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; +import { OpenSearchClient, DeleteDocumentResponse } from '../../../opensearch/'; import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; import { createRepositoryOpenSearchClient, @@ -399,8 +396,8 @@ export class SavedObjectsRepository { return expectedBulkGetResult; } - let savedObjectNamespace; - let savedObjectNamespaces; + let savedObjectNamespace: string | undefined; + let savedObjectNamespaces: string[] | undefined; let versionProperties; const { opensearchRequestIndex, @@ -412,8 +409,9 @@ export class SavedObjectsRepository { const actualResult = indexFound ? bulkGetResponse?.body.docs[opensearchRequestIndex] : undefined; - const docFound = indexFound && actualResult.found === true; - if (docFound && !this.rawDocExistsInNamespace(actualResult, namespace)) { + const docFound = indexFound && actualResult?.found === true; + // @ts-expect-error MultiGetHit._source is optional + if (docFound && !this.rawDocExistsInNamespace(actualResult!, namespace)) { const { id, type } = object; return { tag: 'Left' as 'Left', @@ -428,11 +426,14 @@ export class SavedObjectsRepository { }; } savedObjectNamespaces = - initialNamespaces || getSavedObjectNamespaces(namespace, docFound && actualResult); + initialNamespaces || + // @ts-expect-error MultiGetHit._source is optional + getSavedObjectNamespaces(namespace, docFound ? actualResult : undefined); + // @ts-expect-error MultiGetHit._source is optional versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(object.type)) { - savedObjectNamespace = namespace; + savedObjectNamespace = initialNamespaces ? initialNamespaces[0] : namespace; } else if (this._registry.isMultiNamespace(object.type)) { savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); } @@ -486,7 +487,7 @@ export class SavedObjectsRepository { const { requestedId, rawMigratedDoc, opensearchRequestIndex } = expectedResult.value; const { error, ...rawResponse } = Object.values( - bulkResponse?.body.items[opensearchRequestIndex] + bulkResponse?.body.items[opensearchRequestIndex] ?? {} )[0] as any; if (error) { @@ -572,13 +573,14 @@ export class SavedObjectsRepository { const { type, id, opensearchRequestIndex } = expectedResult.value; const doc = bulkGetResponse?.body.docs[opensearchRequestIndex]; - if (doc.found) { + if (doc?.found) { errors.push({ id, type, error: { ...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)), - ...(!this.rawDocExistsInNamespace(doc, namespace) && { + // @ts-expect-error MultiGetHit._source is optional + ...(!this.rawDocExistsInNamespace(doc!, namespace) && { metadata: { isNotOverwritable: true }, }), }, @@ -813,9 +815,12 @@ export class SavedObjectsRepository { }, }; - const { body, statusCode } = await this.client.search>(opensearchOptions, { - ignore: [404], - }); + const { body, statusCode } = await this.client.search( + opensearchOptions, + { + ignore: [404], + } + ); if (statusCode === 404) { // 404 is only possible here if the index is missing, which // we don't want to leak, see "404s from missing index" above @@ -832,9 +837,12 @@ export class SavedObjectsRepository { per_page: perPage, total: body.hits.total, saved_objects: body.hits.hits.map( - (hit: SavedObjectsRawDoc): SavedObjectsFindResult => ({ + (hit: opensearchtypes.SearchHit): SavedObjectsFindResult => ({ + // @ts-expect-error @opensearch-project/opensearch _source is optional ...this._rawToSavedObject(hit), - score: (hit as any)._score, + score: hit._score!, + // @ts-expect-error @opensearch-project/opensearch _source is optional + sort: hit.sort, }) ), } as SavedObjectsFindResponse; @@ -917,7 +925,8 @@ export class SavedObjectsRepository { const { type, id, opensearchRequestIndex } = expectedResult.value; const doc = bulkGetResponse?.body.docs[opensearchRequestIndex]; - if (!doc.found || !this.rawDocExistsInNamespace(doc, namespace)) { + // @ts-expect-error MultiGetHit._source is optional + if (!doc?.found || !this.rawDocExistsInNamespace(doc, namespace)) { return ({ id, type, @@ -925,25 +934,8 @@ export class SavedObjectsRepository { } as any) as SavedObject; } - const { originId, updated_at: updatedAt } = doc._source; - let namespaces = []; - if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = doc._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(doc._source.namespace), - ]; - } - - return { - id, - type, - namespaces, - ...(originId && { originId }), - ...(updatedAt && { updated_at: updatedAt }), - version: encodeHitVersion(doc), - attributes: doc._source[type], - references: doc._source.references || [], - migrationVersion: doc._source.migrationVersion, - }; + // @ts-expect-error MultiGetHit._source is optional + return getSavedObjectFromSource(this._registry, type, id, doc); }), }; } @@ -968,7 +960,7 @@ export class SavedObjectsRepository { const namespace = normalizeNamespace(options.namespace); - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), @@ -976,9 +968,12 @@ export class SavedObjectsRepository { { ignore: [404] } ); - const docNotFound = body.found === false; const indexNotFound = statusCode === 404; - if (docNotFound || indexNotFound || !this.rawDocExistsInNamespace(body, namespace)) { + if ( + !isFoundGetResponse(body) || + indexNotFound || + !this.rawDocExistsInNamespace(body, namespace) + ) { // see "404s from missing index" above throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -1042,7 +1037,7 @@ export class SavedObjectsRepository { ...(Array.isArray(references) && { references }), }; - const { body, statusCode } = await this.client.update( + const { body, statusCode } = await this.client.update( { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), @@ -1062,11 +1057,11 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { originId } = body.get._source; - let namespaces = []; + const { originId } = body.get?._source ?? {}; + let namespaces: string[] = []; if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = body.get._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(body.get._source.namespace), + namespaces = body.get?._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(body.get?._source.namespace), ]; } @@ -1074,7 +1069,6 @@ export class SavedObjectsRepository { id, type, updated_at: time, - // @ts-expect-error update doesn't have _seq_no, _primary_term as Record / any in LP version: encodeHitVersion(body), namespaces, ...(originId && { originId }), @@ -1368,9 +1362,10 @@ export class SavedObjectsRepository { const actualResult = indexFound ? bulkGetResponse?.body.docs[opensearchRequestIndex] : undefined; - const docFound = indexFound && actualResult.found === true; + const docFound = indexFound && actualResult?.found === true; if ( !docFound || + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source !this.rawDocExistsInNamespace(actualResult, getNamespaceId(objectNamespace)) ) { return { @@ -1382,9 +1377,12 @@ export class SavedObjectsRepository { }, }; } - namespaces = actualResult._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(actualResult._source.namespace), + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + namespaces = actualResult!._source.namespaces ?? [ + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace), ]; + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(type)) { @@ -1439,7 +1437,7 @@ export class SavedObjectsRepository { documentToSave, opensearchRequestIndex, } = expectedResult.value; - const response = bulkUpdateResponse?.body.items[opensearchRequestIndex]; + const response = bulkUpdateResponse?.body.items[opensearchRequestIndex] ?? {}; // When a bulk update operation is completed, any fields specified in `_sourceIncludes` will be found in the "get" value of the // returned object. We need to retrieve the `originId` if it exists so we can return it to the consumer. const { error, _seq_no: seqNo, _primary_term: primaryTerm, get } = Object.values( @@ -1522,7 +1520,7 @@ export class SavedObjectsRepository { const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc); - const { body } = await this.client.update({ + const { body } = await this.client.update({ id: raw._id, index: this.getIndexForType(type), refresh, @@ -1550,17 +1548,16 @@ export class SavedObjectsRepository { }, }); - const { originId } = body.get._source; + const { originId } = body.get?._source ?? {}; return { id, type, ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), ...(originId && { originId }), updated_at: time, - references: body.get._source.references, - // @ts-expect-error + references: body.get?._source.references ?? [], version: encodeHitVersion(body), - attributes: body.get._source[type], + attributes: body.get?._source[type], }; } @@ -1605,7 +1602,7 @@ export class SavedObjectsRepository { * WARNING: This should only be used for documents that were retrieved from OpenSearch. Otherwise, the guarantees of the document ID * format mentioned above do not apply. */ - private rawDocExistsInNamespace(raw: SavedObjectsRawDoc, namespace?: string) { + private rawDocExistsInNamespace(raw: SavedObjectsRawDoc, namespace: string | undefined) { const rawDocType = raw._source.type; // if the type is namespace isolated, or namespace agnostic, we can continue to rely on the guarantees @@ -1638,7 +1635,7 @@ export class SavedObjectsRepository { throw new Error(`Cannot make preflight get request for non-multi-namespace type '${type}'.`); } - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: this._serializer.generateRawId(undefined, type, id), index: this.getIndexForType(type), @@ -1649,8 +1646,7 @@ export class SavedObjectsRepository { ); const indexFound = statusCode !== 404; - const docFound = indexFound && body.found === true; - if (docFound) { + if (indexFound && isFoundGetResponse(body)) { if (!this.rawDocExistsInNamespace(body, namespace)) { throw SavedObjectsErrorHelpers.createConflictError(type, id); } @@ -1675,7 +1671,7 @@ export class SavedObjectsRepository { } const rawId = this._serializer.generateRawId(undefined, type, id); - const { body, statusCode } = await this.client.get>( + const { body, statusCode } = await this.client.get( { id: rawId, index: this.getIndexForType(type), @@ -1684,11 +1680,14 @@ export class SavedObjectsRepository { ); const indexFound = statusCode !== 404; - const docFound = indexFound && body.found === true; - if (!docFound || !this.rawDocExistsInNamespace(body, namespace)) { + if ( + !indexFound || + !isFoundGetResponse(body) || + !this.rawDocExistsInNamespace(body, namespace) + ) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return body as SavedObjectsRawDoc; + return body; } } @@ -1741,6 +1740,44 @@ function getSavedObjectNamespaces( return [SavedObjectsUtils.namespaceIdToString(namespace)]; } +/** + * Gets a saved object from a raw OpenSearch document. + * + * @param registry Registry which holds the registered saved object types information. + * @param type The type of the saved object. + * @param id The ID of the saved object. + * @param doc Doc contains _source and optional _seq_no and _primary_term. + * + * @internal + */ +function getSavedObjectFromSource( + registry: ISavedObjectTypeRegistry, + type: string, + id: string, + doc: { _seq_no?: number; _primary_term?: number; _source: SavedObjectsRawDocSource } +): SavedObject { + const { originId, updated_at: updatedAt } = doc._source; + + let namespaces: string[] = []; + if (!registry.isNamespaceAgnostic(type)) { + namespaces = doc._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(doc._source.namespace), + ]; + } + + return { + id, + type, + namespaces, + ...(originId && { originId }), + ...(updatedAt && { updated_at: updatedAt }), + version: encodeHitVersion(doc), + attributes: doc._source[type], + references: doc._source.references || [], + migrationVersion: doc._source.migrationVersion, + }; +} + /** * Ensure that a namespace is always in its namespace ID representation. * This allows `'default'` to be used interchangeably with `undefined`. @@ -1761,3 +1798,18 @@ const normalizeNamespace = (namespace?: string) => { const errorContent = (error: DecoratedError) => error.output.payload; const unique = (array: string[]) => [...new Set(array)]; + +/** + * Type and type guard function for converting a possibly not existant doc to an existant doc. + */ +type GetResponseFound = opensearchtypes.GetResponse & + Required< + Pick< + opensearchtypes.GetResponse, + '_primary_term' | '_seq_no' | '_version' | '_source' + > + >; + +const isFoundGetResponse = ( + doc: opensearchtypes.GetResponse +): doc is GetResponseFound => doc.found; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.test.ts index 10038d532143..afef66e5a2f7 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.test.ts @@ -74,7 +74,7 @@ const MAPPINGS = { }, }, }, -}; +} as const; describe('searchDsl/getSortParams', () => { describe('type, no sortField', () => { diff --git a/src/core/server/saved_objects/version/encode_hit_version.ts b/src/core/server/saved_objects/version/encode_hit_version.ts index 4de5fc4dc99a..f82df69a8153 100644 --- a/src/core/server/saved_objects/version/encode_hit_version.ts +++ b/src/core/server/saved_objects/version/encode_hit_version.ts @@ -31,6 +31,6 @@ import { encodeVersion } from './encode_version'; * Helper for encoding a version from a "hit" (hits.hits[#] from _search) or * "doc" (body from GET, update, etc) object */ -export function encodeHitVersion(response: { _seq_no: number; _primary_term: number }) { +export function encodeHitVersion(response: { _seq_no?: number; _primary_term?: number }) { return encodeVersion(response._seq_no, response._primary_term); } diff --git a/src/core/server/saved_objects/version/encode_version.ts b/src/core/server/saved_objects/version/encode_version.ts index cb9a232dd8f7..c56fd6c9d818 100644 --- a/src/core/server/saved_objects/version/encode_version.ts +++ b/src/core/server/saved_objects/version/encode_version.ts @@ -32,7 +32,7 @@ import { encodeBase64 } from './base64'; * that can be used in the saved object API in place of numeric * version numbers */ -export function encodeVersion(seqNo: number, primaryTerm: number) { +export function encodeVersion(seqNo?: number, primaryTerm?: number) { if (!Number.isInteger(primaryTerm)) { throw new TypeError('_primary_term from opensearch must be an integer'); } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ea28b2e51648..6061f78557b5 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2060,38 +2060,6 @@ export interface SavedObjectsClientWrapperOptions { typeRegistry: ISavedObjectTypeRegistry; } -// @public -export interface SavedObjectsComplexFieldMapping { - // (undocumented) - doc_values?: boolean; - dynamic?: false | 'strict'; - // (undocumented) - enabled?: boolean; - // (undocumented) - properties: SavedObjectsMappingProperties; - // (undocumented) - type?: string; -} - -// @public -export interface SavedObjectsCoreFieldMapping { - // (undocumented) - doc_values?: boolean; - // (undocumented) - fields?: { - [subfield: string]: { - type: string; - ignore_above?: number; - }; - }; - // (undocumented) - index?: boolean; - // (undocumented) - null_value?: number | boolean | string; - // (undocumented) - type: string; -} - // @public (undocumented) export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { id?: string; @@ -2210,7 +2178,9 @@ export interface SavedObjectsExportResultDetails { } // @public -export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping; +export type SavedObjectsFieldMapping = opensearchtypes.MappingProperty & { + dynamic?: false | 'strict'; +}; // @public (undocumented) export interface SavedObjectsFindOptions {