Skip to content

Commit c3f99e2

Browse files
[FSH] Moved package_installer to @kbn/fs usage (#245664)
## Summary Moved package_installer to `@kbn/fs` usage. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit dce7e81)
1 parent 6829c1f commit c3f99e2

9 files changed

Lines changed: 60 additions & 54 deletions

File tree

x-pack/platform/packages/shared/kbn-fs/validations/file_extension.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ describe('validateFileExtension', () => {
5959
'.doc',
6060
'.pdf',
6161
'.exe',
62-
'.zip',
6362
'.bat',
6463
'.cmd',
6564
'.sh',
@@ -70,7 +69,7 @@ describe('validateFileExtension', () => {
7069
];
7170
unsupportedExtensions.forEach((extension) => {
7271
expect(() => validateFileExtension(`file${extension}`)).toThrow(
73-
`Invalid file type: "file${extension}". Only .txt, .md, .log, .json, .yml, .yaml, .csv, .svg, .png files are allowed.`
72+
`Invalid file type: "file${extension}". Only .txt, .md, .log, .json, .yml, .yaml, .csv, .svg, .png, .zip files are allowed.`
7473
);
7574
});
7675
});

x-pack/platform/packages/shared/kbn-fs/validations/file_extension.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@
77

88
import path from 'path';
99

10-
const allowedExtensions = ['.txt', '.md', '.log', '.json', '.yml', '.yaml', '.csv', '.svg', '.png'];
10+
const allowedExtensions = [
11+
'.txt',
12+
'.md',
13+
'.log',
14+
'.json',
15+
'.yml',
16+
'.yaml',
17+
'.csv',
18+
'.svg',
19+
'.png',
20+
'.zip',
21+
];
1122

1223
export function validateFileExtension(filePath: string) {
1324
const extension = path.extname(filePath).toLowerCase();

x-pack/platform/plugins/shared/ai_infra/product_doc_base/moon.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ dependsOn:
2323
- '@kbn/config-schema'
2424
- '@kbn/product-doc-common'
2525
- '@kbn/core-saved-objects-server'
26-
- '@kbn/utils'
2726
- '@kbn/core-http-browser'
2827
- '@kbn/logging-mocks'
2928
- '@kbn/licensing-plugin'
@@ -33,6 +32,7 @@ dependsOn:
3332
- '@kbn/ml-is-populated-object'
3433
- '@kbn/i18n'
3534
- '@kbn/licensing-types'
35+
- '@kbn/fs'
3636
- '@kbn/react-query'
3737
tags:
3838
- plugin

x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/plugin.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
* 2.0.
66
*/
77

8-
import Path from 'path';
98
import type { Logger } from '@kbn/logging';
10-
import { getDataPath } from '@kbn/utils';
119
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
1210
import { SavedObjectsClient } from '@kbn/core/server';
1311
import { productDocInstallStatusSavedObjectTypeName } from '../common/consts';
@@ -85,7 +83,7 @@ export class ProductDocBasePlugin
8583
esClient: core.elasticsearch.client.asInternalUser,
8684
productDocClient,
8785
kibanaVersion: this.context.env.packageInfo.version,
88-
artifactsFolder: Path.join(getDataPath(), 'ai-kb-artifacts'),
86+
artifactsFolder: 'ai-kb-artifacts',
8987
artifactRepositoryUrl: this.context.config.get().artifactRepositoryUrl,
9088
elserInferenceId: this.context.config.get().elserInferenceId,
9189
logger: this.logger.get('package-installer'),

x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/services/package_installer/package_installer.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ describe('PackageInstaller', () => {
8787
};
8888
openZipArchiveMock.mockResolvedValue(zipArchive);
8989

90+
const artifactName = getArtifactName({
91+
productName: 'kibana',
92+
productVersion: '8.16',
93+
});
94+
95+
downloadToDiskMock.mockResolvedValue(`${artifactsFolder}/${artifactName}`);
96+
9097
const mappings = {
9198
properties: {
9299
semantic: {
@@ -100,11 +107,8 @@ describe('PackageInstaller', () => {
100107

101108
await packageInstaller.installPackage({ productName: 'kibana', productVersion: '8.16' });
102109

103-
const artifactName = getArtifactName({
104-
productName: 'kibana',
105-
productVersion: '8.16',
106-
});
107110
const indexName = getProductDocIndexName('kibana');
111+
108112
expect(ensureDefaultElserDeployedMock).toHaveBeenCalledTimes(1);
109113

110114
expect(downloadToDiskMock).toHaveBeenCalledTimes(1);

x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/services/package_installer/package_installer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,12 @@ export class PackageInstaller {
208208
inferenceId: customInference?.inference_id ?? this.elserInferenceId,
209209
});
210210
const artifactUrl = `${this.artifactRepositoryUrl}/${artifactFileName}`;
211-
const artifactPath = `${this.artifactsFolder}/${artifactFileName}`;
211+
const artifactPathAtVolume = `${this.artifactsFolder}/${artifactFileName}`;
212212

213-
this.log.debug(`Downloading from [${artifactUrl}] to [${artifactPath}]`);
214-
await downloadToDisk(artifactUrl, artifactPath);
213+
this.log.debug(`Downloading from [${artifactUrl}] to [${artifactPathAtVolume}]`);
214+
const artifactFullPath = await downloadToDisk(artifactUrl, artifactPathAtVolume);
215215

216-
zipArchive = await openZipArchive(artifactPath);
216+
zipArchive = await openZipArchive(artifactFullPath);
217217
validateArtifactArchive(zipArchive);
218218

219219
const [manifest, mappings] = await Promise.all([

x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/services/package_installer/utils/download.test.ts

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,10 @@
66
*/
77

88
import { createReadStream } from 'fs';
9-
import { mkdir } from 'fs/promises';
109
import fetch from 'node-fetch';
1110
import { downloadToDisk } from './download';
1211

13-
jest.mock('fs', () => ({
14-
createReadStream: jest.fn().mockReturnValue({
15-
on: jest.fn(),
16-
pipe: jest.fn(),
17-
}),
12+
jest.mock('@kbn/fs', () => ({
1813
createWriteStream: jest.fn(() => ({
1914
on: jest.fn((event, callback) => {
2015
if (event === 'finish') {
@@ -23,37 +18,34 @@ jest.mock('fs', () => ({
2318
}),
2419
pipe: jest.fn(),
2520
})),
21+
getSafePath: jest.fn().mockReturnValue({
22+
fullPath: 'artifacts/package_installer/file.txt',
23+
alias: 'disk:artifacts/package_installer/file.txt',
24+
}),
2625
}));
2726

28-
jest.mock('fs/promises', () => ({
29-
mkdir: jest.fn(),
27+
jest.mock('fs', () => ({
28+
createReadStream: jest.fn().mockReturnValue({
29+
on: jest.fn(),
30+
pipe: jest.fn(),
31+
}),
32+
}));
33+
34+
jest.mock('stream/promises', () => ({
35+
pipeline: jest.fn(),
3036
}));
3137

3238
jest.mock('node-fetch', () => jest.fn());
3339

3440
describe('downloadToDisk', () => {
3541
const mockFileUrl = 'http://example.com/file.txt';
36-
const mockFilePath = '/path/to/file.txt';
37-
const mockDirPath = '/path/to';
42+
const mockFilePathAtVolume = 'artifacts/package_installer/file.txt';
3843
const mockLocalPath = '/local/path/to/file.txt';
3944

4045
beforeEach(() => {
4146
jest.clearAllMocks();
4247
});
4348

44-
it('should create the directory if it does not exist', async () => {
45-
(fetch as unknown as jest.Mock).mockResolvedValue({
46-
body: {
47-
pipe: jest.fn(),
48-
on: jest.fn(),
49-
},
50-
});
51-
52-
await downloadToDisk(mockFileUrl, mockFilePath);
53-
54-
expect(mkdir).toHaveBeenCalledWith(mockDirPath, { recursive: true });
55-
});
56-
5749
it('should download a file from a remote URL', async () => {
5850
const mockResponseBody = {
5951
pipe: jest.fn(),
@@ -64,15 +56,15 @@ describe('downloadToDisk', () => {
6456
body: mockResponseBody,
6557
});
6658

67-
await downloadToDisk(mockFileUrl, mockFilePath);
59+
await downloadToDisk(mockFileUrl, mockFilePathAtVolume);
6860

6961
expect(fetch).toHaveBeenCalledWith(mockFileUrl);
7062
});
7163

7264
it('should copy a file from a local file URL', async () => {
7365
const mockLocalFileUrl = 'file:///local/path/to/file.txt';
7466

75-
await downloadToDisk(mockLocalFileUrl, mockFilePath);
67+
await downloadToDisk(mockLocalFileUrl, mockFilePathAtVolume);
7668

7769
expect(createReadStream).toHaveBeenCalledWith(mockLocalPath);
7870
});
@@ -81,6 +73,8 @@ describe('downloadToDisk', () => {
8173
const mockError = new Error('Download failed');
8274
(fetch as unknown as jest.Mock).mockRejectedValue(mockError);
8375

84-
await expect(downloadToDisk(mockFileUrl, mockFilePath)).rejects.toThrow('Download failed');
76+
await expect(downloadToDisk(mockFileUrl, mockFilePathAtVolume)).rejects.toThrow(
77+
'Download failed'
78+
);
8579
});
8680
});

x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/services/package_installer/utils/download.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
* 2.0.
66
*/
77

8-
import { type ReadStream, createReadStream, createWriteStream } from 'fs';
9-
import { mkdir } from 'fs/promises';
10-
import Path from 'path';
8+
import { type ReadStream, createReadStream } from 'fs';
119
import fetch from 'node-fetch';
10+
import { createWriteStream, getSafePath } from '@kbn/fs';
11+
import { pipeline } from 'stream/promises';
1212
import { resolveLocalArtifactsPath } from './local_artifacts';
1313

14-
export const downloadToDisk = async (fileUrl: string, filePath: string) => {
15-
const dirPath = Path.dirname(filePath);
16-
await mkdir(dirPath, { recursive: true });
17-
const writeStream = createWriteStream(filePath);
14+
export const downloadToDisk = async (
15+
fileUrl: string,
16+
filePathAtVolume: string
17+
): Promise<string> => {
18+
const { fullPath: artifactFullPath } = getSafePath(filePathAtVolume);
19+
const writeStream = createWriteStream(filePathAtVolume);
1820
let readStream: ReadStream;
1921

2022
const parsedUrl = new URL(fileUrl);
@@ -28,9 +30,7 @@ export const downloadToDisk = async (fileUrl: string, filePath: string) => {
2830
readStream = res.body as ReadStream;
2931
}
3032

31-
await new Promise<void>((resolve, reject) => {
32-
readStream.pipe(writeStream);
33-
readStream.on('error', reject);
34-
writeStream.on('finish', resolve);
35-
});
33+
await pipeline(readStream, writeStream);
34+
35+
return artifactFullPath;
3636
};

x-pack/platform/plugins/shared/ai_infra/product_doc_base/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"@kbn/config-schema",
2121
"@kbn/product-doc-common",
2222
"@kbn/core-saved-objects-server",
23-
"@kbn/utils",
2423
"@kbn/core-http-browser",
2524
"@kbn/logging-mocks",
2625
"@kbn/licensing-plugin",
@@ -30,6 +29,7 @@
3029
"@kbn/ml-is-populated-object",
3130
"@kbn/i18n",
3231
"@kbn/licensing-types",
32+
"@kbn/fs",
3333
"@kbn/react-query"
3434
]
3535
}

0 commit comments

Comments
 (0)