-
Notifications
You must be signed in to change notification settings - Fork 885
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MD] Feature test connection #2973
Changes from all commits
01e296d
7d28759
7c0b266
770aa5f
ee1509b
77b0eb1
dad1f8d
1bcad92
c3be392
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ import { | |
} from '../../common/data_sources'; | ||
import { DataSourcePluginConfigType } from '../../config'; | ||
import { CryptographyServiceSetup } from '../cryptography_service'; | ||
import { createDataSourceError, DataSourceError } from '../lib/error'; | ||
import { createDataSourceError } from '../lib/error'; | ||
import { DataSourceClientParams } from '../types'; | ||
import { parseClientOptions } from './client_config'; | ||
import { OpenSearchClientPoolSetup } from './client_pool'; | ||
|
@@ -25,8 +25,8 @@ export const configureClient = async ( | |
logger: Logger | ||
): Promise<Client> => { | ||
try { | ||
const dataSource = await getDataSource(dataSourceId, savedObjects); | ||
const rootClient = getRootClient(dataSource.attributes, config, openSearchClientPoolSetup); | ||
const { attributes: dataSource } = await getDataSource(dataSourceId, savedObjects); | ||
const rootClient = getRootClient(dataSource, config, openSearchClientPoolSetup); | ||
|
||
return await getQueryClient(rootClient, dataSource, cryptography); | ||
} catch (error: any) { | ||
|
@@ -37,6 +37,43 @@ export const configureClient = async ( | |
} | ||
}; | ||
|
||
export const configureTestClient = async ( | ||
{ savedObjects, cryptography }: DataSourceClientParams, | ||
dataSource: DataSourceAttributes, | ||
openSearchClientPoolSetup: OpenSearchClientPoolSetup, | ||
config: DataSourcePluginConfigType, | ||
logger: Logger | ||
): Promise<Client> => { | ||
try { | ||
const { | ||
id, | ||
auth: { type, credentials }, | ||
} = dataSource; | ||
let requireDecryption = false; | ||
|
||
const rootClient = getRootClient(dataSource, config, openSearchClientPoolSetup); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd still suggest to not use any of
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't have specific suggest code we could apply, I would suggest not block the PR. merge to main, but a issue to track and don't backport to release branch. |
||
|
||
if (type === AuthType.UsernamePasswordType && !credentials?.password && id) { | ||
const { attributes: fetchedDataSource } = await getDataSource(id || '', savedObjects); | ||
dataSource.auth = { | ||
type, | ||
credentials: { | ||
username: credentials?.username || '', | ||
password: fetchedDataSource.auth.credentials?.password || '', | ||
}, | ||
}; | ||
requireDecryption = true; | ||
} | ||
|
||
return getQueryClient(rootClient, dataSource, cryptography, requireDecryption); | ||
} catch (error: any) { | ||
logger.error(`Failed to get data source client for dataSource: ${dataSource}`); | ||
mpabba3003 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger.error(error); | ||
// Re-throw as DataSourceError | ||
throw createDataSourceError(error); | ||
} | ||
}; | ||
|
||
export const getDataSource = async ( | ||
dataSourceId: string, | ||
savedObjects: SavedObjectsClientContract | ||
|
@@ -45,16 +82,17 @@ export const getDataSource = async ( | |
DATA_SOURCE_SAVED_OBJECT_TYPE, | ||
dataSourceId | ||
); | ||
|
||
return dataSource; | ||
}; | ||
|
||
export const getCredential = async ( | ||
dataSource: SavedObject<DataSourceAttributes>, | ||
dataSource: DataSourceAttributes, | ||
cryptography: CryptographyServiceSetup | ||
): Promise<UsernamePasswordTypedContent> => { | ||
const { endpoint } = dataSource.attributes!; | ||
const { endpoint } = dataSource; | ||
|
||
const { username, password } = dataSource.attributes.auth.credentials!; | ||
const { username, password } = dataSource.auth.credentials!; | ||
mpabba3003 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const { decryptedText, encryptionContext } = await cryptography | ||
.decodeAndDecrypt(password) | ||
|
@@ -87,17 +125,20 @@ export const getCredential = async ( | |
*/ | ||
const getQueryClient = async ( | ||
rootClient: Client, | ||
dataSource: SavedObject<DataSourceAttributes>, | ||
cryptography: CryptographyServiceSetup | ||
dataSource: DataSourceAttributes, | ||
cryptography?: CryptographyServiceSetup, | ||
requireDecryption: boolean = true | ||
): Promise<Client> => { | ||
const authType = dataSource.attributes.auth.type; | ||
const authType = dataSource.auth.type; | ||
|
||
switch (authType) { | ||
case AuthType.NoAuth: | ||
return rootClient.child(); | ||
|
||
case AuthType.UsernamePasswordType: | ||
const credential = await getCredential(dataSource, cryptography); | ||
const credential = requireDecryption | ||
? await getCredential(dataSource, cryptography!) | ||
: (dataSource.auth.credentials as UsernamePasswordTypedContent); | ||
return getBasicAuthClient(rootClient, credential); | ||
|
||
default: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { OpenSearchClient } from 'opensearch-dashboards/server'; | ||
import { createDataSourceError } from '../lib/error'; | ||
|
||
export class DataSourceConnectionValidator { | ||
constructor(private readonly callDataCluster: OpenSearchClient) {} | ||
|
||
async validate() { | ||
try { | ||
return await this.callDataCluster.info<OpenSearchClient>(); | ||
} catch (e) { | ||
if (e.statusCode === 403) { | ||
return true; | ||
} else { | ||
throw createDataSourceError(e); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { schema } from '@osd/config-schema'; | ||
import { IRouter, OpenSearchClient } from 'opensearch-dashboards/server'; | ||
import { DataSourceAttributes } from '../../common/data_sources'; | ||
import { DataSourceConnectionValidator } from './data_source_connection_validator'; | ||
import { DataSourceServiceSetup } from '../data_source_service'; | ||
import { CryptographyServiceSetup } from '../cryptography_service'; | ||
|
||
export const registerTestConnectionRoute = ( | ||
router: IRouter, | ||
dataSourceServiceSetup: DataSourceServiceSetup, | ||
cryptography: CryptographyServiceSetup | ||
) => { | ||
router.post( | ||
{ | ||
path: '/internal/data-source-management/validate', | ||
validate: { | ||
body: schema.object({ | ||
id: schema.string(), | ||
endpoint: schema.string(), | ||
auth: schema.maybe( | ||
schema.object({ | ||
type: schema.oneOf([schema.literal('username_password'), schema.literal('no_auth')]), | ||
credentials: schema.oneOf([ | ||
schema.object({ | ||
username: schema.string(), | ||
password: schema.string(), | ||
}), | ||
schema.literal(null), | ||
]), | ||
}) | ||
), | ||
}), | ||
}, | ||
}, | ||
async (context, request, response) => { | ||
const dataSource: DataSourceAttributes = request.body as DataSourceAttributes; | ||
|
||
const dataSourceClient: OpenSearchClient = await dataSourceServiceSetup.getTestingClient( | ||
{ | ||
dataSourceId: dataSource.id || '', | ||
savedObjects: context.core.savedObjects.client, | ||
cryptography, | ||
}, | ||
dataSource | ||
); | ||
|
||
try { | ||
const dsValidator = new DataSourceConnectionValidator(dataSourceClient); | ||
|
||
await dsValidator.validate(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we close the client using |
||
|
||
return response.ok({ | ||
body: { | ||
success: true, | ||
}, | ||
}); | ||
} catch (err) { | ||
return response.customError({ | ||
statusCode: err.statusCode || 500, | ||
body: { | ||
message: err.message, | ||
attributes: { | ||
error: err.body?.error || err.message, | ||
}, | ||
}, | ||
}); | ||
} | ||
} | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think id should be added here, as it is part of type
SavedObjectAttributes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file: test_connection.ts & line: 41 .. is the reason I added this as optional field.