From 1d7fbf31a5858889666555592549a87162e34482 Mon Sep 17 00:00:00 2001 From: Su Date: Thu, 25 Aug 2022 11:37:54 -0700 Subject: [PATCH 1/4] support legacy client for data source Signed-off-by: Su --- src/core/server/http/router/router.ts | 1 - .../opensearch/legacy/cluster_client.ts | 2 +- .../fetcher/index_patterns_fetcher.ts | 6 +- .../field_capabilities/field_capabilities.ts | 4 +- .../fetcher/lib/opensearch_api.ts | 31 +--- .../fetcher/lib/resolve_time_pattern.ts | 5 +- .../data/server/index_patterns/routes.ts | 19 +- .../data_source/server/client/client_pool.ts | 17 +- .../server/client/configure_client.test.ts | 30 ++- .../server/client/configure_client.ts | 9 +- .../data_source/server/client/index.ts | 4 +- .../server/data_source_service.test.ts | 1 + .../data_source/server/data_source_service.ts | 65 ++++--- .../server/legacy/client_config.test.ts | 28 +++ .../server/legacy/client_config.ts | 28 +++ .../configure_legacy_client.test.mocks.ts | 18 ++ .../legacy/configure_legacy_client.test.ts | 173 ++++++++++++++++++ .../server/legacy/configure_legacy_client.ts | 114 ++++++++++++ .../data_source/server/legacy/index.ts | 6 + src/plugins/data_source/server/plugin.ts | 17 +- src/plugins/data_source/server/types.ts | 31 +++- .../server/routes/resolve_index.ts | 29 +-- 22 files changed, 510 insertions(+), 128 deletions(-) create mode 100644 src/plugins/data_source/server/legacy/client_config.test.ts create mode 100644 src/plugins/data_source/server/legacy/client_config.ts create mode 100644 src/plugins/data_source/server/legacy/configure_legacy_client.test.mocks.ts create mode 100644 src/plugins/data_source/server/legacy/configure_legacy_client.test.ts create mode 100644 src/plugins/data_source/server/legacy/configure_legacy_client.ts create mode 100644 src/plugins/data_source/server/legacy/index.ts diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 752706879d0e..047639372454 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -306,7 +306,6 @@ export class Router implements IRouter { opensearchDashboardsResponseFactory.badRequest({ body: e.message }) ); } - // TODO: add legacy data source client config error handling return hapiResponseAdapter.toInternalError(); } diff --git a/src/core/server/opensearch/legacy/cluster_client.ts b/src/core/server/opensearch/legacy/cluster_client.ts index bb8884fd3952..ab8bd4f8ceb0 100644 --- a/src/core/server/opensearch/legacy/cluster_client.ts +++ b/src/core/server/opensearch/legacy/cluster_client.ts @@ -65,7 +65,7 @@ const noop = () => undefined; * OpenSearch JS client. * @param options Options that affect the way we call the API and process the result. */ -const callAPI = async ( +export const callAPI = async ( client: Client, endpoint: string, clientParams: Record = {}, diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index a53e06cdea0a..9144e505133c 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -28,7 +28,7 @@ * under the License. */ -import { LegacyAPICaller, OpenSearchClient } from 'opensearch-dashboards/server'; +import { LegacyAPICaller } from 'opensearch-dashboards/server'; import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; @@ -48,9 +48,9 @@ interface FieldSubType { } export class IndexPatternsFetcher { - private _callDataCluster: LegacyAPICaller | OpenSearchClient; + private _callDataCluster: LegacyAPICaller; - constructor(callDataCluster: LegacyAPICaller | OpenSearchClient) { + constructor(callDataCluster: LegacyAPICaller) { this._callDataCluster = callDataCluster; } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index 57d59ce5a486..0aee1222a361 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -30,7 +30,7 @@ import { defaults, keyBy, sortBy } from 'lodash'; -import { LegacyAPICaller, OpenSearchClient } from 'opensearch-dashboards/server'; +import { LegacyAPICaller } from 'opensearch-dashboards/server'; import { callFieldCapsApi } from '../opensearch_api'; import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response'; import { mergeOverrides } from './overrides'; @@ -47,7 +47,7 @@ import { FieldDescriptor } from '../../index_patterns_fetcher'; * @return {Promise>} */ export async function getFieldCapabilities( - callCluster: LegacyAPICaller | OpenSearchClient, + callCluster: LegacyAPICaller, indices: string | string[] = [], metaFields: string[] = [], fieldCapsOptions?: { allowNoIndices: boolean } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts index 5fcbb01ce07a..0a33e51b1018 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts @@ -57,23 +57,10 @@ export interface IndexAliasResponse { * @return {Promise} */ export async function callIndexAliasApi( - callCluster: LegacyAPICaller | OpenSearchClient, + callCluster: LegacyAPICaller, indices: string[] | string ): Promise { try { - // This approach of identify between OpenSearchClient vs LegacyAPICaller - // will be deprecated after support data client with legacy client - // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2133 - if ('transport' in callCluster) { - return ( - await callCluster.indices.getAlias({ - index: indices, - ignore_unavailable: true, - allow_no_indices: true, - }) - ).body as IndicesAliasResponse; - } - return (await callCluster('indices.getAlias', { index: indices, ignoreUnavailable: true, @@ -97,25 +84,11 @@ export async function callIndexAliasApi( * @return {Promise} */ export async function callFieldCapsApi( - callCluster: LegacyAPICaller | OpenSearchClient, + callCluster: LegacyAPICaller, indices: string[] | string, fieldCapsOptions: { allowNoIndices: boolean } = { allowNoIndices: false } ) { try { - // This approach of identify between OpenSearchClient vs LegacyAPICaller - // will be deprecated after support data client with legacy client - // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2133 - if ('transport' in callCluster) { - return ( - await callCluster.fieldCaps({ - index: indices, - fields: '*', - ignore_unavailable: true, - allow_no_indices: fieldCapsOptions.allowNoIndices, - }) - ).body as FieldCapsResponse; - } - return (await callCluster('fieldCaps', { index: indices, fields: '*', diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index 7b19ff78646f..c1ef0074a1d6 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -47,10 +47,7 @@ import { callIndexAliasApi, IndicesAliasResponse } from './opensearch_api'; * and the indices that actually match the time * pattern (matches); */ -export async function resolveTimePattern( - callCluster: LegacyAPICaller | OpenSearchClient, - timePattern: string -) { +export async function resolveTimePattern(callCluster: LegacyAPICaller, timePattern: string) { const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern)); const allIndexDetails = chain(aliases) diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index f41c15ccb381..3adc1970dd81 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -29,7 +29,11 @@ */ import { schema } from '@osd/config-schema'; -import { HttpServiceSetup, RequestHandlerContext } from 'opensearch-dashboards/server'; +import { + HttpServiceSetup, + LegacyAPICaller, + RequestHandlerContext, +} from 'opensearch-dashboards/server'; import { IndexPatternsFetcher } from './fetcher'; export function registerRoutes(http: HttpServiceSetup) { @@ -151,11 +155,12 @@ export function registerRoutes(http: HttpServiceSetup) { ); } -const decideClient = async (context: RequestHandlerContext, request: any) => { +const decideClient = async ( + context: RequestHandlerContext, + request: any +): Promise => { const dataSourceId = request.query.data_source; - if (dataSourceId) { - return await context.dataSource.opensearch.getClient(dataSourceId); - } - - return context.core.opensearch.legacy.client.callAsCurrentUser; + return dataSourceId + ? (context.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI as LegacyAPICaller) + : context.core.opensearch.legacy.client.callAsCurrentUser; }; diff --git a/src/plugins/data_source/server/client/client_pool.ts b/src/plugins/data_source/server/client/client_pool.ts index be1957bc769e..fe5458d8f6ca 100644 --- a/src/plugins/data_source/server/client/client_pool.ts +++ b/src/plugins/data_source/server/client/client_pool.ts @@ -4,31 +4,32 @@ */ import { Client } from '@opensearch-project/opensearch'; +import { Client as LegacyClient } from 'elasticsearch'; import LRUCache from 'lru-cache'; import { Logger } from 'src/core/server'; import { DataSourcePluginConfigType } from '../../config'; export interface OpenSearchClientPoolSetup { - getClientFromPool: (id: string) => Client | undefined; - addClientToPool: (endpoint: string, client: Client) => void; + getClientFromPool: (id: string) => Client | LegacyClient | undefined; + addClientToPool: (endpoint: string, client: Client | LegacyClient) => void; } /** - * OpenSearch client pool. + * OpenSearch client pool for data source. * * This client pool uses an LRU cache to manage OpenSearch Js client objects. * It reuse TPC connections for each OpenSearch endpoint. */ export class OpenSearchClientPool { // LRU cache - // key: data source endpoint url - // value: OpenSearch client object - private cache?: LRUCache; + // key: data source endpoint + // value: OpenSearch client object | Legacy client object + private cache?: LRUCache; private isClosed = false; constructor(private logger: Logger) {} - public async setup(config: DataSourcePluginConfigType) { + public async setup(config: DataSourcePluginConfigType): Promise { const logger = this.logger; const { size } = config.clientPool; @@ -53,7 +54,7 @@ export class OpenSearchClientPool { return this.cache!.get(endpoint); }; - const addClientToPool = (endpoint: string, client: Client) => { + const addClientToPool = (endpoint: string, client: Client | LegacyClient) => { this.cache!.set(endpoint, client); }; diff --git a/src/plugins/data_source/server/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index f8f8f2cb5802..696c7ce2daf6 100644 --- a/src/plugins/data_source/server/client/configure_client.test.ts +++ b/src/plugins/data_source/server/client/configure_client.test.ts @@ -15,6 +15,7 @@ import { ClientOptions } from '@opensearch-project/opensearch'; // eslint-disable-next-line @osd/eslint/no-restricted-paths import { opensearchClientMock } from '../../../../core/server/opensearch/client/mocks'; import { CryptographyClient } from '../cryptography'; +import { DataSourceClientParams } from '../types'; const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8'; const cryptoClient = new CryptographyClient('test', 'test', new Array(32).fill(0)); @@ -28,6 +29,7 @@ describe('configureClient', () => { let clientOptions: ClientOptions; let dataSourceAttr: DataSourceAttributes; let dsClient: ReturnType; + let dataSourceClientParams: DataSourceClientParams; beforeEach(() => { dsClient = opensearchClientMock.createInternalClient(); @@ -70,9 +72,13 @@ describe('configureClient', () => { references: [], }); - ClientMock.mockImplementation(() => { - return dsClient; - }); + dataSourceClientParams = { + dataSourceId: DATA_SOURCE_ID, + savedObjects: savedObjectsMock, + cryptographyClient: cryptoClient, + }; + + ClientMock.mockImplementation(() => dsClient); }); afterEach(() => { @@ -94,14 +100,7 @@ describe('configureClient', () => { parseClientOptionsMock.mockReturnValue(clientOptions); - const client = await configureClient( - DATA_SOURCE_ID, - savedObjectsMock, - cryptoClient, - clientPoolSetup, - config, - logger - ); + const client = await configureClient(dataSourceClientParams, clientPoolSetup, config, logger); expect(parseClientOptionsMock).toHaveBeenCalled(); expect(ClientMock).toHaveBeenCalledTimes(1); @@ -113,14 +112,7 @@ describe('configureClient', () => { test('configure client with auth.type == username_password, will first call decrypt()', async () => { const spy = jest.spyOn(cryptoClient, 'decodeAndDecrypt').mockResolvedValue('password'); - const client = await configureClient( - DATA_SOURCE_ID, - savedObjectsMock, - cryptoClient, - clientPoolSetup, - config, - logger - ); + const client = await configureClient(dataSourceClientParams, clientPoolSetup, config, logger); expect(ClientMock).toHaveBeenCalledTimes(1); expect(savedObjectsMock.get).toHaveBeenCalledTimes(1); diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index 8cfa9769de7a..d88e817780de 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -14,13 +14,12 @@ import { import { DataSourcePluginConfigType } from '../../config'; import { CryptographyClient } from '../cryptography'; import { DataSourceConfigError } from '../lib/error'; +import { DataSourceClientParams } from '../types'; import { parseClientOptions } from './client_config'; import { OpenSearchClientPoolSetup } from './client_pool'; export const configureClient = async ( - dataSourceId: string, - savedObjects: SavedObjectsClientContract, - cryptographyClient: CryptographyClient, + { dataSourceId, savedObjects, cryptographyClient }: DataSourceClientParams, openSearchClientPoolSetup: OpenSearchClientPoolSetup, config: DataSourcePluginConfigType, logger: Logger @@ -68,7 +67,7 @@ export const getCredential = async ( * * @param rootClient root client for the connection with given data source endpoint. * @param dataSource data source saved object - * @param cryptographyClient cryptography client for password encryption / decrpytion + * @param cryptographyClient cryptography client for password encryption / decryption * @returns child client. */ const getQueryClient = async ( @@ -101,7 +100,7 @@ const getRootClient = ( const endpoint = dataSourceAttr.endpoint; const cachedClient = getClientFromPool(endpoint); if (cachedClient) { - return cachedClient; + return cachedClient as Client; } else { const clientOptions = parseClientOptions(config, endpoint); diff --git a/src/plugins/data_source/server/client/index.ts b/src/plugins/data_source/server/client/index.ts index 8adc96115b91..f27848965077 100644 --- a/src/plugins/data_source/server/client/index.ts +++ b/src/plugins/data_source/server/client/index.ts @@ -3,5 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { OpenSearchClientPool } from './client_pool'; -export { configureClient } from './configure_client'; +export { OpenSearchClientPool, OpenSearchClientPoolSetup } from './client_pool'; +export { configureClient, getDataSource, getCredential } from './configure_client'; diff --git a/src/plugins/data_source/server/data_source_service.test.ts b/src/plugins/data_source/server/data_source_service.test.ts index 53dfb6f273eb..690188562360 100644 --- a/src/plugins/data_source/server/data_source_service.test.ts +++ b/src/plugins/data_source/server/data_source_service.test.ts @@ -33,6 +33,7 @@ describe('Data Source Service', () => { test('exposes proper contract', async () => { const setup = await service.setup(config); expect(setup).toHaveProperty('getDataSourceClient'); + expect(setup).toHaveProperty('getDataSourceLegacyClient'); }); }); }); diff --git a/src/plugins/data_source/server/data_source_service.ts b/src/plugins/data_source/server/data_source_service.ts index 73f61b87cb38..42932a330b06 100644 --- a/src/plugins/data_source/server/data_source_service.ts +++ b/src/plugins/data_source/server/data_source_service.ts @@ -5,47 +5,66 @@ import { Auditor, + LegacyCallAPIOptions, Logger, OpenSearchClient, - SavedObjectsClientContract, } from '../../../../src/core/server'; import { DataSourcePluginConfigType } from '../config'; import { configureClient, OpenSearchClientPool } from './client'; -import { CryptographyClient } from './cryptography'; +import { configureLegacyClient } from './legacy'; +import { DataSourceClientParams } from './types'; export interface DataSourceServiceSetup { - getDataSourceClient: ( - dataSourceId: string, - // this saved objects client is used to fetch data source on behalf of users, caller should pass scoped saved objects client - savedObjects: SavedObjectsClientContract, - cryptographyClient: CryptographyClient - ) => Promise; + getDataSourceClient: (params: DataSourceClientParams) => Promise; + + getDataSourceLegacyClient: ( + params: DataSourceClientParams + ) => { + callAPI: ( + endpoint: string, + clientParams?: Record, + options?: LegacyCallAPIOptions + ) => Promise; + }; } export class DataSourceService { private readonly openSearchClientPool: OpenSearchClientPool; + private readonly legacyClientPool: OpenSearchClientPool; + private readonly legacyLogger: Logger; constructor(private logger: Logger) { + this.legacyLogger = logger.get('legacy'); this.openSearchClientPool = new OpenSearchClientPool(logger); + this.legacyClientPool = new OpenSearchClientPool(this.legacyLogger); } - async setup(config: DataSourcePluginConfigType) { - const openSearchClientPoolSetup = await this.openSearchClientPool.setup(config); + async setup(config: DataSourcePluginConfigType): Promise { + const opensearchClientPoolSetup = await this.openSearchClientPool.setup(config); + const legacyClientPoolSetup = await this.legacyClientPool.setup(config); - const getDataSourceClient = ( - dataSourceId: string, - savedObjects: SavedObjectsClientContract, - cryptographyClient: CryptographyClient + const getDataSourceClient = async ( + params: DataSourceClientParams ): Promise => { - return configureClient( - dataSourceId, - savedObjects, - cryptographyClient, - openSearchClientPoolSetup, - config, - this.logger - ); + return configureClient(params, opensearchClientPoolSetup, config, this.logger); + }; + + const getDataSourceLegacyClient = (params: DataSourceClientParams) => { + return { + callAPI: ( + endpoint: string, + clientParams?: Record, + options?: LegacyCallAPIOptions + ) => + configureLegacyClient( + params, + { endpoint, clientParams, options }, + legacyClientPoolSetup, + config, + this.legacyLogger + ), + }; }; - return { getDataSourceClient }; + return { getDataSourceClient, getDataSourceLegacyClient }; } start() {} diff --git a/src/plugins/data_source/server/legacy/client_config.test.ts b/src/plugins/data_source/server/legacy/client_config.test.ts new file mode 100644 index 000000000000..1b21eede35bc --- /dev/null +++ b/src/plugins/data_source/server/legacy/client_config.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { DataSourcePluginConfigType } from '../../config'; +import { parseClientOptions } from './client_config'; + +const TEST_DATA_SOURCE_ENDPOINT = 'http://datasource.com'; + +const config = { + enabled: true, + clientPool: { + size: 5, + }, +} as DataSourcePluginConfigType; + +describe('parseClientOptions', () => { + test('include the ssl client configs as defaults', () => { + expect(parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT)).toEqual( + expect.objectContaining({ + host: TEST_DATA_SOURCE_ENDPOINT, + ssl: { + rejectUnauthorized: true, + }, + }) + ); + }); +}); diff --git a/src/plugins/data_source/server/legacy/client_config.ts b/src/plugins/data_source/server/legacy/client_config.ts new file mode 100644 index 000000000000..d9b1cc704e3a --- /dev/null +++ b/src/plugins/data_source/server/legacy/client_config.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ConfigOptions } from 'elasticsearch'; +import { DataSourcePluginConfigType } from '../../config'; + +/** + * Parse the client options from given data source config and endpoint + * + * @param config The config to generate the client options from. + * @param endpoint endpoint url of data source + */ +export function parseClientOptions( + // TODO: will use client configs, that comes from a merge result of user config and default legacy client config, + config: DataSourcePluginConfigType, + endpoint: string +): ConfigOptions { + const configOptions: ConfigOptions = { + host: endpoint, + ssl: { + rejectUnauthorized: true, + }, + }; + + return configOptions; +} diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.test.mocks.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.test.mocks.ts new file mode 100644 index 000000000000..e6c1b3363896 --- /dev/null +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.test.mocks.ts @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const ClientMock = jest.fn(); +jest.doMock('elasticsearch', () => { + const actual = jest.requireActual('elasticsearch'); + return { + ...actual, + Client: ClientMock, + }; +}); + +export const parseClientOptionsMock = jest.fn(); +jest.doMock('./client_config', () => ({ + parseClientOptions: parseClientOptionsMock, +})); diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts new file mode 100644 index 000000000000..752487741f10 --- /dev/null +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.test.ts @@ -0,0 +1,173 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsClientContract } from '../../../../core/server'; +import { loggingSystemMock, savedObjectsClientMock } from '../../../../core/server/mocks'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common'; +import { AuthType, DataSourceAttributes } from '../../common/data_sources'; +import { DataSourcePluginConfigType } from '../../config'; +import { CryptographyClient } from '../cryptography'; +import { DataSourceClientParams, LegacyClientCallAPIParams } from '../types'; +import { OpenSearchClientPoolSetup } from '../client'; +import { ConfigOptions } from 'elasticsearch'; +import { ClientMock, parseClientOptionsMock } from './configure_legacy_client.test.mocks'; +import { configureLegacyClient } from './configure_legacy_client'; + +const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8'; +const cryptographyClient = new CryptographyClient('test', 'test', new Array(32).fill(0)); + +// TODO: improve UT +describe('configureLegacyClient', () => { + let logger: ReturnType; + let config: DataSourcePluginConfigType; + let savedObjectsMock: jest.Mocked; + let clientPoolSetup: OpenSearchClientPoolSetup; + let configOptions: ConfigOptions; + let dataSourceAttr: DataSourceAttributes; + + let mockOpenSearchClientInstance: { + close: jest.Mock; + ping: jest.Mock; + }; + let dataSourceClientParams: DataSourceClientParams; + let callApiParams: LegacyClientCallAPIParams; + let decodeAndDecryptSpy: jest.SpyInstance, [encrypted: string]>; + + const mockResponse = { data: 'ping' }; + + beforeEach(() => { + mockOpenSearchClientInstance = { + close: jest.fn(), + ping: jest.fn(), + }; + logger = loggingSystemMock.createLogger(); + savedObjectsMock = savedObjectsClientMock.create(); + config = { + enabled: true, + clientPool: { + size: 5, + }, + } as DataSourcePluginConfigType; + + configOptions = { + host: 'http://localhost', + ssl: { + rejectUnauthorized: true, + }, + } as ConfigOptions; + + dataSourceAttr = { + title: 'title', + endpoint: 'http://localhost', + auth: { + type: AuthType.UsernamePasswordType, + credentials: { + username: 'username', + password: 'password', + }, + }, + } as DataSourceAttributes; + + clientPoolSetup = { + getClientFromPool: jest.fn(), + addClientToPool: jest.fn(), + }; + + callApiParams = { + endpoint: 'ping', + }; + + savedObjectsMock.get.mockResolvedValueOnce({ + id: DATA_SOURCE_ID, + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + attributes: dataSourceAttr, + references: [], + }); + + dataSourceClientParams = { + dataSourceId: DATA_SOURCE_ID, + savedObjects: savedObjectsMock, + cryptographyClient, + }; + + ClientMock.mockImplementation(() => mockOpenSearchClientInstance); + + mockOpenSearchClientInstance.ping.mockImplementation(function mockCall(this: any) { + return Promise.resolve({ + context: this, + response: mockResponse, + }); + }); + + decodeAndDecryptSpy = jest + .spyOn(cryptographyClient, 'decodeAndDecrypt') + .mockResolvedValue('password'); + }); + + afterEach(() => { + ClientMock.mockReset(); + jest.resetAllMocks(); + }); + + test('configure client with auth.type == no_auth, will call new Client() to create client', async () => { + savedObjectsMock.get.mockReset().mockResolvedValueOnce({ + id: DATA_SOURCE_ID, + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + attributes: { + ...dataSourceAttr, + auth: { + type: AuthType.NoAuth, + }, + }, + references: [], + }); + + parseClientOptionsMock.mockReturnValue(configOptions); + + await configureLegacyClient( + dataSourceClientParams, + callApiParams, + clientPoolSetup, + config, + logger + ); + + expect(parseClientOptionsMock).toHaveBeenCalled(); + expect(ClientMock).toHaveBeenCalledTimes(1); + expect(ClientMock).toHaveBeenCalledWith(configOptions); + expect(savedObjectsMock.get).toHaveBeenCalledTimes(1); + }); + + test('configure client with auth.type == no_auth, will first call decrypt()', async () => { + const mockResult = await configureLegacyClient( + dataSourceClientParams, + callApiParams, + clientPoolSetup, + config, + logger + ); + + expect(ClientMock).toHaveBeenCalledTimes(1); + expect(savedObjectsMock.get).toHaveBeenCalledTimes(1); + expect(decodeAndDecryptSpy).toHaveBeenCalledTimes(1); + expect(mockResult).toBeDefined(); + }); + + test('correctly called with endpoint and params', async () => { + const mockParams = { param: 'ping' }; + const mockResult = await configureLegacyClient( + dataSourceClientParams, + { ...callApiParams, clientParams: mockParams }, + clientPoolSetup, + config, + logger + ); + + expect(mockResult.response).toBe(mockResponse); + expect(mockResult.context).toBe(mockOpenSearchClientInstance); + expect(mockOpenSearchClientInstance.ping).toHaveBeenCalledTimes(1); + expect(mockOpenSearchClientInstance.ping).toHaveBeenLastCalledWith(mockParams); + }); +}); diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.ts new file mode 100644 index 000000000000..84514c85b048 --- /dev/null +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.ts @@ -0,0 +1,114 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Client } from 'elasticsearch'; +// eslint-disable-next-line @osd/eslint/no-restricted-paths +import { callAPI } from '../../../../../src/core/server/opensearch/legacy/cluster_client'; +import { Headers, LegacyAPICaller, Logger, SavedObject } from '../../../../../src/core/server'; +import { + AuthType, + DataSourceAttributes, + UsernamePasswordTypedContent, +} from '../../common/data_sources'; +import { DataSourcePluginConfigType } from '../../config'; +import { CryptographyClient } from '../cryptography'; +import { DataSourceClientParams, LegacyClientCallAPIParams } from '../types'; +import { OpenSearchClientPoolSetup, getCredential, getDataSource } from '../client'; +import { parseClientOptions } from './client_config'; +import { DataSourceConfigError } from '../lib/error'; + +export const configureLegacyClient = async ( + { dataSourceId, savedObjects, cryptographyClient }: DataSourceClientParams, + callApiParams: LegacyClientCallAPIParams, + openSearchClientPoolSetup: OpenSearchClientPoolSetup, + config: DataSourcePluginConfigType, + logger: Logger +) => { + try { + const dataSource = await getDataSource(dataSourceId, savedObjects); + const rootClient = getRootClient(dataSource.attributes, config, openSearchClientPoolSetup); + + return await getQueryClient(rootClient, dataSource, cryptographyClient, callApiParams); + } catch (error: any) { + logger.error(`Fail to get data source client for dataSourceId: [${dataSourceId}]`); + logger.error(error); + // Re-throw as DataSourceConfigError + throw new DataSourceConfigError('Fail to get data source client: ', error); + } +}; + +/** + * Create a child client object with given auth info. + * + * @param rootClient root client for the connection with given data source endpoint. + * @param dataSource data source saved object + * @param cryptographyClient cryptography client for password encryption / decryption + * @returns child client. + */ +const getQueryClient = async ( + rootClient: Client, + dataSource: SavedObject, + cryptographyClient: CryptographyClient, + callApiParams: LegacyClientCallAPIParams +) => { + if (AuthType.NoAuth === dataSource.attributes.auth.type) { + return legacyClientWrapper(rootClient, callApiParams); + } else { + const credential = await getCredential(dataSource, cryptographyClient); + return legacyClientWrapper(rootClient, callApiParams, credential); + } +}; + +/** + * Gets a root client object of the OpenSearch endpoint. + * Will attempt to get from cache, if cache miss, create a new one and load into cache. + * + * @param dataSourceAttr data source saved objects attributes. + * @param config data source config + * @returns OpenSearch client for the given data source endpoint. + */ +const getRootClient = ( + dataSourceAttr: DataSourceAttributes, + config: DataSourcePluginConfigType, + { getClientFromPool, addClientToPool }: OpenSearchClientPoolSetup +): Client => { + const endpoint = dataSourceAttr.endpoint; + const cachedClient = getClientFromPool(endpoint); + if (cachedClient) { + return cachedClient as Client; + } else { + const configOptions = parseClientOptions(config, endpoint); + const client = new Client(configOptions); + addClientToPool(endpoint, client); + + return client; + } +}; + +/** + * Wrapper to expose API that allow calling the OpenSearch API endpoint with the specified + * parameters, using legacy client. + * + * @param client Raw OpenSearch JS client instance to use. + * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. + * @param clientParams - A dictionary of parameters that will be passed directly to the OpenSearch JS client. + * @param options - Options that affect the way we call the API and process the result. + * @param credential - Decrypted credential content + */ +const legacyClientWrapper = async ( + rootClient: Client, + { endpoint, clientParams = {}, options }: LegacyClientCallAPIParams, + credential?: UsernamePasswordTypedContent +) => { + if (credential) { + const headers: Headers = { + authorization: + 'Basic ' + Buffer.from(`${credential.username}:${credential.password}`).toString('base64'), + }; + clientParams.headers = Object.assign({}, clientParams.headers, headers); + } + + return await (callAPI.bind(null, rootClient) as LegacyAPICaller)(endpoint, clientParams, options); +}; diff --git a/src/plugins/data_source/server/legacy/index.ts b/src/plugins/data_source/server/legacy/index.ts new file mode 100644 index 000000000000..b7c8d7a26ad7 --- /dev/null +++ b/src/plugins/data_source/server/legacy/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { configureLegacyClient } from './configure_legacy_client'; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index b0b6718ae11b..c166fb736768 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -131,11 +131,20 @@ export class DataSourcePlugin implements Plugin auditTrail.asScoped(req)); this.logAuditMessage(auditor, dataSourceId, req); - return dataSourceService.getDataSourceClient( + return dataSourceService.getDataSourceClient({ dataSourceId, - context.core.savedObjects.client, - cryptographyClient - ); + savedObjects: context.core.savedObjects.client, + cryptographyClient, + }); + }, + legacy: { + getClient: (dataSourceId: string) => { + return dataSourceService.getDataSourceLegacyClient({ + dataSourceId, + savedObjects: context.core.savedObjects.client, + cryptographyClient, + }); + }, }, }, }; diff --git a/src/plugins/data_source/server/types.ts b/src/plugins/data_source/server/types.ts index bad309b4b871..97909366ab54 100644 --- a/src/plugins/data_source/server/types.ts +++ b/src/plugins/data_source/server/types.ts @@ -3,11 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { OpenSearchClient } from 'src/core/server'; +import { + LegacyCallAPIOptions, + OpenSearchClient, + SavedObjectsClientContract, +} from 'src/core/server'; +import { CryptographyClient } from './cryptography'; + +export interface LegacyClientCallAPIParams { + endpoint: string; + clientParams?: Record; + options?: LegacyCallAPIOptions; +} + +export interface DataSourceClientParams { + dataSourceId: string; + // this saved objects client is used to fetch data source on behalf of users, caller should pass scoped saved objects client + savedObjects: SavedObjectsClientContract; + cryptographyClient: CryptographyClient; +} export interface DataSourcePluginRequestContext { opensearch: { getClient: (dataSourceId: string) => Promise; + legacy: { + getClient: ( + dataSourceId: string + ) => { + callAPI: ( + endpoint: string, + clientParams: Record, + options?: LegacyCallAPIOptions + ) => Promise; + }; + }; }; } declare module 'src/core/server' { diff --git a/src/plugins/index_pattern_management/server/routes/resolve_index.ts b/src/plugins/index_pattern_management/server/routes/resolve_index.ts index 510eeb367da8..baf19ca5b7d0 100644 --- a/src/plugins/index_pattern_management/server/routes/resolve_index.ts +++ b/src/plugins/index_pattern_management/server/routes/resolve_index.ts @@ -29,7 +29,7 @@ */ import { schema } from '@osd/config-schema'; -import { IRouter } from 'src/core/server'; +import { IRouter, LegacyAPICaller } from 'src/core/server'; export function registerResolveIndexRoute(router: IRouter): void { router.get( @@ -59,25 +59,16 @@ export function registerResolveIndexRoute(router: IRouter): void { : null; const dataSourceId = req.query.data_source; - if (dataSourceId) { - const result = await ( - await context.dataSource.opensearch.getClient(dataSourceId) - ).indices.resolveIndex({ - name: encodeURIComponent(req.params.query), - expand_wildcards: req.query.expand_wildcards, - }); - return res.ok({ body: result.body }); - } + const caller = dataSourceId + ? context.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI + : context.core.opensearch.legacy.client.callAsCurrentUser; - const result = await context.core.opensearch.legacy.client.callAsCurrentUser( - 'transport.request', - { - method: 'GET', - path: `/_resolve/index/${encodeURIComponent(req.params.query)}${ - queryString ? '?' + new URLSearchParams(queryString).toString() : '' - }`, - } - ); + const result = await caller('transport.request', { + method: 'GET', + path: `/_resolve/index/${encodeURIComponent(req.params.query)}${ + queryString ? '?' + new URLSearchParams(queryString).toString() : '' + }`, + }); return res.ok({ body: result }); } ); From 30cd218246f8f83aa70489b3a82348ef02fd7fb8 Mon Sep 17 00:00:00 2001 From: Su Date: Mon, 26 Sep 2022 17:00:31 -0700 Subject: [PATCH 2/4] not wrap 401 error for data source client Signed-off-by: Su --- CHANGELOG.md | 40 +++++++------- .../opensearch/legacy/cluster_client.ts | 2 +- .../server/legacy/configure_legacy_client.ts | 54 +++++++++++++++++-- src/plugins/data_source/server/lib/error.ts | 4 +- 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d6987c59ac..4508fceda05c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # CHANGELOG + Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] + ### 💥 Breaking Changes ### Deprecations @@ -10,11 +12,13 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 📈 Features/Enhancements +- [MD] Support legacy client for data source ([#2204](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2204)) + ### 🐛 Bug Fixes ### 🚞 Infrastructure -* Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) +- Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) ### 📝 Documentation @@ -25,48 +29,48 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🔩 Tests ## [2.x] + ### 💥 Breaking Changes ### Deprecations ### 🛡 Security -* Use a forced CSP-compliant interpreter with Vega visualizations ([#2352](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2352)) -* Bump moment-timezone from 0.5.34 to 0.5.37 ([#2361](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2361)) -* [CVE-2022-33987] Upgrade geckodriver to 3.0.2 ([#2166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2166)) +- Use a forced CSP-compliant interpreter with Vega visualizations ([#2352](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2352)) +- Bump moment-timezone from 0.5.34 to 0.5.37 ([#2361](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2361)) +- [CVE-2022-33987] Upgrade geckodriver to 3.0.2 ([#2166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2166)) ### 📈 Features/Enhancements -* Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) +- Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) ### 🐛 Bug Fixes -* [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309)) -* [Viz Builder] Add index pattern info when loading embeddable ([#2363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2363)) -* Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) +- [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309)) +- [Viz Builder] Add index pattern info when loading embeddable ([#2363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2363)) +- Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) ### 🚞 Infrastructure -* Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) -* Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) - +- Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) +- Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) ### 📝 Documentation -* README.md for saving index pattern relationship ([#2276](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2276)) -* Remove extra typo from README. ([#2403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2403)) +- README.md for saving index pattern relationship ([#2276](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2276)) +- Remove extra typo from README. ([#2403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2403)) ### 🛠 Maintenance -* Increment from 2.3 to 2.4. ([#2295](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2295)) -* Adding @zengyan-amazon as maintainer ([#2419](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2419)) -* Updating @tmarkley to Emeritus status. ([#2423](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2423)) +- Increment from 2.3 to 2.4. ([#2295](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2295)) +- Adding @zengyan-amazon as maintainer ([#2419](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2419)) +- Updating @tmarkley to Emeritus status. ([#2423](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2423)) ### 🪛 Refactoring ### 🔩 Tests -* Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) +- Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) -[Unreleased]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...HEAD +[unreleased]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...HEAD [2.x]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...2.x diff --git a/src/core/server/opensearch/legacy/cluster_client.ts b/src/core/server/opensearch/legacy/cluster_client.ts index ab8bd4f8ceb0..bb8884fd3952 100644 --- a/src/core/server/opensearch/legacy/cluster_client.ts +++ b/src/core/server/opensearch/legacy/cluster_client.ts @@ -65,7 +65,7 @@ const noop = () => undefined; * OpenSearch JS client. * @param options Options that affect the way we call the API and process the result. */ -export const callAPI = async ( +const callAPI = async ( client: Client, endpoint: string, clientParams: Record = {}, diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.ts index 84514c85b048..63ca6c714744 100644 --- a/src/plugins/data_source/server/legacy/configure_legacy_client.ts +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.ts @@ -4,9 +4,15 @@ */ import { Client } from 'elasticsearch'; -// eslint-disable-next-line @osd/eslint/no-restricted-paths -import { callAPI } from '../../../../../src/core/server/opensearch/legacy/cluster_client'; -import { Headers, LegacyAPICaller, Logger, SavedObject } from '../../../../../src/core/server'; +import { get } from 'lodash'; +import { + Headers, + LegacyAPICaller, + LegacyCallAPIOptions, + LegacyOpenSearchErrorHelpers, + Logger, + SavedObject, +} from '../../../../../src/core/server'; import { AuthType, DataSourceAttributes, @@ -87,6 +93,48 @@ const getRootClient = ( } }; +/** + * Calls the OpenSearch API endpoint with the specified parameters. + * @param client Raw OpenSearch JS client instance to use. + * @param endpoint Name of the API endpoint to call. + * @param clientParams Parameters that will be directly passed to the + * OpenSearch JS client. + * @param options Options that affect the way we call the API and process the result. + * make wrap401Errors default to false, because we don't want browser login pop-up + */ +const callAPI = async ( + client: Client, + endpoint: string, + clientParams: Record = {}, + options: LegacyCallAPIOptions = { wrap401Errors: false } +) => { + const clientPath = endpoint.split('.'); + const api: any = get(client, clientPath); + if (!api) { + throw new Error(`called with an invalid endpoint: ${endpoint}`); + } + + const apiContext = clientPath.length === 1 ? client : get(client, clientPath.slice(0, -1)); + try { + return await new Promise((resolve, reject) => { + const request = api.call(apiContext, clientParams); + if (options.signal) { + options.signal.addEventListener('abort', () => { + request.abort(); + reject(new Error('Request was aborted')); + }); + } + return request.then(resolve, reject); + }); + } catch (err) { + if (!options.wrap401Errors || err.statusCode !== 401) { + throw err; + } + + throw LegacyOpenSearchErrorHelpers.decorateNotAuthorizedError(err); + } +}; + /** * Wrapper to expose API that allow calling the OpenSearch API endpoint with the specified * parameters, using legacy client. diff --git a/src/plugins/data_source/server/lib/error.ts b/src/plugins/data_source/server/lib/error.ts index e921a8d8043f..71830a440c9f 100644 --- a/src/plugins/data_source/server/lib/error.ts +++ b/src/plugins/data_source/server/lib/error.ts @@ -14,8 +14,8 @@ export class DataSourceConfigError extends OsdError { ? error.output.payload.message : error.message; super(messagePrefix + messageContent); - // Cast all 5xx error returned by saveObjectClient to 500, 400 for both savedObject client - // 4xx errors, and other errors + // Cast all 5xx error returned by saveObjectClient to 500. + // Case both savedObject client 4xx errors, and other errors to 400 this.statusCode = SavedObjectsErrorHelpers.isOpenSearchUnavailableError(error) ? 500 : 400; } } From d26ac47ba36fbcfcc8f80d5655d4731345b41b47 Mon Sep 17 00:00:00 2001 From: Su Date: Wed, 28 Sep 2022 12:24:42 -0700 Subject: [PATCH 3/4] address comments Signed-off-by: Su --- .../server/client/configure_client.ts | 16 ++++-- .../server/legacy/configure_legacy_client.ts | 55 ++++++++++--------- src/plugins/data_source/server/lib/error.ts | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index d88e817780de..febac85070ea 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -75,12 +75,18 @@ const getQueryClient = async ( dataSource: SavedObject, cryptographyClient: CryptographyClient ): Promise => { - if (AuthType.NoAuth === dataSource.attributes.auth.type) { - return rootClient.child(); - } else { - const credential = await getCredential(dataSource, cryptographyClient); + const authType = dataSource.attributes.auth.type; + + switch (authType) { + case AuthType.NoAuth: + return rootClient.child(); + + case AuthType.UsernamePasswordType: + const credential = await getCredential(dataSource, cryptographyClient); + return getBasicAuthClient(rootClient, credential); - return getBasicAuthClient(rootClient, credential); + default: + throw Error(`${authType} is not a supported auth type for data source`); } }; diff --git a/src/plugins/data_source/server/legacy/configure_legacy_client.ts b/src/plugins/data_source/server/legacy/configure_legacy_client.ts index 63ca6c714744..0f2a63287e14 100644 --- a/src/plugins/data_source/server/legacy/configure_legacy_client.ts +++ b/src/plugins/data_source/server/legacy/configure_legacy_client.ts @@ -46,7 +46,7 @@ export const configureLegacyClient = async ( }; /** - * Create a child client object with given auth info. + * With given auth info, wrap the rootClient and return * * @param rootClient root client for the connection with given data source endpoint. * @param dataSource data source saved object @@ -57,13 +57,23 @@ const getQueryClient = async ( rootClient: Client, dataSource: SavedObject, cryptographyClient: CryptographyClient, - callApiParams: LegacyClientCallAPIParams + { endpoint, clientParams, options }: LegacyClientCallAPIParams ) => { - if (AuthType.NoAuth === dataSource.attributes.auth.type) { - return legacyClientWrapper(rootClient, callApiParams); - } else { - const credential = await getCredential(dataSource, cryptographyClient); - return legacyClientWrapper(rootClient, callApiParams, credential); + const authType = dataSource.attributes.auth.type; + + switch (authType) { + case AuthType.NoAuth: + return await (callAPI.bind(null, rootClient) as LegacyAPICaller)( + endpoint, + clientParams, + options + ); + case AuthType.UsernamePasswordType: + const credential = await getCredential(dataSource, cryptographyClient); + return getBasicAuthClient(rootClient, { endpoint, clientParams, options }, credential); + + default: + throw Error(`${authType} is not a supported auth type for data source`); } }; @@ -73,7 +83,7 @@ const getQueryClient = async ( * * @param dataSourceAttr data source saved objects attributes. * @param config data source config - * @returns OpenSearch client for the given data source endpoint. + * @returns Legacy client for the given data source endpoint. */ const getRootClient = ( dataSourceAttr: DataSourceAttributes, @@ -95,12 +105,12 @@ const getRootClient = ( /** * Calls the OpenSearch API endpoint with the specified parameters. - * @param client Raw OpenSearch JS client instance to use. + * @param client Raw legacy JS client instance to use. * @param endpoint Name of the API endpoint to call. * @param clientParams Parameters that will be directly passed to the - * OpenSearch JS client. + * legacy JS client. * @param options Options that affect the way we call the API and process the result. - * make wrap401Errors default to false, because we don't want browser login pop-up + * make wrap401Errors default to false, because we don't want login pop-up from browser */ const callAPI = async ( client: Client, @@ -136,27 +146,22 @@ const callAPI = async ( }; /** - * Wrapper to expose API that allow calling the OpenSearch API endpoint with the specified - * parameters, using legacy client. + * Get a legacy client that configured with basic auth * - * @param client Raw OpenSearch JS client instance to use. + * @param rootClient Raw legacy client instance to use. * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. - * @param clientParams - A dictionary of parameters that will be passed directly to the OpenSearch JS client. + * @param clientParams - A dictionary of parameters that will be passed directly to the legacy JS client. * @param options - Options that affect the way we call the API and process the result. - * @param credential - Decrypted credential content */ -const legacyClientWrapper = async ( +const getBasicAuthClient = async ( rootClient: Client, { endpoint, clientParams = {}, options }: LegacyClientCallAPIParams, - credential?: UsernamePasswordTypedContent + { username, password }: UsernamePasswordTypedContent ) => { - if (credential) { - const headers: Headers = { - authorization: - 'Basic ' + Buffer.from(`${credential.username}:${credential.password}`).toString('base64'), - }; - clientParams.headers = Object.assign({}, clientParams.headers, headers); - } + const headers: Headers = { + authorization: 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + }; + clientParams.headers = Object.assign({}, clientParams.headers, headers); return await (callAPI.bind(null, rootClient) as LegacyAPICaller)(endpoint, clientParams, options); }; diff --git a/src/plugins/data_source/server/lib/error.ts b/src/plugins/data_source/server/lib/error.ts index 71830a440c9f..6667b41992f3 100644 --- a/src/plugins/data_source/server/lib/error.ts +++ b/src/plugins/data_source/server/lib/error.ts @@ -15,7 +15,7 @@ export class DataSourceConfigError extends OsdError { : error.message; super(messagePrefix + messageContent); // Cast all 5xx error returned by saveObjectClient to 500. - // Case both savedObject client 4xx errors, and other errors to 400 + // Cast both savedObject client 4xx errors, and other errors to 400 this.statusCode = SavedObjectsErrorHelpers.isOpenSearchUnavailableError(error) ? 500 : 400; } } From a26db06cfcfc8d640764dfe7447787230fde64ef Mon Sep 17 00:00:00 2001 From: Su Date: Wed, 28 Sep 2022 16:10:05 -0700 Subject: [PATCH 4/4] adress more comments Signed-off-by: Su --- CHANGELOG.md | 40 +++++++++---------- .../data_source/server/data_source_service.ts | 2 +- src/plugins/data_source/server/types.ts | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4508fceda05c..af1cc60d5ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,7 @@ # CHANGELOG - Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] - ### 💥 Breaking Changes ### Deprecations @@ -12,13 +10,13 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 📈 Features/Enhancements -- [MD] Support legacy client for data source ([#2204](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2204)) +* [MD] Support legacy client for data source ([#2204](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2204)) ### 🐛 Bug Fixes ### 🚞 Infrastructure -- Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) +* Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) ### 📝 Documentation @@ -29,48 +27,48 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🔩 Tests ## [2.x] - ### 💥 Breaking Changes ### Deprecations ### 🛡 Security -- Use a forced CSP-compliant interpreter with Vega visualizations ([#2352](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2352)) -- Bump moment-timezone from 0.5.34 to 0.5.37 ([#2361](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2361)) -- [CVE-2022-33987] Upgrade geckodriver to 3.0.2 ([#2166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2166)) +* Use a forced CSP-compliant interpreter with Vega visualizations ([#2352](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2352)) +* Bump moment-timezone from 0.5.34 to 0.5.37 ([#2361](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2361)) +* [CVE-2022-33987] Upgrade geckodriver to 3.0.2 ([#2166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2166)) ### 📈 Features/Enhancements -- Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) +* Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) ### 🐛 Bug Fixes -- [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309)) -- [Viz Builder] Add index pattern info when loading embeddable ([#2363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2363)) -- Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) +* [Viz Builder] Fixes time series for new chart types ([#2309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2309)) +* [Viz Builder] Add index pattern info when loading embeddable ([#2363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2363)) +* Fixes management app breadcrumb error ([#2344](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2344)) ### 🚞 Infrastructure -- Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) -- Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) +* Add path ignore for markdown files for CI ([#2312](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2312)) +* Updating WS scans to ignore BWC artifacts in `cypress` ([#2408](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2408)) + ### 📝 Documentation -- README.md for saving index pattern relationship ([#2276](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2276)) -- Remove extra typo from README. ([#2403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2403)) +* README.md for saving index pattern relationship ([#2276](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2276)) +* Remove extra typo from README. ([#2403](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2403)) ### 🛠 Maintenance -- Increment from 2.3 to 2.4. ([#2295](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2295)) -- Adding @zengyan-amazon as maintainer ([#2419](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2419)) -- Updating @tmarkley to Emeritus status. ([#2423](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2423)) +* Increment from 2.3 to 2.4. ([#2295](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2295)) +* Adding @zengyan-amazon as maintainer ([#2419](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2419)) +* Updating @tmarkley to Emeritus status. ([#2423](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2423)) ### 🪛 Refactoring ### 🔩 Tests -- Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) +* Update caniuse to fix failed integration tests ([#2322](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2322)) -[unreleased]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...HEAD +[Unreleased]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...HEAD [2.x]: https://github.com/opensearch-project/OpenSearch-Dashboards/compare/2.3.0...2.x diff --git a/src/plugins/data_source/server/data_source_service.ts b/src/plugins/data_source/server/data_source_service.ts index 42932a330b06..8466bb7e914b 100644 --- a/src/plugins/data_source/server/data_source_service.ts +++ b/src/plugins/data_source/server/data_source_service.ts @@ -23,7 +23,7 @@ export interface DataSourceServiceSetup { endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions - ) => Promise; + ) => Promise; }; } export class DataSourceService { diff --git a/src/plugins/data_source/server/types.ts b/src/plugins/data_source/server/types.ts index 97909366ab54..2f20363b4b2d 100644 --- a/src/plugins/data_source/server/types.ts +++ b/src/plugins/data_source/server/types.ts @@ -34,7 +34,7 @@ export interface DataSourcePluginRequestContext { endpoint: string, clientParams: Record, options?: LegacyCallAPIOptions - ) => Promise; + ) => Promise; }; }; };