@backstage/plugin-search-backend-module-elasticsearch 0.0.0-nightly-20260202031345 → 0.0.0-nightly-20260203031143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -5
- package/dist/auth.cjs.js +10 -0
- package/dist/auth.cjs.js.map +1 -0
- package/dist/engines/ElasticSearchAuthTransport.cjs.js +90 -0
- package/dist/engines/ElasticSearchAuthTransport.cjs.js.map +1 -0
- package/dist/engines/ElasticSearchSearchEngine.cjs.js +38 -15
- package/dist/engines/ElasticSearchSearchEngine.cjs.js.map +1 -1
- package/dist/engines/ElasticSearchSearchEngineIndexer.cjs.js +1 -1
- package/dist/engines/ElasticSearchSearchEngineIndexer.cjs.js.map +1 -1
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +110 -2
- package/dist/module.cjs.js +17 -0
- package/dist/module.cjs.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
# @backstage/plugin-search-backend-module-elasticsearch
|
|
2
2
|
|
|
3
|
-
## 0.0.0-nightly-
|
|
3
|
+
## 0.0.0-nightly-20260203031143
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 583bd3a: Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation.
|
|
4
8
|
|
|
5
9
|
### Patch Changes
|
|
6
10
|
|
|
7
11
|
- 7455dae: Use node prefix on native imports
|
|
12
|
+
- 7021165: Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments.
|
|
8
13
|
- Updated dependencies
|
|
9
|
-
- @backstage/integration-aws-node@0.0.0-nightly-
|
|
10
|
-
- @backstage/backend-plugin-api@0.0.0-nightly-
|
|
11
|
-
- @backstage/plugin-search-backend-node@0.0.0-nightly-
|
|
12
|
-
- @backstage/plugin-search-common@0.0.0-nightly-
|
|
14
|
+
- @backstage/integration-aws-node@0.0.0-nightly-20260203031143
|
|
15
|
+
- @backstage/backend-plugin-api@0.0.0-nightly-20260203031143
|
|
16
|
+
- @backstage/plugin-search-backend-node@0.0.0-nightly-20260203031143
|
|
17
|
+
- @backstage/plugin-search-common@0.0.0-nightly-20260203031143
|
|
13
18
|
- @backstage/config@1.3.6
|
|
14
19
|
|
|
15
20
|
## 1.7.10-next.0
|
package/dist/auth.cjs.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
|
|
5
|
+
const elasticsearchAuthExtensionPoint = backendPluginApi.createExtensionPoint({
|
|
6
|
+
id: "search.elasticsearchEngine.auth"
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
exports.elasticsearchAuthExtensionPoint = elasticsearchAuthExtensionPoint;
|
|
10
|
+
//# sourceMappingURL=auth.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.cjs.js","sources":["../src/auth.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createExtensionPoint } from '@backstage/backend-plugin-api';\n\n/**\n * A provider that supplies authentication headers for Elasticsearch/OpenSearch requests.\n *\n * @remarks\n *\n * This interface allows for dynamic authentication mechanisms such as bearer tokens\n * that need to be refreshed or rotated. The `getAuthHeaders` method is called before\n * each request to the Elasticsearch/OpenSearch cluster, allowing for just-in-time\n * token retrieval and automatic rotation.\n *\n * @example\n *\n * ```ts\n * const authProvider: ElasticSearchAuthProvider = {\n * async getAuthHeaders() {\n * const token = await myTokenService.getToken();\n * return { Authorization: `Bearer ${token}` };\n * },\n * };\n * ```\n *\n * @public\n */\nexport interface ElasticSearchAuthProvider {\n /**\n * Returns authentication headers to be included in requests to Elasticsearch/OpenSearch.\n *\n * @remarks\n *\n * This method is called before each request, allowing for dynamic token refresh\n * and rotation. Implementations should handle caching internally if needed to\n * avoid excessive token generation.\n *\n * @returns A promise that resolves to a record of header names and values\n */\n getAuthHeaders(): Promise<Record<string, string>>;\n}\n\n/**\n * Extension point for providing custom authentication to the Elasticsearch search engine.\n *\n * @remarks\n *\n * Use this extension point to provide dynamic authentication mechanisms such as\n * bearer tokens with automatic rotation. When an auth provider is set, it takes\n * precedence over any static authentication configured in app-config.yaml.\n *\n * @example\n *\n * ```ts\n * import { createBackendModule } from '@backstage/backend-plugin-api';\n * import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch';\n *\n * export default createBackendModule({\n * pluginId: 'search',\n * moduleId: 'elasticsearch-custom-auth',\n * register(env) {\n * env.registerInit({\n * deps: {\n * elasticsearchAuth: elasticsearchAuthExtensionPoint,\n * },\n * async init({ elasticsearchAuth }) {\n * elasticsearchAuth.setAuthProvider({\n * async getAuthHeaders() {\n * const token = await fetchTokenFromIdentityService();\n * return { Authorization: `Bearer ${token}` };\n * },\n * });\n * },\n * });\n * },\n * });\n * ```\n *\n * @public\n */\nexport interface ElasticSearchAuthExtensionPoint {\n /**\n * Sets the authentication provider for the Elasticsearch search engine.\n *\n * @remarks\n *\n * This method can only be called once. Subsequent calls will throw an error.\n * The auth provider takes precedence over static authentication configuration.\n *\n * @param provider - The authentication provider to use\n */\n setAuthProvider(provider: ElasticSearchAuthProvider): void;\n}\n\n/**\n * Extension point used to customize Elasticsearch/OpenSearch authentication.\n *\n * @public\n */\nexport const elasticsearchAuthExtensionPoint =\n createExtensionPoint<ElasticSearchAuthExtensionPoint>({\n id: 'search.elasticsearchEngine.auth',\n });\n"],"names":["createExtensionPoint"],"mappings":";;;;AAiHO,MAAM,kCACXA,qCAAA,CAAsD;AAAA,EACpD,EAAA,EAAI;AACN,CAAC;;;;"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var opensearch = require('@opensearch-project/opensearch');
|
|
4
|
+
var elasticsearch = require('@elastic/elasticsearch');
|
|
5
|
+
|
|
6
|
+
function createOpenSearchAuthTransport(authProvider) {
|
|
7
|
+
class AuthTransport extends opensearch.Transport {
|
|
8
|
+
request(params, optionsOrCallback = {}, maybeCallback) {
|
|
9
|
+
const isOptionsCallback = typeof optionsOrCallback === "function";
|
|
10
|
+
const options = isOptionsCallback ? {} : optionsOrCallback;
|
|
11
|
+
const callback = isOptionsCallback ? optionsOrCallback : maybeCallback;
|
|
12
|
+
if (callback !== void 0) {
|
|
13
|
+
authProvider.getAuthHeaders().then((authHeaders) => {
|
|
14
|
+
const mergedOptions = {
|
|
15
|
+
...options,
|
|
16
|
+
headers: {
|
|
17
|
+
...options.headers,
|
|
18
|
+
...authHeaders
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return super.request(params, mergedOptions, callback);
|
|
22
|
+
}).catch(callback);
|
|
23
|
+
return {
|
|
24
|
+
abort: () => {
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return authProvider.getAuthHeaders().then((authHeaders) => {
|
|
29
|
+
const mergedOptions = {
|
|
30
|
+
...options,
|
|
31
|
+
headers: {
|
|
32
|
+
...options.headers,
|
|
33
|
+
...authHeaders
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return super.request(params, mergedOptions);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return AuthTransport;
|
|
41
|
+
}
|
|
42
|
+
function createElasticSearchAuthTransport(authProvider) {
|
|
43
|
+
class AuthTransport extends elasticsearch.Transport {
|
|
44
|
+
request(params, options, callback) {
|
|
45
|
+
if (typeof options === "function") {
|
|
46
|
+
const cb = options;
|
|
47
|
+
authProvider.getAuthHeaders().then((authHeaders) => {
|
|
48
|
+
const mergedOptions = {
|
|
49
|
+
headers: authHeaders
|
|
50
|
+
};
|
|
51
|
+
return super.request(params, mergedOptions, cb);
|
|
52
|
+
}).catch(cb);
|
|
53
|
+
return { abort: () => {
|
|
54
|
+
} };
|
|
55
|
+
}
|
|
56
|
+
if (callback !== void 0) {
|
|
57
|
+
authProvider.getAuthHeaders().then((authHeaders) => {
|
|
58
|
+
const mergedOptions = {
|
|
59
|
+
...options,
|
|
60
|
+
headers: {
|
|
61
|
+
...options?.headers,
|
|
62
|
+
...authHeaders
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return super.request(params, mergedOptions, callback);
|
|
66
|
+
}).catch(callback);
|
|
67
|
+
return { abort: () => {
|
|
68
|
+
} };
|
|
69
|
+
}
|
|
70
|
+
const result = authProvider.getAuthHeaders().then((authHeaders) => {
|
|
71
|
+
const mergedOptions = {
|
|
72
|
+
...options,
|
|
73
|
+
headers: {
|
|
74
|
+
...options?.headers,
|
|
75
|
+
...authHeaders
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return super.request(params, mergedOptions);
|
|
79
|
+
});
|
|
80
|
+
result.abort = () => {
|
|
81
|
+
};
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return AuthTransport;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.createElasticSearchAuthTransport = createElasticSearchAuthTransport;
|
|
89
|
+
exports.createOpenSearchAuthTransport = createOpenSearchAuthTransport;
|
|
90
|
+
//# sourceMappingURL=ElasticSearchAuthTransport.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElasticSearchAuthTransport.cjs.js","sources":["../../src/engines/ElasticSearchAuthTransport.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Transport as OpenSearchTransport } from '@opensearch-project/opensearch';\nimport { Transport as ElasticSearchTransport } from '@elastic/elasticsearch';\nimport { ElasticSearchAuthProvider } from '../auth';\nimport { ElasticSearchTransportConstructor } from './ElasticSearchClientOptions';\n\n/**\n * Creates a custom OpenSearch Transport class that injects authentication headers\n * from the provided auth provider before each request.\n *\n * @param authProvider - The authentication provider to use for getting headers\n * @returns A Transport class constructor that can be used with the OpenSearch client\n *\n * @internal\n */\nexport function createOpenSearchAuthTransport(\n authProvider: ElasticSearchAuthProvider,\n): ElasticSearchTransportConstructor {\n class AuthTransport extends OpenSearchTransport {\n request(\n params: any,\n optionsOrCallback: any = {},\n maybeCallback?: any,\n ): any {\n // options is optional, so if it's a function, it's the callback\n const isOptionsCallback = typeof optionsOrCallback === 'function';\n const options = isOptionsCallback ? {} : optionsOrCallback;\n const callback = isOptionsCallback ? optionsOrCallback : maybeCallback;\n\n // If callback style, wrap it to inject headers\n if (callback !== undefined) {\n authProvider\n .getAuthHeaders()\n .then(authHeaders => {\n const mergedOptions = {\n ...options,\n headers: {\n ...options.headers,\n ...authHeaders,\n },\n };\n return super.request(params, mergedOptions, callback);\n })\n .catch(callback);\n\n // Return an abort-able object similar to the AWS transport\n return {\n abort: () => {},\n };\n }\n\n // Promise style\n return authProvider.getAuthHeaders().then(authHeaders => {\n const mergedOptions = {\n ...options,\n headers: {\n ...options.headers,\n ...authHeaders,\n },\n };\n return super.request(params, mergedOptions);\n });\n }\n }\n\n return AuthTransport as unknown as ElasticSearchTransportConstructor;\n}\n\n/**\n * Creates a custom Elasticsearch Transport class that injects authentication headers\n * from the provided auth provider before each request.\n *\n * @param authProvider - The authentication provider to use for getting headers\n * @returns A Transport class constructor that can be used with the Elasticsearch client\n *\n * @internal\n */\nexport function createElasticSearchAuthTransport(\n authProvider: ElasticSearchAuthProvider,\n): ElasticSearchTransportConstructor {\n class AuthTransport extends ElasticSearchTransport {\n request(params: any, options?: any, callback?: any): any {\n // Handle overloaded signatures\n if (typeof options === 'function') {\n // Callback style without options\n const cb = options;\n authProvider\n .getAuthHeaders()\n .then(authHeaders => {\n const mergedOptions = {\n headers: authHeaders,\n };\n return super.request(params, mergedOptions, cb);\n })\n .catch(cb);\n\n return { abort: () => {} };\n }\n\n if (callback !== undefined) {\n // Callback style with options\n authProvider\n .getAuthHeaders()\n .then(authHeaders => {\n const mergedOptions = {\n ...options,\n headers: {\n ...options?.headers,\n ...authHeaders,\n },\n };\n return super.request(params, mergedOptions, callback);\n })\n .catch(callback);\n\n return { abort: () => {} };\n }\n\n // Promise style\n const result = authProvider.getAuthHeaders().then(authHeaders => {\n const mergedOptions = {\n ...options,\n headers: {\n ...options?.headers,\n ...authHeaders,\n },\n };\n return super.request(params, mergedOptions);\n });\n\n // Add abort method to match TransportRequestPromise interface\n (result as any).abort = () => {};\n return result;\n }\n }\n\n return AuthTransport as unknown as ElasticSearchTransportConstructor;\n}\n"],"names":["OpenSearchTransport","ElasticSearchTransport"],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ var pluginSearchBackendNode = require('@backstage/plugin-search-backend-node');
|
|
|
9
9
|
var esb = require('elastic-builder');
|
|
10
10
|
var uuid = require('uuid');
|
|
11
11
|
var integrationAwsNode = require('@backstage/integration-aws-node');
|
|
12
|
+
var ElasticSearchAuthTransport = require('./ElasticSearchAuthTransport.cjs.js');
|
|
12
13
|
|
|
13
14
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
14
15
|
|
|
@@ -53,12 +54,14 @@ class ElasticSearchSearchEngine {
|
|
|
53
54
|
config,
|
|
54
55
|
aliasPostfix = `search`,
|
|
55
56
|
indexPrefix = ``,
|
|
56
|
-
translator
|
|
57
|
+
translator,
|
|
58
|
+
authProvider
|
|
57
59
|
} = options;
|
|
58
60
|
const credentialProvider = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(config);
|
|
59
61
|
const clientOptions = await this.createElasticSearchClientOptions(
|
|
60
62
|
await credentialProvider?.getCredentialProvider(),
|
|
61
|
-
config.getConfig("search.elasticsearch")
|
|
63
|
+
config.getConfig("search.elasticsearch"),
|
|
64
|
+
authProvider
|
|
62
65
|
);
|
|
63
66
|
if (clientOptions.provider === "elastic") {
|
|
64
67
|
logger.info("Initializing Elastic.co ElasticSearch search engine.");
|
|
@@ -295,23 +298,29 @@ class ElasticSearchSearchEngine {
|
|
|
295
298
|
const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : "";
|
|
296
299
|
return `${this.indexPrefix}${type}${postFix}`;
|
|
297
300
|
}
|
|
298
|
-
static async createElasticSearchClientOptions(credentialProvider, config) {
|
|
301
|
+
static async createElasticSearchClientOptions(credentialProvider, config, authProvider) {
|
|
299
302
|
if (!config) {
|
|
300
303
|
throw new Error("No elastic search config found");
|
|
301
304
|
}
|
|
302
305
|
const clientOptionsConfig = config.getOptionalConfig("clientOptions");
|
|
303
306
|
const sslConfig = clientOptionsConfig?.getOptionalConfig("ssl");
|
|
304
307
|
if (config.getOptionalString("provider") === "elastic") {
|
|
305
|
-
const authConfig2 = config.getConfig("auth");
|
|
308
|
+
const authConfig2 = authProvider ? void 0 : config.getConfig("auth");
|
|
306
309
|
return {
|
|
307
310
|
provider: "elastic",
|
|
308
311
|
cloud: {
|
|
309
312
|
id: config.getString("cloudId")
|
|
310
313
|
},
|
|
311
|
-
auth
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
// When using authProvider, we inject auth via custom Transport, not static auth
|
|
315
|
+
...authConfig2 ? {
|
|
316
|
+
auth: {
|
|
317
|
+
username: authConfig2.getString("username"),
|
|
318
|
+
password: authConfig2.getString("password")
|
|
319
|
+
}
|
|
320
|
+
} : {},
|
|
321
|
+
...authProvider ? {
|
|
322
|
+
Transport: ElasticSearchAuthTransport.createElasticSearchAuthTransport(authProvider)
|
|
323
|
+
} : {},
|
|
315
324
|
...sslConfig ? {
|
|
316
325
|
ssl: {
|
|
317
326
|
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
@@ -320,6 +329,11 @@ class ElasticSearchSearchEngine {
|
|
|
320
329
|
};
|
|
321
330
|
}
|
|
322
331
|
if (config.getOptionalString("provider") === "aws") {
|
|
332
|
+
if (authProvider) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"Custom auth provider is not supported with AWS provider. AWS uses SigV4 signing."
|
|
335
|
+
);
|
|
336
|
+
}
|
|
323
337
|
const requestSigner = new aws4.RequestSigner(config.getString("node"));
|
|
324
338
|
const service = config.getOptionalString("service") ?? requestSigner.service;
|
|
325
339
|
if (service !== "es" && service !== "aoss")
|
|
@@ -343,14 +357,20 @@ class ElasticSearchSearchEngine {
|
|
|
343
357
|
};
|
|
344
358
|
}
|
|
345
359
|
if (config.getOptionalString("provider") === "opensearch") {
|
|
346
|
-
const authConfig2 = config.
|
|
360
|
+
const authConfig2 = authProvider ? void 0 : config.getOptionalConfig("auth");
|
|
347
361
|
return {
|
|
348
362
|
provider: "opensearch",
|
|
349
363
|
node: config.getString("node"),
|
|
350
|
-
auth
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
// When using authProvider, we inject auth via custom Transport, not static auth
|
|
365
|
+
...authConfig2 ? {
|
|
366
|
+
auth: {
|
|
367
|
+
username: authConfig2.getString("username"),
|
|
368
|
+
password: authConfig2.getString("password")
|
|
369
|
+
}
|
|
370
|
+
} : {},
|
|
371
|
+
...authProvider ? {
|
|
372
|
+
Transport: ElasticSearchAuthTransport.createOpenSearchAuthTransport(authProvider)
|
|
373
|
+
} : {},
|
|
354
374
|
...sslConfig ? {
|
|
355
375
|
ssl: {
|
|
356
376
|
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
@@ -358,7 +378,7 @@ class ElasticSearchSearchEngine {
|
|
|
358
378
|
} : {}
|
|
359
379
|
};
|
|
360
380
|
}
|
|
361
|
-
const authConfig = config.getOptionalConfig("auth");
|
|
381
|
+
const authConfig = authProvider ? void 0 : config.getOptionalConfig("auth");
|
|
362
382
|
const auth = authConfig && (authConfig.has("apiKey") ? {
|
|
363
383
|
apiKey: authConfig.getString("apiKey")
|
|
364
384
|
} : {
|
|
@@ -367,7 +387,10 @@ class ElasticSearchSearchEngine {
|
|
|
367
387
|
});
|
|
368
388
|
return {
|
|
369
389
|
node: config.getString("node"),
|
|
370
|
-
auth,
|
|
390
|
+
...auth ? { auth } : {},
|
|
391
|
+
...authProvider ? {
|
|
392
|
+
Transport: ElasticSearchAuthTransport.createElasticSearchAuthTransport(authProvider)
|
|
393
|
+
} : {},
|
|
371
394
|
...sslConfig ? {
|
|
372
395
|
ssl: {
|
|
373
396
|
rejectUnauthorized: sslConfig?.getOptionalBoolean("rejectUnauthorized")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElasticSearchSearchEngine.cjs.js","sources":["../../src/engines/ElasticSearchSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n IndexableDocument,\n IndexableResult,\n IndexableResultSet,\n SearchQuery,\n} from '@backstage/plugin-search-common';\nimport { SearchEngine } from '@backstage/plugin-search-backend-node';\nimport { isEmpty, isNumber, isNaN as nan } from 'lodash';\n\nimport { AwsSigv4Signer } from '@opensearch-project/opensearch/aws';\nimport { RequestSigner } from 'aws4';\nimport { Config } from '@backstage/config';\nimport {\n ElasticSearchClientOptions,\n OpenSearchElasticSearchClientOptions,\n} from './ElasticSearchClientOptions';\nimport { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper';\nimport { ElasticSearchCustomIndexTemplate } from './types';\nimport { ElasticSearchSearchEngineIndexer } from './ElasticSearchSearchEngineIndexer';\nimport { MissingIndexError } from '@backstage/plugin-search-backend-node';\nimport esb from 'elastic-builder';\nimport { v4 as uuid } from 'uuid';\nimport {\n AwsCredentialProvider,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport type { ElasticSearchClientOptions };\n\n/**\n * Search query that the elasticsearch engine understands.\n * @public\n */\nexport type ElasticSearchConcreteQuery = {\n documentTypes?: string[];\n elasticSearchQuery: Object;\n pageSize: number;\n};\n\n/**\n * Options available for the Elasticsearch specific query translator.\n * @public\n */\nexport type ElasticSearchQueryTranslatorOptions = {\n highlightOptions?: ElasticSearchHighlightConfig;\n queryOptions?: ElasticSearchQueryConfig;\n};\n\n/**\n * Elasticsearch specific query translator.\n * @public\n */\nexport type ElasticSearchQueryTranslator = (\n query: SearchQuery,\n options?: ElasticSearchQueryTranslatorOptions,\n) => ElasticSearchConcreteQuery;\n\n/**\n * Options for instantiate ElasticSearchSearchEngine\n * @public\n */\nexport type ElasticSearchOptions = {\n logger: LoggerService;\n config: Config;\n aliasPostfix?: string;\n indexPrefix?: string;\n translator?: ElasticSearchQueryTranslator;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchHighlightOptions = {\n fragmentDelimiter?: string;\n fragmentSize?: number;\n numFragments?: number;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchQueryConfig = {\n fuzziness?: string | number;\n prefixLength?: number;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchHighlightConfig = {\n fragmentDelimiter: string;\n fragmentSize: number;\n numFragments: number;\n preTag: string;\n postTag: string;\n};\n\ntype ElasticSearchResult = {\n _index: string;\n _type: string;\n _score: number;\n _source: IndexableDocument;\n highlight?: {\n [field: string]: string[];\n };\n};\n\nfunction isBlank(str: string) {\n return (isEmpty(str) && !isNumber(str)) || nan(str);\n}\n\nconst DEFAULT_INDEXER_BATCH_SIZE = 1000;\n\n/**\n * @public\n */\nexport class ElasticSearchSearchEngine implements SearchEngine {\n private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper;\n private readonly highlightOptions: ElasticSearchHighlightConfig;\n private readonly queryOptions?: ElasticSearchQueryConfig;\n\n private readonly elasticSearchClientOptions: ElasticSearchClientOptions;\n private readonly aliasPostfix: string;\n private readonly indexPrefix: string;\n private readonly logger: LoggerService;\n private readonly batchSize: number;\n private readonly batchKeyField?: string;\n\n constructor(\n elasticSearchClientOptions: ElasticSearchClientOptions,\n aliasPostfix: string,\n indexPrefix: string,\n logger: LoggerService,\n batchSize: number,\n batchKeyField?: string,\n highlightOptions?: ElasticSearchHighlightOptions,\n queryOptions?: ElasticSearchQueryConfig,\n ) {\n this.elasticSearchClientOptions = elasticSearchClientOptions;\n this.aliasPostfix = aliasPostfix;\n this.indexPrefix = indexPrefix;\n this.logger = logger;\n this.batchSize = batchSize;\n this.batchKeyField = batchKeyField;\n this.elasticSearchClientWrapper =\n ElasticSearchClientWrapper.fromClientOptions(elasticSearchClientOptions);\n const uuidTag = uuid();\n this.highlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n fragmentSize: 1000,\n numFragments: 1,\n fragmentDelimiter: ' ... ',\n ...highlightOptions,\n };\n this.queryOptions = queryOptions;\n }\n\n static async fromConfig(options: ElasticSearchOptions) {\n const {\n logger,\n config,\n aliasPostfix = `search`,\n indexPrefix = ``,\n translator,\n } = options;\n const credentialProvider = DefaultAwsCredentialsManager.fromConfig(config);\n const clientOptions = await this.createElasticSearchClientOptions(\n await credentialProvider?.getCredentialProvider(),\n config.getConfig('search.elasticsearch'),\n );\n if (clientOptions.provider === 'elastic') {\n logger.info('Initializing Elastic.co ElasticSearch search engine.');\n } else if (clientOptions.provider === 'aws') {\n logger.info('Initializing AWS OpenSearch search engine.');\n } else if (clientOptions.provider === 'opensearch') {\n logger.info('Initializing OpenSearch search engine.');\n } else {\n logger.info('Initializing ElasticSearch search engine.');\n }\n\n const engine = new ElasticSearchSearchEngine(\n clientOptions,\n aliasPostfix,\n indexPrefix,\n logger,\n config.getOptionalNumber('search.elasticsearch.batchSize') ??\n DEFAULT_INDEXER_BATCH_SIZE,\n config.getOptionalString('search.elasticsearch.batchKeyField'),\n config.getOptional<ElasticSearchHighlightOptions>(\n 'search.elasticsearch.highlightOptions',\n ),\n config.getOptional<ElasticSearchQueryConfig>(\n 'search.elasticsearch.queryOptions',\n ),\n );\n\n for (const indexTemplate of this.readIndexTemplateConfig(\n config.getConfig('search.elasticsearch'),\n )) {\n await engine.setIndexTemplate(indexTemplate);\n }\n\n if (translator) {\n await engine.setTranslator(translator);\n }\n\n return engine;\n }\n\n /**\n * Create a custom search client from the derived search client configuration.\n * This need not be the same client that the engine uses internally.\n *\n * @example Instantiate an instance of an Elasticsearch client.\n *\n * ```ts\n * import { isOpenSearchCompatible } from '@backstage/plugin-search-backend-module-elasticsearch';\n * import { Client } from '@elastic/elasticsearch';\n *\n * const client = searchEngine.newClient<Client>(options => {\n * // This type guard ensures options are compatible with either OpenSearch\n * // or Elasticsearch client constructors.\n * if (!isOpenSearchCompatible(options)) {\n * return new Client(options);\n * }\n * throw new Error('Incompatible options provided');\n * });\n * ```\n */\n newClient<T>(create: (options: ElasticSearchClientOptions) => T): T {\n return create(this.elasticSearchClientOptions);\n }\n\n protected translator(\n query: SearchQuery,\n options?: ElasticSearchQueryTranslatorOptions,\n ): ElasticSearchConcreteQuery {\n const { term, filters = {}, types, pageCursor } = query;\n\n const filter = Object.entries(filters)\n .filter(([_, value]) => Boolean(value))\n .map(([key, value]: [key: string, value: any]) => {\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n // Use exact matching for string datatype fields\n const keyword = typeof value === 'string' ? `${key}.keyword` : key;\n return esb.matchQuery(keyword, value.toString());\n }\n if (Array.isArray(value)) {\n return esb\n .boolQuery()\n .should(value.map(it => esb.matchQuery(key, it.toString())));\n }\n this.logger.error('Failed to query, unrecognized filter type', {\n key,\n value,\n });\n throw new Error(\n 'Failed to add filters to query. Unrecognized filter type',\n );\n });\n\n const esbQueries = [];\n // https://regex101.com/r/Lr0MqS/1\n const phraseTerms = term.match(/\"[^\"]*\"/g);\n\n if (isBlank(term)) {\n const esbQuery = esb.matchAllQuery();\n esbQueries.push(esbQuery);\n } else if (phraseTerms && phraseTerms.length > 0) {\n let restTerm = term;\n for (const phraseTerm of phraseTerms) {\n restTerm = restTerm.replace(phraseTerm, '');\n const esbPhraseQuery = esb\n .multiMatchQuery(['*'], phraseTerm.replace(/\"/g, ''))\n .type('phrase');\n esbQueries.push(esbPhraseQuery);\n }\n if (restTerm?.length > 0) {\n const esbRestQuery = esb\n .multiMatchQuery(['*'], restTerm.trim())\n .fuzziness(options?.queryOptions?.fuzziness ?? 'auto')\n .prefixLength(options?.queryOptions?.prefixLength ?? 0);\n esbQueries.push(esbRestQuery);\n }\n } else {\n const esbQuery = esb\n .multiMatchQuery(['*'], term)\n .fuzziness(options?.queryOptions?.fuzziness ?? 'auto')\n .prefixLength(options?.queryOptions?.prefixLength ?? 0);\n esbQueries.push(esbQuery);\n }\n\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(pageCursor);\n\n let esbRequestBodySearch = esb\n .requestBodySearch()\n .query(esb.boolQuery().filter(filter).should(esbQueries))\n .from(page * pageSize)\n .size(pageSize);\n\n if (options?.highlightOptions) {\n esbRequestBodySearch = esbRequestBodySearch.highlight(\n esb\n .highlight('*')\n .numberOfFragments(options.highlightOptions.numFragments as number)\n .fragmentSize(options.highlightOptions.fragmentSize as number)\n .preTags(options.highlightOptions.preTag)\n .postTags(options.highlightOptions.postTag),\n );\n }\n\n return {\n elasticSearchQuery: esbRequestBodySearch.toJSON(),\n documentTypes: types,\n pageSize,\n };\n }\n\n setTranslator(translator: ElasticSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async setIndexTemplate(template: ElasticSearchCustomIndexTemplate) {\n try {\n await this.elasticSearchClientWrapper.putIndexTemplate(template);\n this.logger.info('Custom index template set');\n } catch (error) {\n this.logger.error(`Unable to set custom index template: ${error}`);\n }\n }\n\n async getIndexer(type: string) {\n const alias = this.constructSearchAlias(type);\n const indexerLogger = this.logger.child({ documentType: type });\n\n const indexer = new ElasticSearchSearchEngineIndexer({\n type,\n indexPrefix: this.indexPrefix,\n indexSeparator: this.indexSeparator,\n alias,\n elasticSearchClientWrapper: this.elasticSearchClientWrapper,\n logger: indexerLogger,\n batchSize: this.batchSize,\n batchKeyField: this.batchKeyField,\n skipRefresh:\n (\n this\n .elasticSearchClientOptions as OpenSearchElasticSearchClientOptions\n )?.service === 'aoss',\n });\n\n // Attempt cleanup upon failure.\n // todo(@backstage/search-maintainers): Consider introducing a more\n // formal mechanism for handling such errors in BatchSearchEngineIndexer and\n // replacing this handler with it. See: #17291\n indexer.on('error', async e => {\n indexerLogger.error(`Failed to index documents for type ${type}`, e);\n let cleanupError: Error | undefined;\n\n // In some cases, a failure may have occurred before the indexer was able\n // to complete initialization. Try up to 5 times to remove the dangling\n // index.\n await new Promise<void>(async done => {\n const maxAttempts = 5;\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: indexer.indexName,\n });\n\n attempts = maxAttempts;\n cleanupError = undefined;\n done();\n } catch (err) {\n cleanupError = err;\n }\n\n // Wait 1 second between retries.\n await new Promise(okay => setTimeout(okay, 1000));\n\n attempts++;\n }\n done();\n });\n\n if (cleanupError) {\n indexerLogger.error(\n `Unable to clean up elastic index ${indexer.indexName}: ${cleanupError}`,\n );\n } else {\n indexerLogger.info(\n `Removed partial, failed index ${indexer.indexName}`,\n );\n }\n });\n\n return indexer;\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { elasticSearchQuery, documentTypes, pageSize } = this.translator(\n query,\n {\n highlightOptions: this.highlightOptions,\n queryOptions: this.queryOptions,\n },\n );\n const queryIndices = documentTypes\n ? documentTypes.map(it => this.constructSearchAlias(it))\n : this.constructSearchAlias('*');\n try {\n const result = await this.elasticSearchClientWrapper.search({\n index: queryIndices,\n body: elasticSearchQuery,\n });\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = result.body.hits.total.value > (page + 1) * pageSize;\n const hasPreviousPage = page > 0;\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n return {\n results: result.body.hits.hits.map(\n (d: ElasticSearchResult, index: number) => {\n const resultItem: IndexableResult = {\n type: this.getTypeFromIndex(d._index),\n document: d._source,\n rank: pageSize * page + index + 1,\n };\n\n if (d.highlight) {\n resultItem.highlight = {\n preTag: this.highlightOptions.preTag as string,\n postTag: this.highlightOptions.postTag as string,\n fields: Object.fromEntries(\n Object.entries(d.highlight).map(([field, fragments]) => [\n field,\n fragments.join(this.highlightOptions.fragmentDelimiter),\n ]),\n ),\n };\n }\n\n return resultItem;\n },\n ),\n nextPageCursor,\n previousPageCursor,\n numberOfResults: result.body.hits.total.value,\n };\n } catch (error) {\n if (error.meta?.body?.error?.type === 'index_not_found_exception') {\n throw new MissingIndexError(\n `Missing index for ${queryIndices}. This means there are no documents to search through.`,\n error,\n );\n }\n this.logger.error(\n `Failed to query documents for indices ${queryIndices}`,\n error,\n );\n return Promise.reject({ results: [] });\n }\n }\n\n private readonly indexSeparator = '-index__';\n\n private getTypeFromIndex(index: string) {\n return index\n .substring(this.indexPrefix.length)\n .split(this.indexSeparator)[0];\n }\n\n private constructSearchAlias(type: string) {\n const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : '';\n return `${this.indexPrefix}${type}${postFix}`;\n }\n\n private static async createElasticSearchClientOptions(\n credentialProvider: AwsCredentialProvider,\n config?: Config,\n ): Promise<ElasticSearchClientOptions> {\n if (!config) {\n throw new Error('No elastic search config found');\n }\n const clientOptionsConfig = config.getOptionalConfig('clientOptions');\n const sslConfig = clientOptionsConfig?.getOptionalConfig('ssl');\n\n if (config.getOptionalString('provider') === 'elastic') {\n const authConfig = config.getConfig('auth');\n return {\n provider: 'elastic',\n cloud: {\n id: config.getString('cloudId'),\n },\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n if (config.getOptionalString('provider') === 'aws') {\n const requestSigner = new RequestSigner(config.getString('node'));\n const service =\n config.getOptionalString('service') ?? requestSigner.service;\n if (service !== 'es' && service !== 'aoss')\n throw new Error(`Unrecognized service type: ${service}`);\n return {\n provider: 'aws',\n node: config.getString('node'),\n region: config.getOptionalString('region'),\n service,\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n ...AwsSigv4Signer({\n region: config.getOptionalString('region') ?? requestSigner.region, // for backwards compatibility\n service: service,\n getCredentials: async () =>\n await credentialProvider.sdkCredentialProvider(),\n }),\n };\n }\n if (config.getOptionalString('provider') === 'opensearch') {\n const authConfig = config.getConfig('auth');\n return {\n provider: 'opensearch',\n node: config.getString('node'),\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n const authConfig = config.getOptionalConfig('auth');\n const auth =\n authConfig &&\n (authConfig.has('apiKey')\n ? {\n apiKey: authConfig.getString('apiKey'),\n }\n : {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n });\n return {\n node: config.getString('node'),\n auth,\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n\n private static readIndexTemplateConfig(\n config: Config,\n ): ElasticSearchCustomIndexTemplate[] {\n return (\n config.getOptionalConfigArray('indexTemplates')?.map(templateConfig => {\n const bodyConfig = templateConfig.getConfig('body');\n return {\n name: templateConfig.getString('name'),\n body: {\n index_patterns: bodyConfig.getStringArray('index_patterns'),\n composed_of: bodyConfig.getOptionalStringArray('composed_of'),\n template: bodyConfig.getOptionalConfig('template')?.get(),\n },\n };\n }) ?? []\n );\n }\n}\n\n/**\n * @public\n */\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["isEmpty","isNumber","nan","ElasticSearchClientWrapper","uuid","DefaultAwsCredentialsManager","esb","ElasticSearchSearchEngineIndexer","MissingIndexError","authConfig","RequestSigner","AwsSigv4Signer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ElasticSearchSearchEngine.cjs.js","sources":["../../src/engines/ElasticSearchSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n IndexableDocument,\n IndexableResult,\n IndexableResultSet,\n SearchQuery,\n} from '@backstage/plugin-search-common';\nimport { SearchEngine } from '@backstage/plugin-search-backend-node';\nimport { isEmpty, isNumber, isNaN as nan } from 'lodash';\n\nimport { AwsSigv4Signer } from '@opensearch-project/opensearch/aws';\nimport { RequestSigner } from 'aws4';\nimport { Config } from '@backstage/config';\nimport {\n ElasticSearchClientOptions,\n OpenSearchElasticSearchClientOptions,\n} from './ElasticSearchClientOptions';\nimport { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper';\nimport { ElasticSearchCustomIndexTemplate } from './types';\nimport { ElasticSearchSearchEngineIndexer } from './ElasticSearchSearchEngineIndexer';\nimport { MissingIndexError } from '@backstage/plugin-search-backend-node';\nimport esb from 'elastic-builder';\nimport { v4 as uuid } from 'uuid';\nimport {\n AwsCredentialProvider,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ElasticSearchAuthProvider } from '../auth';\nimport {\n createOpenSearchAuthTransport,\n createElasticSearchAuthTransport,\n} from './ElasticSearchAuthTransport';\n\nexport type { ElasticSearchClientOptions };\n\n/**\n * Search query that the elasticsearch engine understands.\n * @public\n */\nexport type ElasticSearchConcreteQuery = {\n documentTypes?: string[];\n elasticSearchQuery: Object;\n pageSize: number;\n};\n\n/**\n * Options available for the Elasticsearch specific query translator.\n * @public\n */\nexport type ElasticSearchQueryTranslatorOptions = {\n highlightOptions?: ElasticSearchHighlightConfig;\n queryOptions?: ElasticSearchQueryConfig;\n};\n\n/**\n * Elasticsearch specific query translator.\n * @public\n */\nexport type ElasticSearchQueryTranslator = (\n query: SearchQuery,\n options?: ElasticSearchQueryTranslatorOptions,\n) => ElasticSearchConcreteQuery;\n\n/**\n * Options for instantiate ElasticSearchSearchEngine\n * @public\n */\nexport type ElasticSearchOptions = {\n logger: LoggerService;\n config: Config;\n aliasPostfix?: string;\n indexPrefix?: string;\n translator?: ElasticSearchQueryTranslator;\n /**\n * An optional authentication provider for dynamic authentication.\n *\n * @remarks\n *\n * When provided, this auth provider will be used to inject authentication\n * headers into each request to Elasticsearch/OpenSearch. This is useful\n * for scenarios requiring dynamic tokens (e.g., bearer tokens with rotation).\n *\n * The auth provider takes precedence over static authentication configured\n * in app-config.yaml.\n */\n authProvider?: ElasticSearchAuthProvider;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchHighlightOptions = {\n fragmentDelimiter?: string;\n fragmentSize?: number;\n numFragments?: number;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchQueryConfig = {\n fuzziness?: string | number;\n prefixLength?: number;\n};\n\n/**\n * @public\n */\nexport type ElasticSearchHighlightConfig = {\n fragmentDelimiter: string;\n fragmentSize: number;\n numFragments: number;\n preTag: string;\n postTag: string;\n};\n\ntype ElasticSearchResult = {\n _index: string;\n _type: string;\n _score: number;\n _source: IndexableDocument;\n highlight?: {\n [field: string]: string[];\n };\n};\n\nfunction isBlank(str: string) {\n return (isEmpty(str) && !isNumber(str)) || nan(str);\n}\n\nconst DEFAULT_INDEXER_BATCH_SIZE = 1000;\n\n/**\n * @public\n */\nexport class ElasticSearchSearchEngine implements SearchEngine {\n private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper;\n private readonly highlightOptions: ElasticSearchHighlightConfig;\n private readonly queryOptions?: ElasticSearchQueryConfig;\n\n private readonly elasticSearchClientOptions: ElasticSearchClientOptions;\n private readonly aliasPostfix: string;\n private readonly indexPrefix: string;\n private readonly logger: LoggerService;\n private readonly batchSize: number;\n private readonly batchKeyField?: string;\n\n constructor(\n elasticSearchClientOptions: ElasticSearchClientOptions,\n aliasPostfix: string,\n indexPrefix: string,\n logger: LoggerService,\n batchSize: number,\n batchKeyField?: string,\n highlightOptions?: ElasticSearchHighlightOptions,\n queryOptions?: ElasticSearchQueryConfig,\n ) {\n this.elasticSearchClientOptions = elasticSearchClientOptions;\n this.aliasPostfix = aliasPostfix;\n this.indexPrefix = indexPrefix;\n this.logger = logger;\n this.batchSize = batchSize;\n this.batchKeyField = batchKeyField;\n this.elasticSearchClientWrapper =\n ElasticSearchClientWrapper.fromClientOptions(elasticSearchClientOptions);\n const uuidTag = uuid();\n this.highlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n fragmentSize: 1000,\n numFragments: 1,\n fragmentDelimiter: ' ... ',\n ...highlightOptions,\n };\n this.queryOptions = queryOptions;\n }\n\n static async fromConfig(options: ElasticSearchOptions) {\n const {\n logger,\n config,\n aliasPostfix = `search`,\n indexPrefix = ``,\n translator,\n authProvider,\n } = options;\n const credentialProvider = DefaultAwsCredentialsManager.fromConfig(config);\n const clientOptions = await this.createElasticSearchClientOptions(\n await credentialProvider?.getCredentialProvider(),\n config.getConfig('search.elasticsearch'),\n authProvider,\n );\n if (clientOptions.provider === 'elastic') {\n logger.info('Initializing Elastic.co ElasticSearch search engine.');\n } else if (clientOptions.provider === 'aws') {\n logger.info('Initializing AWS OpenSearch search engine.');\n } else if (clientOptions.provider === 'opensearch') {\n logger.info('Initializing OpenSearch search engine.');\n } else {\n logger.info('Initializing ElasticSearch search engine.');\n }\n\n const engine = new ElasticSearchSearchEngine(\n clientOptions,\n aliasPostfix,\n indexPrefix,\n logger,\n config.getOptionalNumber('search.elasticsearch.batchSize') ??\n DEFAULT_INDEXER_BATCH_SIZE,\n config.getOptionalString('search.elasticsearch.batchKeyField'),\n config.getOptional<ElasticSearchHighlightOptions>(\n 'search.elasticsearch.highlightOptions',\n ),\n config.getOptional<ElasticSearchQueryConfig>(\n 'search.elasticsearch.queryOptions',\n ),\n );\n\n for (const indexTemplate of this.readIndexTemplateConfig(\n config.getConfig('search.elasticsearch'),\n )) {\n await engine.setIndexTemplate(indexTemplate);\n }\n\n if (translator) {\n await engine.setTranslator(translator);\n }\n\n return engine;\n }\n\n /**\n * Create a custom search client from the derived search client configuration.\n * This need not be the same client that the engine uses internally.\n *\n * @example Instantiate an instance of an Elasticsearch client.\n *\n * ```ts\n * import { isOpenSearchCompatible } from '@backstage/plugin-search-backend-module-elasticsearch';\n * import { Client } from '@elastic/elasticsearch';\n *\n * const client = searchEngine.newClient<Client>(options => {\n * // This type guard ensures options are compatible with either OpenSearch\n * // or Elasticsearch client constructors.\n * if (!isOpenSearchCompatible(options)) {\n * return new Client(options);\n * }\n * throw new Error('Incompatible options provided');\n * });\n * ```\n */\n newClient<T>(create: (options: ElasticSearchClientOptions) => T): T {\n return create(this.elasticSearchClientOptions);\n }\n\n protected translator(\n query: SearchQuery,\n options?: ElasticSearchQueryTranslatorOptions,\n ): ElasticSearchConcreteQuery {\n const { term, filters = {}, types, pageCursor } = query;\n\n const filter = Object.entries(filters)\n .filter(([_, value]) => Boolean(value))\n .map(([key, value]: [key: string, value: any]) => {\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n // Use exact matching for string datatype fields\n const keyword = typeof value === 'string' ? `${key}.keyword` : key;\n return esb.matchQuery(keyword, value.toString());\n }\n if (Array.isArray(value)) {\n return esb\n .boolQuery()\n .should(value.map(it => esb.matchQuery(key, it.toString())));\n }\n this.logger.error('Failed to query, unrecognized filter type', {\n key,\n value,\n });\n throw new Error(\n 'Failed to add filters to query. Unrecognized filter type',\n );\n });\n\n const esbQueries = [];\n // https://regex101.com/r/Lr0MqS/1\n const phraseTerms = term.match(/\"[^\"]*\"/g);\n\n if (isBlank(term)) {\n const esbQuery = esb.matchAllQuery();\n esbQueries.push(esbQuery);\n } else if (phraseTerms && phraseTerms.length > 0) {\n let restTerm = term;\n for (const phraseTerm of phraseTerms) {\n restTerm = restTerm.replace(phraseTerm, '');\n const esbPhraseQuery = esb\n .multiMatchQuery(['*'], phraseTerm.replace(/\"/g, ''))\n .type('phrase');\n esbQueries.push(esbPhraseQuery);\n }\n if (restTerm?.length > 0) {\n const esbRestQuery = esb\n .multiMatchQuery(['*'], restTerm.trim())\n .fuzziness(options?.queryOptions?.fuzziness ?? 'auto')\n .prefixLength(options?.queryOptions?.prefixLength ?? 0);\n esbQueries.push(esbRestQuery);\n }\n } else {\n const esbQuery = esb\n .multiMatchQuery(['*'], term)\n .fuzziness(options?.queryOptions?.fuzziness ?? 'auto')\n .prefixLength(options?.queryOptions?.prefixLength ?? 0);\n esbQueries.push(esbQuery);\n }\n\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(pageCursor);\n\n let esbRequestBodySearch = esb\n .requestBodySearch()\n .query(esb.boolQuery().filter(filter).should(esbQueries))\n .from(page * pageSize)\n .size(pageSize);\n\n if (options?.highlightOptions) {\n esbRequestBodySearch = esbRequestBodySearch.highlight(\n esb\n .highlight('*')\n .numberOfFragments(options.highlightOptions.numFragments as number)\n .fragmentSize(options.highlightOptions.fragmentSize as number)\n .preTags(options.highlightOptions.preTag)\n .postTags(options.highlightOptions.postTag),\n );\n }\n\n return {\n elasticSearchQuery: esbRequestBodySearch.toJSON(),\n documentTypes: types,\n pageSize,\n };\n }\n\n setTranslator(translator: ElasticSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async setIndexTemplate(template: ElasticSearchCustomIndexTemplate) {\n try {\n await this.elasticSearchClientWrapper.putIndexTemplate(template);\n this.logger.info('Custom index template set');\n } catch (error) {\n this.logger.error(`Unable to set custom index template: ${error}`);\n }\n }\n\n async getIndexer(type: string) {\n const alias = this.constructSearchAlias(type);\n const indexerLogger = this.logger.child({ documentType: type });\n\n const indexer = new ElasticSearchSearchEngineIndexer({\n type,\n indexPrefix: this.indexPrefix,\n indexSeparator: this.indexSeparator,\n alias,\n elasticSearchClientWrapper: this.elasticSearchClientWrapper,\n logger: indexerLogger,\n batchSize: this.batchSize,\n batchKeyField: this.batchKeyField,\n skipRefresh:\n (\n this\n .elasticSearchClientOptions as OpenSearchElasticSearchClientOptions\n )?.service === 'aoss',\n });\n\n // Attempt cleanup upon failure.\n // todo(@backstage/search-maintainers): Consider introducing a more\n // formal mechanism for handling such errors in BatchSearchEngineIndexer and\n // replacing this handler with it. See: #17291\n indexer.on('error', async e => {\n indexerLogger.error(`Failed to index documents for type ${type}`, e);\n let cleanupError: Error | undefined;\n\n // In some cases, a failure may have occurred before the indexer was able\n // to complete initialization. Try up to 5 times to remove the dangling\n // index.\n await new Promise<void>(async done => {\n const maxAttempts = 5;\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: indexer.indexName,\n });\n\n attempts = maxAttempts;\n cleanupError = undefined;\n done();\n } catch (err) {\n cleanupError = err;\n }\n\n // Wait 1 second between retries.\n await new Promise(okay => setTimeout(okay, 1000));\n\n attempts++;\n }\n done();\n });\n\n if (cleanupError) {\n indexerLogger.error(\n `Unable to clean up elastic index ${indexer.indexName}: ${cleanupError}`,\n );\n } else {\n indexerLogger.info(\n `Removed partial, failed index ${indexer.indexName}`,\n );\n }\n });\n\n return indexer;\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { elasticSearchQuery, documentTypes, pageSize } = this.translator(\n query,\n {\n highlightOptions: this.highlightOptions,\n queryOptions: this.queryOptions,\n },\n );\n const queryIndices = documentTypes\n ? documentTypes.map(it => this.constructSearchAlias(it))\n : this.constructSearchAlias('*');\n try {\n const result = await this.elasticSearchClientWrapper.search({\n index: queryIndices,\n body: elasticSearchQuery,\n });\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = result.body.hits.total.value > (page + 1) * pageSize;\n const hasPreviousPage = page > 0;\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n return {\n results: result.body.hits.hits.map(\n (d: ElasticSearchResult, index: number) => {\n const resultItem: IndexableResult = {\n type: this.getTypeFromIndex(d._index),\n document: d._source,\n rank: pageSize * page + index + 1,\n };\n\n if (d.highlight) {\n resultItem.highlight = {\n preTag: this.highlightOptions.preTag as string,\n postTag: this.highlightOptions.postTag as string,\n fields: Object.fromEntries(\n Object.entries(d.highlight).map(([field, fragments]) => [\n field,\n fragments.join(this.highlightOptions.fragmentDelimiter),\n ]),\n ),\n };\n }\n\n return resultItem;\n },\n ),\n nextPageCursor,\n previousPageCursor,\n numberOfResults: result.body.hits.total.value,\n };\n } catch (error) {\n if (error.meta?.body?.error?.type === 'index_not_found_exception') {\n throw new MissingIndexError(\n `Missing index for ${queryIndices}. This means there are no documents to search through.`,\n error,\n );\n }\n this.logger.error(\n `Failed to query documents for indices ${queryIndices}`,\n error,\n );\n return Promise.reject({ results: [] });\n }\n }\n\n private readonly indexSeparator = '-index__';\n\n private getTypeFromIndex(index: string) {\n return index\n .substring(this.indexPrefix.length)\n .split(this.indexSeparator)[0];\n }\n\n private constructSearchAlias(type: string) {\n const postFix = this.aliasPostfix ? `__${this.aliasPostfix}` : '';\n return `${this.indexPrefix}${type}${postFix}`;\n }\n\n private static async createElasticSearchClientOptions(\n credentialProvider: AwsCredentialProvider,\n config?: Config,\n authProvider?: ElasticSearchAuthProvider,\n ): Promise<ElasticSearchClientOptions> {\n if (!config) {\n throw new Error('No elastic search config found');\n }\n const clientOptionsConfig = config.getOptionalConfig('clientOptions');\n const sslConfig = clientOptionsConfig?.getOptionalConfig('ssl');\n\n if (config.getOptionalString('provider') === 'elastic') {\n const authConfig = authProvider ? undefined : config.getConfig('auth');\n return {\n provider: 'elastic',\n cloud: {\n id: config.getString('cloudId'),\n },\n // When using authProvider, we inject auth via custom Transport, not static auth\n ...(authConfig\n ? {\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n }\n : {}),\n ...(authProvider\n ? {\n Transport: createElasticSearchAuthTransport(authProvider),\n }\n : {}),\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n if (config.getOptionalString('provider') === 'aws') {\n // AWS provider uses SigV4 signing; authProvider is not applicable here\n if (authProvider) {\n throw new Error(\n 'Custom auth provider is not supported with AWS provider. AWS uses SigV4 signing.',\n );\n }\n const requestSigner = new RequestSigner(config.getString('node'));\n const service =\n config.getOptionalString('service') ?? requestSigner.service;\n if (service !== 'es' && service !== 'aoss')\n throw new Error(`Unrecognized service type: ${service}`);\n return {\n provider: 'aws',\n node: config.getString('node'),\n region: config.getOptionalString('region'),\n service,\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n ...AwsSigv4Signer({\n region: config.getOptionalString('region') ?? requestSigner.region, // for backwards compatibility\n service: service,\n getCredentials: async () =>\n await credentialProvider.sdkCredentialProvider(),\n }),\n };\n }\n if (config.getOptionalString('provider') === 'opensearch') {\n const authConfig = authProvider\n ? undefined\n : config.getOptionalConfig('auth');\n return {\n provider: 'opensearch',\n node: config.getString('node'),\n // When using authProvider, we inject auth via custom Transport, not static auth\n ...(authConfig\n ? {\n auth: {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n },\n }\n : {}),\n ...(authProvider\n ? {\n Transport: createOpenSearchAuthTransport(authProvider),\n }\n : {}),\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n // Default provider (standard Elasticsearch)\n const authConfig = authProvider\n ? undefined\n : config.getOptionalConfig('auth');\n const auth =\n authConfig &&\n (authConfig.has('apiKey')\n ? {\n apiKey: authConfig.getString('apiKey'),\n }\n : {\n username: authConfig.getString('username'),\n password: authConfig.getString('password'),\n });\n return {\n node: config.getString('node'),\n ...(auth ? { auth } : {}),\n ...(authProvider\n ? {\n Transport: createElasticSearchAuthTransport(authProvider),\n }\n : {}),\n ...(sslConfig\n ? {\n ssl: {\n rejectUnauthorized:\n sslConfig?.getOptionalBoolean('rejectUnauthorized'),\n },\n }\n : {}),\n };\n }\n\n private static readIndexTemplateConfig(\n config: Config,\n ): ElasticSearchCustomIndexTemplate[] {\n return (\n config.getOptionalConfigArray('indexTemplates')?.map(templateConfig => {\n const bodyConfig = templateConfig.getConfig('body');\n return {\n name: templateConfig.getString('name'),\n body: {\n index_patterns: bodyConfig.getStringArray('index_patterns'),\n composed_of: bodyConfig.getOptionalStringArray('composed_of'),\n template: bodyConfig.getOptionalConfig('template')?.get(),\n },\n };\n }) ?? []\n );\n }\n}\n\n/**\n * @public\n */\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["isEmpty","isNumber","nan","ElasticSearchClientWrapper","uuid","DefaultAwsCredentialsManager","esb","ElasticSearchSearchEngineIndexer","MissingIndexError","authConfig","createElasticSearchAuthTransport","RequestSigner","AwsSigv4Signer","createOpenSearchAuthTransport"],"mappings":"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"}
|
|
@@ -47,7 +47,7 @@ class ElasticSearchSearchEngineIndexer extends pluginSearchBackendNode.BatchSear
|
|
|
47
47
|
...options.batchKeyField && doc[options.batchKeyField] ? { _id: doc[options.batchKeyField] } : {}
|
|
48
48
|
};
|
|
49
49
|
},
|
|
50
|
-
refreshOnCompletion: options.skipRefresh !== true
|
|
50
|
+
refreshOnCompletion: options.skipRefresh !== true ? that.indexName : false
|
|
51
51
|
});
|
|
52
52
|
this.bulkResult.catch((e) => {
|
|
53
53
|
this.bulkClientError = e;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElasticSearchSearchEngineIndexer.cjs.js","sources":["../../src/engines/ElasticSearchSearchEngineIndexer.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';\nimport { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Readable } from 'node:stream';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Options for instantiate ElasticSearchSearchEngineIndexer\n * @public\n */\nexport type ElasticSearchSearchEngineIndexerOptions = {\n type: string;\n indexPrefix: string;\n indexSeparator: string;\n alias: string;\n logger: LoggerService;\n elasticSearchClientWrapper: ElasticSearchClientWrapper;\n batchSize: number;\n batchKeyField?: string;\n skipRefresh?: boolean;\n};\n\nfunction duration(startTimestamp: [number, number]): string {\n const delta = process.hrtime(startTimestamp);\n const seconds = delta[0] + delta[1] / 1e9;\n return `${seconds.toFixed(1)}s`;\n}\n\n/**\n * Elasticsearch specific search engine indexer.\n * @public\n */\nexport class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {\n private processed: number = 0;\n private removableIndices: string[] = [];\n\n private readonly startTimestamp: [number, number];\n private readonly type: string;\n public readonly indexName: string;\n private readonly indexPrefix: string;\n private readonly indexSeparator: string;\n private readonly alias: string;\n private readonly logger: LoggerService;\n private readonly sourceStream: Readable;\n private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper;\n private configuredBatchSize: number;\n private bulkResult: Promise<any>;\n private bulkClientError?: Error;\n\n constructor(options: ElasticSearchSearchEngineIndexerOptions) {\n super({ batchSize: options.batchSize });\n this.configuredBatchSize = options.batchSize;\n this.logger = options.logger.child({ documentType: options.type });\n this.startTimestamp = process.hrtime();\n this.type = options.type;\n this.indexPrefix = options.indexPrefix;\n this.indexSeparator = options.indexSeparator;\n this.indexName = this.constructIndexName(`${Date.now()}`);\n this.alias = options.alias;\n this.elasticSearchClientWrapper = options.elasticSearchClientWrapper;\n\n // The ES client bulk helper supports stream-based indexing, but we have to\n // supply the stream directly to it at instantiation-time. We can't supply\n // this class itself, so instead, we create this inline stream instead.\n this.sourceStream = new Readable({ objectMode: true });\n this.sourceStream._read = () => {};\n\n // eslint-disable-next-line consistent-this\n const that = this;\n\n // Keep a reference to the ES Bulk helper so that we can know when all\n // documents have been successfully written to ES.\n this.bulkResult = this.elasticSearchClientWrapper.bulk({\n datasource: this.sourceStream,\n onDocument(doc) {\n that.processed++;\n return {\n index: { _index: that.indexName },\n ...(options.batchKeyField && doc[options.batchKeyField]\n ? { _id: doc[options.batchKeyField] }\n : {}),\n };\n },\n refreshOnCompletion: options.skipRefresh !== true,\n });\n\n // Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts\n this.bulkResult.catch(e => {\n this.bulkClientError = e;\n });\n }\n\n async initialize(): Promise<void> {\n this.logger.info(`Started indexing documents for index ${this.type}`);\n\n const indices = await this.elasticSearchClientWrapper.listIndices({\n index: this.constructIndexName('*'),\n });\n\n for (const key of Object.keys(indices.body)) {\n this.removableIndices.push(key);\n }\n\n await this.elasticSearchClientWrapper.createIndex({\n index: this.indexName,\n });\n }\n\n async index(documents: IndexableDocument[]): Promise<void> {\n await this.isReady();\n documents.forEach(document => {\n this.sourceStream.push(document);\n });\n }\n\n async finalize(): Promise<void> {\n // Wait for all documents to be processed.\n await this.isReady();\n\n // Close off the underlying stream connected to ES, indicating that no more\n // documents will be written.\n this.sourceStream.push(null);\n\n // Wait for the bulk helper to finish processing.\n const result = await this.bulkResult;\n\n // Warn that no documents were indexed, early return so that alias swapping\n // does not occur, and clean up the empty index we just created.\n if (this.processed === 0) {\n this.logger.warn(\n `Index for ${this.indexName} of ${this.type} was not ${\n this.removableIndices.length ? 'replaced' : 'created'\n }: indexer received 0 documents`,\n );\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: this.indexName,\n });\n } catch (error) {\n this.logger.error(`Unable to clean up elastic index: ${error}`);\n }\n return;\n }\n\n // Rotate main alias upon completion. Apply permanent secondary alias so\n // stale indices can be referenced for deletion in case initial attempt\n // fails. Allow errors to bubble up so that we can clean up the created index.\n this.logger.info(\n `Indexing completed for index ${this.indexName} of ${\n this.type\n } in ${duration(this.startTimestamp)}`,\n result,\n );\n await this.elasticSearchClientWrapper.updateAliases({\n actions: [\n {\n remove: { index: this.constructIndexName('*'), alias: this.alias },\n },\n {\n add: { index: this.indexName, alias: this.alias },\n },\n ].filter(Boolean),\n });\n\n // If any indices are removable, remove them. Do not bubble up this error,\n // as doing so would delete the now aliased index. Log instead.\n if (this.removableIndices.length) {\n this.logger.info('Removing stale search indices', {\n removableIndices: this.removableIndices,\n });\n\n // Split the array into chunks of up to 50 indices to handle the case\n // where we need to delete a lot of stalled indices\n const chunks = this.removableIndices.reduce(\n (resultArray, item, index) => {\n const chunkIndex = Math.floor(index / 50);\n\n if (!resultArray[chunkIndex]) {\n resultArray[chunkIndex] = []; // start a new chunk\n }\n\n resultArray[chunkIndex].push(item);\n\n return resultArray;\n },\n [] as string[][],\n );\n\n // Call deleteIndex for each chunk\n for (const chunk of chunks) {\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: chunk,\n });\n } catch (e) {\n this.logger.warn(`Failed to remove stale search indices: ${e}`);\n }\n }\n }\n }\n\n /**\n * Ensures that the number of documents sent over the wire to ES matches the\n * number of documents this stream has received so far. This helps manage\n * backpressure in other parts of the indexing pipeline.\n */\n private isReady(): Promise<void> {\n // Early exit if the underlying ES client encountered an error.\n if (this.bulkClientError) {\n return Promise.reject(this.bulkClientError);\n }\n\n // Optimization: if the stream that ES reads from has fewer docs queued\n // than the configured batch size, continue early to allow more docs to be\n // queued\n if (this.sourceStream.readableLength < this.configuredBatchSize) {\n return Promise.resolve();\n }\n\n // Otherwise, continue periodically checking the stream queue to see if\n // ES has consumed the documents and continue when it's ready for more.\n return new Promise((isReady, abort) => {\n let streamLengthChecks = 0;\n const interval = setInterval(() => {\n streamLengthChecks++;\n\n if (this.sourceStream.readableLength < this.configuredBatchSize) {\n clearInterval(interval);\n isReady();\n }\n\n // Do not allow this interval to loop endlessly; anything longer than 5\n // minutes likely indicates an unrecoverable error in ES; direct the\n // user to inspect ES logs for more clues and abort in order to allow\n // the index to be cleaned up.\n if (streamLengthChecks >= 6000) {\n clearInterval(interval);\n abort(\n new Error(\n 'Exceeded 5 minutes waiting for elastic to be ready to accept more documents. Check the elastic logs for possible problems.',\n ),\n );\n }\n }, 50);\n });\n }\n\n private constructIndexName(postFix: string) {\n return `${this.indexPrefix}${this.type}${this.indexSeparator}${postFix}`;\n }\n}\n"],"names":["BatchSearchEngineIndexer","Readable"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ElasticSearchSearchEngineIndexer.cjs.js","sources":["../../src/engines/ElasticSearchSearchEngineIndexer.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';\nimport { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Readable } from 'node:stream';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Options for instantiate ElasticSearchSearchEngineIndexer\n * @public\n */\nexport type ElasticSearchSearchEngineIndexerOptions = {\n type: string;\n indexPrefix: string;\n indexSeparator: string;\n alias: string;\n logger: LoggerService;\n elasticSearchClientWrapper: ElasticSearchClientWrapper;\n batchSize: number;\n batchKeyField?: string;\n skipRefresh?: boolean;\n};\n\nfunction duration(startTimestamp: [number, number]): string {\n const delta = process.hrtime(startTimestamp);\n const seconds = delta[0] + delta[1] / 1e9;\n return `${seconds.toFixed(1)}s`;\n}\n\n/**\n * Elasticsearch specific search engine indexer.\n * @public\n */\nexport class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {\n private processed: number = 0;\n private removableIndices: string[] = [];\n\n private readonly startTimestamp: [number, number];\n private readonly type: string;\n public readonly indexName: string;\n private readonly indexPrefix: string;\n private readonly indexSeparator: string;\n private readonly alias: string;\n private readonly logger: LoggerService;\n private readonly sourceStream: Readable;\n private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper;\n private configuredBatchSize: number;\n private bulkResult: Promise<any>;\n private bulkClientError?: Error;\n\n constructor(options: ElasticSearchSearchEngineIndexerOptions) {\n super({ batchSize: options.batchSize });\n this.configuredBatchSize = options.batchSize;\n this.logger = options.logger.child({ documentType: options.type });\n this.startTimestamp = process.hrtime();\n this.type = options.type;\n this.indexPrefix = options.indexPrefix;\n this.indexSeparator = options.indexSeparator;\n this.indexName = this.constructIndexName(`${Date.now()}`);\n this.alias = options.alias;\n this.elasticSearchClientWrapper = options.elasticSearchClientWrapper;\n\n // The ES client bulk helper supports stream-based indexing, but we have to\n // supply the stream directly to it at instantiation-time. We can't supply\n // this class itself, so instead, we create this inline stream instead.\n this.sourceStream = new Readable({ objectMode: true });\n this.sourceStream._read = () => {};\n\n // eslint-disable-next-line consistent-this\n const that = this;\n\n // Keep a reference to the ES Bulk helper so that we can know when all\n // documents have been successfully written to ES.\n this.bulkResult = this.elasticSearchClientWrapper.bulk({\n datasource: this.sourceStream,\n onDocument(doc) {\n that.processed++;\n return {\n index: { _index: that.indexName },\n ...(options.batchKeyField && doc[options.batchKeyField]\n ? { _id: doc[options.batchKeyField] }\n : {}),\n };\n },\n refreshOnCompletion:\n options.skipRefresh !== true ? that.indexName : false,\n });\n\n // Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts\n this.bulkResult.catch(e => {\n this.bulkClientError = e;\n });\n }\n\n async initialize(): Promise<void> {\n this.logger.info(`Started indexing documents for index ${this.type}`);\n\n const indices = await this.elasticSearchClientWrapper.listIndices({\n index: this.constructIndexName('*'),\n });\n\n for (const key of Object.keys(indices.body)) {\n this.removableIndices.push(key);\n }\n\n await this.elasticSearchClientWrapper.createIndex({\n index: this.indexName,\n });\n }\n\n async index(documents: IndexableDocument[]): Promise<void> {\n await this.isReady();\n documents.forEach(document => {\n this.sourceStream.push(document);\n });\n }\n\n async finalize(): Promise<void> {\n // Wait for all documents to be processed.\n await this.isReady();\n\n // Close off the underlying stream connected to ES, indicating that no more\n // documents will be written.\n this.sourceStream.push(null);\n\n // Wait for the bulk helper to finish processing.\n const result = await this.bulkResult;\n\n // Warn that no documents were indexed, early return so that alias swapping\n // does not occur, and clean up the empty index we just created.\n if (this.processed === 0) {\n this.logger.warn(\n `Index for ${this.indexName} of ${this.type} was not ${\n this.removableIndices.length ? 'replaced' : 'created'\n }: indexer received 0 documents`,\n );\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: this.indexName,\n });\n } catch (error) {\n this.logger.error(`Unable to clean up elastic index: ${error}`);\n }\n return;\n }\n\n // Rotate main alias upon completion. Apply permanent secondary alias so\n // stale indices can be referenced for deletion in case initial attempt\n // fails. Allow errors to bubble up so that we can clean up the created index.\n this.logger.info(\n `Indexing completed for index ${this.indexName} of ${\n this.type\n } in ${duration(this.startTimestamp)}`,\n result,\n );\n await this.elasticSearchClientWrapper.updateAliases({\n actions: [\n {\n remove: { index: this.constructIndexName('*'), alias: this.alias },\n },\n {\n add: { index: this.indexName, alias: this.alias },\n },\n ].filter(Boolean),\n });\n\n // If any indices are removable, remove them. Do not bubble up this error,\n // as doing so would delete the now aliased index. Log instead.\n if (this.removableIndices.length) {\n this.logger.info('Removing stale search indices', {\n removableIndices: this.removableIndices,\n });\n\n // Split the array into chunks of up to 50 indices to handle the case\n // where we need to delete a lot of stalled indices\n const chunks = this.removableIndices.reduce(\n (resultArray, item, index) => {\n const chunkIndex = Math.floor(index / 50);\n\n if (!resultArray[chunkIndex]) {\n resultArray[chunkIndex] = []; // start a new chunk\n }\n\n resultArray[chunkIndex].push(item);\n\n return resultArray;\n },\n [] as string[][],\n );\n\n // Call deleteIndex for each chunk\n for (const chunk of chunks) {\n try {\n await this.elasticSearchClientWrapper.deleteIndex({\n index: chunk,\n });\n } catch (e) {\n this.logger.warn(`Failed to remove stale search indices: ${e}`);\n }\n }\n }\n }\n\n /**\n * Ensures that the number of documents sent over the wire to ES matches the\n * number of documents this stream has received so far. This helps manage\n * backpressure in other parts of the indexing pipeline.\n */\n private isReady(): Promise<void> {\n // Early exit if the underlying ES client encountered an error.\n if (this.bulkClientError) {\n return Promise.reject(this.bulkClientError);\n }\n\n // Optimization: if the stream that ES reads from has fewer docs queued\n // than the configured batch size, continue early to allow more docs to be\n // queued\n if (this.sourceStream.readableLength < this.configuredBatchSize) {\n return Promise.resolve();\n }\n\n // Otherwise, continue periodically checking the stream queue to see if\n // ES has consumed the documents and continue when it's ready for more.\n return new Promise((isReady, abort) => {\n let streamLengthChecks = 0;\n const interval = setInterval(() => {\n streamLengthChecks++;\n\n if (this.sourceStream.readableLength < this.configuredBatchSize) {\n clearInterval(interval);\n isReady();\n }\n\n // Do not allow this interval to loop endlessly; anything longer than 5\n // minutes likely indicates an unrecoverable error in ES; direct the\n // user to inspect ES logs for more clues and abort in order to allow\n // the index to be cleaned up.\n if (streamLengthChecks >= 6000) {\n clearInterval(interval);\n abort(\n new Error(\n 'Exceeded 5 minutes waiting for elastic to be ready to accept more documents. Check the elastic logs for possible problems.',\n ),\n );\n }\n }, 50);\n });\n }\n\n private constructIndexName(postFix: string) {\n return `${this.indexPrefix}${this.type}${this.indexSeparator}${postFix}`;\n }\n}\n"],"names":["BatchSearchEngineIndexer","Readable"],"mappings":";;;;;AAsCA,SAAS,SAAS,cAAA,EAA0C;AAC1D,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,MAAA,CAAO,cAAc,CAAA;AAC3C,EAAA,MAAM,UAAU,KAAA,CAAM,CAAC,CAAA,GAAI,KAAA,CAAM,CAAC,CAAA,GAAI,GAAA;AACtC,EAAA,OAAO,CAAA,EAAG,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAC9B;AAMO,MAAM,yCAAyCA,gDAAA,CAAyB;AAAA,EACrE,SAAA,GAAoB,CAAA;AAAA,EACpB,mBAA6B,EAAC;AAAA,EAErB,cAAA;AAAA,EACA,IAAA;AAAA,EACD,SAAA;AAAA,EACC,WAAA;AAAA,EACA,cAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,YAAA;AAAA,EACA,0BAAA;AAAA,EACT,mBAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EAER,YAAY,OAAA,EAAkD;AAC5D,IAAA,KAAA,CAAM,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAW,CAAA;AACtC,IAAA,IAAA,CAAK,sBAAsB,OAAA,CAAQ,SAAA;AACnC,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,MAAA,CAAO,KAAA,CAAM,EAAE,YAAA,EAAc,OAAA,CAAQ,MAAM,CAAA;AACjE,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAQ,MAAA,EAAO;AACrC,IAAA,IAAA,CAAK,OAAO,OAAA,CAAQ,IAAA;AACpB,IAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,WAAA;AAC3B,IAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAC9B,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK,kBAAA,CAAmB,GAAG,IAAA,CAAK,GAAA,EAAK,CAAA,CAAE,CAAA;AACxD,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,6BAA6B,OAAA,CAAQ,0BAAA;AAK1C,IAAA,IAAA,CAAK,eAAe,IAAIC,oBAAA,CAAS,EAAE,UAAA,EAAY,MAAM,CAAA;AACrD,IAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,MAAM;AAAA,IAAC,CAAA;AAGjC,IAAA,MAAM,IAAA,GAAO,IAAA;AAIb,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,0BAAA,CAA2B,IAAA,CAAK;AAAA,MACrD,YAAY,IAAA,CAAK,YAAA;AAAA,MACjB,WAAW,GAAA,EAAK;AACd,QAAA,IAAA,CAAK,SAAA,EAAA;AACL,QAAA,OAAO;AAAA,UACL,KAAA,EAAO,EAAE,MAAA,EAAQ,IAAA,CAAK,SAAA,EAAU;AAAA,UAChC,GAAI,OAAA,CAAQ,aAAA,IAAiB,GAAA,CAAI,QAAQ,aAAa,CAAA,GAClD,EAAE,GAAA,EAAK,GAAA,CAAI,OAAA,CAAQ,aAAa,CAAA,KAChC;AAAC,SACP;AAAA,MACF,CAAA;AAAA,MACA,mBAAA,EACE,OAAA,CAAQ,WAAA,KAAgB,IAAA,GAAO,KAAK,SAAA,GAAY;AAAA,KACnD,CAAA;AAGD,IAAA,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,CAAA,KAAK;AACzB,MAAA,IAAA,CAAK,eAAA,GAAkB,CAAA;AAAA,IACzB,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,UAAA,GAA4B;AAChC,IAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,qCAAA,EAAwC,IAAA,CAAK,IAAI,CAAA,CAAE,CAAA;AAEpE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,0BAAA,CAA2B,WAAA,CAAY;AAAA,MAChE,KAAA,EAAO,IAAA,CAAK,kBAAA,CAAmB,GAAG;AAAA,KACnC,CAAA;AAED,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC3C,MAAA,IAAA,CAAK,gBAAA,CAAiB,KAAK,GAAG,CAAA;AAAA,IAChC;AAEA,IAAA,MAAM,IAAA,CAAK,2BAA2B,WAAA,CAAY;AAAA,MAChD,OAAO,IAAA,CAAK;AAAA,KACb,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,SAAA,EAA+C;AACzD,IAAA,MAAM,KAAK,OAAA,EAAQ;AACnB,IAAA,SAAA,CAAU,QAAQ,CAAA,QAAA,KAAY;AAC5B,MAAA,IAAA,CAAK,YAAA,CAAa,KAAK,QAAQ,CAAA;AAAA,IACjC,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,QAAA,GAA0B;AAE9B,IAAA,MAAM,KAAK,OAAA,EAAQ;AAInB,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,IAAI,CAAA;AAG3B,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,UAAA;AAI1B,IAAA,IAAI,IAAA,CAAK,cAAc,CAAA,EAAG;AACxB,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,CAAA,UAAA,EAAa,IAAA,CAAK,SAAS,CAAA,IAAA,EAAO,IAAA,CAAK,IAAI,CAAA,SAAA,EACzC,IAAA,CAAK,gBAAA,CAAiB,MAAA,GAAS,UAAA,GAAa,SAC9C,CAAA,8BAAA;AAAA,OACF;AACA,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,2BAA2B,WAAA,CAAY;AAAA,UAChD,OAAO,IAAA,CAAK;AAAA,SACb,CAAA;AAAA,MACH,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,kCAAA,EAAqC,KAAK,CAAA,CAAE,CAAA;AAAA,MAChE;AACA,MAAA;AAAA,IACF;AAKA,IAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,MACV,CAAA,6BAAA,EAAgC,IAAA,CAAK,SAAS,CAAA,IAAA,EAC5C,IAAA,CAAK,IACP,CAAA,IAAA,EAAO,QAAA,CAAS,IAAA,CAAK,cAAc,CAAC,CAAA,CAAA;AAAA,MACpC;AAAA,KACF;AACA,IAAA,MAAM,IAAA,CAAK,2BAA2B,aAAA,CAAc;AAAA,MAClD,OAAA,EAAS;AAAA,QACP;AAAA,UACE,MAAA,EAAQ,EAAE,KAAA,EAAO,IAAA,CAAK,mBAAmB,GAAG,CAAA,EAAG,KAAA,EAAO,IAAA,CAAK,KAAA;AAAM,SACnE;AAAA,QACA;AAAA,UACE,KAAK,EAAE,KAAA,EAAO,KAAK,SAAA,EAAW,KAAA,EAAO,KAAK,KAAA;AAAM;AAClD,OACF,CAAE,OAAO,OAAO;AAAA,KACjB,CAAA;AAID,IAAA,IAAI,IAAA,CAAK,iBAAiB,MAAA,EAAQ;AAChC,MAAA,IAAA,CAAK,MAAA,CAAO,KAAK,+BAAA,EAAiC;AAAA,QAChD,kBAAkB,IAAA,CAAK;AAAA,OACxB,CAAA;AAID,MAAA,MAAM,MAAA,GAAS,KAAK,gBAAA,CAAiB,MAAA;AAAA,QACnC,CAAC,WAAA,EAAa,IAAA,EAAM,KAAA,KAAU;AAC5B,UAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,EAAE,CAAA;AAExC,UAAA,IAAI,CAAC,WAAA,CAAY,UAAU,CAAA,EAAG;AAC5B,YAAA,WAAA,CAAY,UAAU,IAAI,EAAC;AAAA,UAC7B;AAEA,UAAA,WAAA,CAAY,UAAU,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAEjC,UAAA,OAAO,WAAA;AAAA,QACT,CAAA;AAAA,QACA;AAAC,OACH;AAGA,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,CAAK,2BAA2B,WAAA,CAAY;AAAA,YAChD,KAAA,EAAO;AAAA,WACR,CAAA;AAAA,QACH,SAAS,CAAA,EAAG;AACV,UAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,uCAAA,EAA0C,CAAC,CAAA,CAAE,CAAA;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,OAAA,GAAyB;AAE/B,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA;AAAA,IAC5C;AAKA,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,cAAA,GAAiB,IAAA,CAAK,mBAAA,EAAqB;AAC/D,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB;AAIA,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,KAAA,KAAU;AACrC,MAAA,IAAI,kBAAA,GAAqB,CAAA;AACzB,MAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AACjC,QAAA,kBAAA,EAAA;AAEA,QAAA,IAAI,IAAA,CAAK,YAAA,CAAa,cAAA,GAAiB,IAAA,CAAK,mBAAA,EAAqB;AAC/D,UAAA,aAAA,CAAc,QAAQ,CAAA;AACtB,UAAA,OAAA,EAAQ;AAAA,QACV;AAMA,QAAA,IAAI,sBAAsB,GAAA,EAAM;AAC9B,UAAA,aAAA,CAAc,QAAQ,CAAA;AACtB,UAAA,KAAA;AAAA,YACE,IAAI,KAAA;AAAA,cACF;AAAA;AACF,WACF;AAAA,QACF;AAAA,MACF,GAAG,EAAE,CAAA;AAAA,IACP,CAAC,CAAA;AAAA,EACH;AAAA,EAEQ,mBAAmB,OAAA,EAAiB;AAC1C,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,WAAW,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,cAAc,CAAA,EAAG,OAAO,CAAA,CAAA;AAAA,EACxE;AACF;;;;"}
|
package/dist/index.cjs.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var module$1 = require('./module.cjs.js');
|
|
6
6
|
var ElasticSearchSearchEngine = require('./engines/ElasticSearchSearchEngine.cjs.js');
|
|
7
7
|
var ElasticSearchClientOptions = require('./engines/ElasticSearchClientOptions.cjs.js');
|
|
8
|
+
var auth = require('./auth.cjs.js');
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
|
|
@@ -13,4 +14,5 @@ exports.elasticsearchTranslatorExtensionPoint = module$1.elasticsearchTranslator
|
|
|
13
14
|
exports.ElasticSearchSearchEngine = ElasticSearchSearchEngine.ElasticSearchSearchEngine;
|
|
14
15
|
exports.decodeElasticSearchPageCursor = ElasticSearchSearchEngine.decodePageCursor;
|
|
15
16
|
exports.isOpenSearchCompatible = ElasticSearchClientOptions.isOpenSearchCompatible;
|
|
17
|
+
exports.elasticsearchAuthExtensionPoint = auth.elasticsearchAuthExtensionPoint;
|
|
16
18
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -357,6 +357,101 @@ declare class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer
|
|
|
357
357
|
private constructIndexName;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* A provider that supplies authentication headers for Elasticsearch/OpenSearch requests.
|
|
362
|
+
*
|
|
363
|
+
* @remarks
|
|
364
|
+
*
|
|
365
|
+
* This interface allows for dynamic authentication mechanisms such as bearer tokens
|
|
366
|
+
* that need to be refreshed or rotated. The `getAuthHeaders` method is called before
|
|
367
|
+
* each request to the Elasticsearch/OpenSearch cluster, allowing for just-in-time
|
|
368
|
+
* token retrieval and automatic rotation.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
*
|
|
372
|
+
* ```ts
|
|
373
|
+
* const authProvider: ElasticSearchAuthProvider = {
|
|
374
|
+
* async getAuthHeaders() {
|
|
375
|
+
* const token = await myTokenService.getToken();
|
|
376
|
+
* return { Authorization: `Bearer ${token}` };
|
|
377
|
+
* },
|
|
378
|
+
* };
|
|
379
|
+
* ```
|
|
380
|
+
*
|
|
381
|
+
* @public
|
|
382
|
+
*/
|
|
383
|
+
interface ElasticSearchAuthProvider {
|
|
384
|
+
/**
|
|
385
|
+
* Returns authentication headers to be included in requests to Elasticsearch/OpenSearch.
|
|
386
|
+
*
|
|
387
|
+
* @remarks
|
|
388
|
+
*
|
|
389
|
+
* This method is called before each request, allowing for dynamic token refresh
|
|
390
|
+
* and rotation. Implementations should handle caching internally if needed to
|
|
391
|
+
* avoid excessive token generation.
|
|
392
|
+
*
|
|
393
|
+
* @returns A promise that resolves to a record of header names and values
|
|
394
|
+
*/
|
|
395
|
+
getAuthHeaders(): Promise<Record<string, string>>;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Extension point for providing custom authentication to the Elasticsearch search engine.
|
|
399
|
+
*
|
|
400
|
+
* @remarks
|
|
401
|
+
*
|
|
402
|
+
* Use this extension point to provide dynamic authentication mechanisms such as
|
|
403
|
+
* bearer tokens with automatic rotation. When an auth provider is set, it takes
|
|
404
|
+
* precedence over any static authentication configured in app-config.yaml.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
*
|
|
408
|
+
* ```ts
|
|
409
|
+
* import { createBackendModule } from '@backstage/backend-plugin-api';
|
|
410
|
+
* import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch';
|
|
411
|
+
*
|
|
412
|
+
* export default createBackendModule({
|
|
413
|
+
* pluginId: 'search',
|
|
414
|
+
* moduleId: 'elasticsearch-custom-auth',
|
|
415
|
+
* register(env) {
|
|
416
|
+
* env.registerInit({
|
|
417
|
+
* deps: {
|
|
418
|
+
* elasticsearchAuth: elasticsearchAuthExtensionPoint,
|
|
419
|
+
* },
|
|
420
|
+
* async init({ elasticsearchAuth }) {
|
|
421
|
+
* elasticsearchAuth.setAuthProvider({
|
|
422
|
+
* async getAuthHeaders() {
|
|
423
|
+
* const token = await fetchTokenFromIdentityService();
|
|
424
|
+
* return { Authorization: `Bearer ${token}` };
|
|
425
|
+
* },
|
|
426
|
+
* });
|
|
427
|
+
* },
|
|
428
|
+
* });
|
|
429
|
+
* },
|
|
430
|
+
* });
|
|
431
|
+
* ```
|
|
432
|
+
*
|
|
433
|
+
* @public
|
|
434
|
+
*/
|
|
435
|
+
interface ElasticSearchAuthExtensionPoint {
|
|
436
|
+
/**
|
|
437
|
+
* Sets the authentication provider for the Elasticsearch search engine.
|
|
438
|
+
*
|
|
439
|
+
* @remarks
|
|
440
|
+
*
|
|
441
|
+
* This method can only be called once. Subsequent calls will throw an error.
|
|
442
|
+
* The auth provider takes precedence over static authentication configuration.
|
|
443
|
+
*
|
|
444
|
+
* @param provider - The authentication provider to use
|
|
445
|
+
*/
|
|
446
|
+
setAuthProvider(provider: ElasticSearchAuthProvider): void;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Extension point used to customize Elasticsearch/OpenSearch authentication.
|
|
450
|
+
*
|
|
451
|
+
* @public
|
|
452
|
+
*/
|
|
453
|
+
declare const elasticsearchAuthExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<ElasticSearchAuthExtensionPoint>;
|
|
454
|
+
|
|
360
455
|
/**
|
|
361
456
|
* Search query that the elasticsearch engine understands.
|
|
362
457
|
* @public
|
|
@@ -389,6 +484,19 @@ type ElasticSearchOptions = {
|
|
|
389
484
|
aliasPostfix?: string;
|
|
390
485
|
indexPrefix?: string;
|
|
391
486
|
translator?: ElasticSearchQueryTranslator;
|
|
487
|
+
/**
|
|
488
|
+
* An optional authentication provider for dynamic authentication.
|
|
489
|
+
*
|
|
490
|
+
* @remarks
|
|
491
|
+
*
|
|
492
|
+
* When provided, this auth provider will be used to inject authentication
|
|
493
|
+
* headers into each request to Elasticsearch/OpenSearch. This is useful
|
|
494
|
+
* for scenarios requiring dynamic tokens (e.g., bearer tokens with rotation).
|
|
495
|
+
*
|
|
496
|
+
* The auth provider takes precedence over static authentication configured
|
|
497
|
+
* in app-config.yaml.
|
|
498
|
+
*/
|
|
499
|
+
authProvider?: ElasticSearchAuthProvider;
|
|
392
500
|
};
|
|
393
501
|
/**
|
|
394
502
|
* @public
|
|
@@ -486,5 +594,5 @@ declare const elasticsearchTranslatorExtensionPoint: _backstage_backend_plugin_a
|
|
|
486
594
|
*/
|
|
487
595
|
declare const _default: _backstage_backend_plugin_api.BackendFeature;
|
|
488
596
|
|
|
489
|
-
export { ElasticSearchClientWrapper, ElasticSearchSearchEngine, ElasticSearchSearchEngineIndexer, decodePageCursor as decodeElasticSearchPageCursor, _default as default, elasticsearchTranslatorExtensionPoint, isOpenSearchCompatible };
|
|
490
|
-
export type { BaseElasticSearchClientOptions, ElasticSearchAgentOptions, ElasticSearchAliasAction, ElasticSearchAuth, ElasticSearchClientOptions, ElasticSearchConcreteQuery, ElasticSearchConnectionConstructor, ElasticSearchCustomIndexTemplate, ElasticSearchCustomIndexTemplateBody, ElasticSearchElasticSearchClientOptions, ElasticSearchHighlightConfig, ElasticSearchHighlightOptions, ElasticSearchIndexAction, ElasticSearchNodeOptions, ElasticSearchOptions, ElasticSearchQueryConfig, ElasticSearchQueryTranslator, ElasticSearchQueryTranslatorExtensionPoint, ElasticSearchQueryTranslatorOptions, ElasticSearchSearchEngineIndexerOptions, ElasticSearchTransportConstructor, OpenSearchAuth, OpenSearchConnectionConstructor, OpenSearchElasticSearchClientOptions, OpenSearchNodeOptions };
|
|
597
|
+
export { ElasticSearchClientWrapper, ElasticSearchSearchEngine, ElasticSearchSearchEngineIndexer, decodePageCursor as decodeElasticSearchPageCursor, _default as default, elasticsearchAuthExtensionPoint, elasticsearchTranslatorExtensionPoint, isOpenSearchCompatible };
|
|
598
|
+
export type { BaseElasticSearchClientOptions, ElasticSearchAgentOptions, ElasticSearchAliasAction, ElasticSearchAuth, ElasticSearchAuthExtensionPoint, ElasticSearchAuthProvider, ElasticSearchClientOptions, ElasticSearchConcreteQuery, ElasticSearchConnectionConstructor, ElasticSearchCustomIndexTemplate, ElasticSearchCustomIndexTemplateBody, ElasticSearchElasticSearchClientOptions, ElasticSearchHighlightConfig, ElasticSearchHighlightOptions, ElasticSearchIndexAction, ElasticSearchNodeOptions, ElasticSearchOptions, ElasticSearchQueryConfig, ElasticSearchQueryTranslator, ElasticSearchQueryTranslatorExtensionPoint, ElasticSearchQueryTranslatorOptions, ElasticSearchSearchEngineIndexerOptions, ElasticSearchTransportConstructor, OpenSearchAuth, OpenSearchConnectionConstructor, OpenSearchElasticSearchClientOptions, OpenSearchNodeOptions };
|
package/dist/module.cjs.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
6
|
var alpha = require('@backstage/plugin-search-backend-node/alpha');
|
|
7
7
|
var ElasticSearchSearchEngine = require('./engines/ElasticSearchSearchEngine.cjs.js');
|
|
8
|
+
var auth = require('./auth.cjs.js');
|
|
8
9
|
|
|
9
10
|
const elasticsearchTranslatorExtensionPoint = backendPluginApi.createExtensionPoint({
|
|
10
11
|
id: "search.elasticsearchEngine.translator"
|
|
@@ -14,6 +15,7 @@ var feature = backendPluginApi.createBackendModule({
|
|
|
14
15
|
moduleId: "elasticsearch-engine",
|
|
15
16
|
register(env) {
|
|
16
17
|
let translator;
|
|
18
|
+
let authProvider;
|
|
17
19
|
env.registerExtensionPoint(elasticsearchTranslatorExtensionPoint, {
|
|
18
20
|
setTranslator(newTranslator) {
|
|
19
21
|
if (translator) {
|
|
@@ -24,6 +26,14 @@ var feature = backendPluginApi.createBackendModule({
|
|
|
24
26
|
translator = newTranslator;
|
|
25
27
|
}
|
|
26
28
|
});
|
|
29
|
+
env.registerExtensionPoint(auth.elasticsearchAuthExtensionPoint, {
|
|
30
|
+
setAuthProvider(provider) {
|
|
31
|
+
if (authProvider) {
|
|
32
|
+
throw new Error("ElasticSearch auth provider may only be set once");
|
|
33
|
+
}
|
|
34
|
+
authProvider = provider;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
27
37
|
env.registerInit({
|
|
28
38
|
deps: {
|
|
29
39
|
searchEngineRegistry: alpha.searchEngineRegistryExtensionPoint,
|
|
@@ -45,11 +55,17 @@ var feature = backendPluginApi.createBackendModule({
|
|
|
45
55
|
if (indexPrefix) {
|
|
46
56
|
logger.info(`Index prefix will be used for indices: ${indexPrefix}`);
|
|
47
57
|
}
|
|
58
|
+
if (authProvider) {
|
|
59
|
+
logger.info(
|
|
60
|
+
"Using custom auth provider for ElasticSearch authentication."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
48
63
|
searchEngineRegistry.setSearchEngine(
|
|
49
64
|
await ElasticSearchSearchEngine.ElasticSearchSearchEngine.fromConfig({
|
|
50
65
|
logger,
|
|
51
66
|
config,
|
|
52
67
|
translator,
|
|
68
|
+
authProvider,
|
|
53
69
|
indexPrefix
|
|
54
70
|
})
|
|
55
71
|
);
|
|
@@ -58,6 +74,7 @@ var feature = backendPluginApi.createBackendModule({
|
|
|
58
74
|
}
|
|
59
75
|
});
|
|
60
76
|
|
|
77
|
+
exports.elasticsearchAuthExtensionPoint = auth.elasticsearchAuthExtensionPoint;
|
|
61
78
|
exports.default = feature;
|
|
62
79
|
exports.elasticsearchTranslatorExtensionPoint = elasticsearchTranslatorExtensionPoint;
|
|
63
80
|
//# sourceMappingURL=module.cjs.js.map
|
package/dist/module.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n createExtensionPoint,\n} from '@backstage/backend-plugin-api';\nimport { searchEngineRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';\nimport {\n ElasticSearchQueryTranslator,\n ElasticSearchSearchEngine,\n} from './engines/ElasticSearchSearchEngine';\n\n/** @public */\nexport interface ElasticSearchQueryTranslatorExtensionPoint {\n setTranslator(translator: ElasticSearchQueryTranslator): void;\n}\n\n/**\n * Extension point used to customize the ElasticSearch query translator.\n *\n * @public\n */\nexport const elasticsearchTranslatorExtensionPoint =\n createExtensionPoint<ElasticSearchQueryTranslatorExtensionPoint>({\n id: 'search.elasticsearchEngine.translator',\n });\n\n/**\n * Search backend module for the Elasticsearch engine.\n *\n * @public\n */\nexport default createBackendModule({\n pluginId: 'search',\n moduleId: 'elasticsearch-engine',\n register(env) {\n let translator: ElasticSearchQueryTranslator | undefined;\n\n env.registerExtensionPoint(elasticsearchTranslatorExtensionPoint, {\n setTranslator(newTranslator) {\n if (translator) {\n throw new Error(\n 'ElasticSearch query translator may only be set once',\n );\n }\n translator = newTranslator;\n },\n });\n\n env.registerInit({\n deps: {\n searchEngineRegistry: searchEngineRegistryExtensionPoint,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n },\n async init({ searchEngineRegistry, logger, config }) {\n const baseKey = 'search.elasticsearch';\n const baseConfig = config.getOptional(baseKey);\n if (!baseConfig) {\n logger.warn(\n 'No configuration found under \"search.elasticsearch\" key. Skipping search engine initialization.',\n );\n return;\n }\n const indexPrefix = config.getOptionalString(\n 'search.elasticsearch.indexPrefix',\n );\n if (indexPrefix) {\n logger.info(`Index prefix will be used for indices: ${indexPrefix}`);\n }\n searchEngineRegistry.setSearchEngine(\n await ElasticSearchSearchEngine.fromConfig({\n logger,\n config,\n translator,\n indexPrefix,\n }),\n );\n },\n });\n },\n});\n"],"names":["createExtensionPoint","createBackendModule","searchEngineRegistryExtensionPoint","coreServices","ElasticSearchSearchEngine"],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n createExtensionPoint,\n} from '@backstage/backend-plugin-api';\nimport { searchEngineRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';\nimport {\n ElasticSearchQueryTranslator,\n ElasticSearchSearchEngine,\n} from './engines/ElasticSearchSearchEngine';\nimport {\n type ElasticSearchAuthProvider,\n type ElasticSearchAuthExtensionPoint,\n elasticsearchAuthExtensionPoint,\n} from './auth';\n\n// Re-export auth types and extension point\nexport type { ElasticSearchAuthProvider, ElasticSearchAuthExtensionPoint };\nexport { elasticsearchAuthExtensionPoint };\n\n/** @public */\nexport interface ElasticSearchQueryTranslatorExtensionPoint {\n setTranslator(translator: ElasticSearchQueryTranslator): void;\n}\n\n/**\n * Extension point used to customize the ElasticSearch query translator.\n *\n * @public\n */\nexport const elasticsearchTranslatorExtensionPoint =\n createExtensionPoint<ElasticSearchQueryTranslatorExtensionPoint>({\n id: 'search.elasticsearchEngine.translator',\n });\n\n/**\n * Search backend module for the Elasticsearch engine.\n *\n * @public\n */\nexport default createBackendModule({\n pluginId: 'search',\n moduleId: 'elasticsearch-engine',\n register(env) {\n let translator: ElasticSearchQueryTranslator | undefined;\n let authProvider: ElasticSearchAuthProvider | undefined;\n\n env.registerExtensionPoint(elasticsearchTranslatorExtensionPoint, {\n setTranslator(newTranslator) {\n if (translator) {\n throw new Error(\n 'ElasticSearch query translator may only be set once',\n );\n }\n translator = newTranslator;\n },\n });\n\n env.registerExtensionPoint(elasticsearchAuthExtensionPoint, {\n setAuthProvider(provider) {\n if (authProvider) {\n throw new Error('ElasticSearch auth provider may only be set once');\n }\n authProvider = provider;\n },\n });\n\n env.registerInit({\n deps: {\n searchEngineRegistry: searchEngineRegistryExtensionPoint,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n },\n async init({ searchEngineRegistry, logger, config }) {\n const baseKey = 'search.elasticsearch';\n const baseConfig = config.getOptional(baseKey);\n if (!baseConfig) {\n logger.warn(\n 'No configuration found under \"search.elasticsearch\" key. Skipping search engine initialization.',\n );\n return;\n }\n const indexPrefix = config.getOptionalString(\n 'search.elasticsearch.indexPrefix',\n );\n if (indexPrefix) {\n logger.info(`Index prefix will be used for indices: ${indexPrefix}`);\n }\n if (authProvider) {\n logger.info(\n 'Using custom auth provider for ElasticSearch authentication.',\n );\n }\n searchEngineRegistry.setSearchEngine(\n await ElasticSearchSearchEngine.fromConfig({\n logger,\n config,\n translator,\n authProvider,\n indexPrefix,\n }),\n );\n },\n });\n },\n});\n"],"names":["createExtensionPoint","createBackendModule","elasticsearchAuthExtensionPoint","searchEngineRegistryExtensionPoint","coreServices","ElasticSearchSearchEngine"],"mappings":";;;;;;;;;AA6CO,MAAM,wCACXA,qCAAA,CAAiE;AAAA,EAC/D,EAAA,EAAI;AACN,CAAC;AAOH,cAAeC,oCAAA,CAAoB;AAAA,EACjC,QAAA,EAAU,QAAA;AAAA,EACV,QAAA,EAAU,sBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,IAAI,UAAA;AACJ,IAAA,IAAI,YAAA;AAEJ,IAAA,GAAA,CAAI,uBAAuB,qCAAA,EAAuC;AAAA,MAChE,cAAc,aAAA,EAAe;AAC3B,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AACA,QAAA,UAAA,GAAa,aAAA;AAAA,MACf;AAAA,KACD,CAAA;AAED,IAAA,GAAA,CAAI,uBAAuBC,oCAAA,EAAiC;AAAA,MAC1D,gBAAgB,QAAA,EAAU;AACxB,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,QACpE;AACA,QAAA,YAAA,GAAe,QAAA;AAAA,MACjB;AAAA,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,oBAAA,EAAsBC,wCAAA;AAAA,QACtB,QAAQC,6BAAA,CAAa,MAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,oBAAA,EAAsB,MAAA,EAAQ,QAAO,EAAG;AACnD,QAAA,MAAM,OAAA,GAAU,sBAAA;AAChB,QAAA,MAAM,UAAA,GAAa,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA;AAC7C,QAAA,IAAI,CAAC,UAAA,EAAY;AACf,UAAA,MAAA,CAAO,IAAA;AAAA,YACL;AAAA,WACF;AACA,UAAA;AAAA,QACF;AACA,QAAA,MAAM,cAAc,MAAA,CAAO,iBAAA;AAAA,UACzB;AAAA,SACF;AACA,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,MAAA,CAAO,IAAA,CAAK,CAAA,uCAAA,EAA0C,WAAW,CAAA,CAAE,CAAA;AAAA,QACrE;AACA,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,MAAA,CAAO,IAAA;AAAA,YACL;AAAA,WACF;AAAA,QACF;AACA,QAAA,oBAAA,CAAqB,eAAA;AAAA,UACnB,MAAMC,oDAA0B,UAAA,CAAW;AAAA,YACzC,MAAA;AAAA,YACA,MAAA;AAAA,YACA,UAAA;AAAA,YACA,YAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC,CAAA;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-search-backend-module-elasticsearch",
|
|
3
|
-
"version": "0.0.0-nightly-
|
|
3
|
+
"version": "0.0.0-nightly-20260203031143",
|
|
4
4
|
"description": "A module for the search backend that implements search using ElasticSearch",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"test": "backstage-cli package test"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@backstage/backend-plugin-api": "0.0.0-nightly-
|
|
65
|
+
"@backstage/backend-plugin-api": "0.0.0-nightly-20260203031143",
|
|
66
66
|
"@backstage/config": "1.3.6",
|
|
67
|
-
"@backstage/integration-aws-node": "0.0.0-nightly-
|
|
68
|
-
"@backstage/plugin-search-backend-node": "0.0.0-nightly-
|
|
69
|
-
"@backstage/plugin-search-common": "0.0.0-nightly-
|
|
67
|
+
"@backstage/integration-aws-node": "0.0.0-nightly-20260203031143",
|
|
68
|
+
"@backstage/plugin-search-backend-node": "0.0.0-nightly-20260203031143",
|
|
69
|
+
"@backstage/plugin-search-common": "0.0.0-nightly-20260203031143",
|
|
70
70
|
"@elastic/elasticsearch": "^7.13.0",
|
|
71
71
|
"@opensearch-project/opensearch": "^2.2.1",
|
|
72
72
|
"aws4": "^1.12.0",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"uuid": "^11.0.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@backstage/backend-test-utils": "0.0.0-nightly-
|
|
79
|
-
"@backstage/cli": "0.0.0-nightly-
|
|
78
|
+
"@backstage/backend-test-utils": "0.0.0-nightly-20260203031143",
|
|
79
|
+
"@backstage/cli": "0.0.0-nightly-20260203031143",
|
|
80
80
|
"@elastic/elasticsearch-mock": "^1.0.0",
|
|
81
81
|
"@short.io/opensearch-mock": "^0.4.0",
|
|
82
82
|
"@types/aws4": "^1.5.1"
|