"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.FleetFromHostFilesClient = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _server = require("@kbn/files-plugin/server"); var _moment = _interopRequireDefault(require("moment")); var _errors = require("../../errors"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ /** * Public interface for interacting with Files stored in Fleet indexes. Service is consumed via * the Fleet `Plugin#start()` interface - {@link FleetStartContract} */ class FleetFromHostFilesClient { constructor(esClient, logger, fileMetaIndex, fileDataIndex, maxSizeBytes) { (0, _defineProperty2.default)(this, "esFileClient", void 0); this.esClient = esClient; this.logger = logger; this.fileMetaIndex = fileMetaIndex; this.fileDataIndex = fileDataIndex; this.esFileClient = (0, _server.createEsFileClient)({ metadataIndex: this.fileMetaIndex, blobStorageIndex: this.fileDataIndex, elasticsearchClient: esClient, logger, indexIsAlias: true, maxSizeBytes }); } async get(fileId) { try { var _fileDocSearchResult$; const fileDocSearchResult = await this.esClient.search({ index: this.fileMetaIndex, body: { size: 1, query: { term: { _id: fileId } } } }); const docSearchHit = (_fileDocSearchResult$ = fileDocSearchResult.hits.hits[0]) !== null && _fileDocSearchResult$ !== void 0 ? _fileDocSearchResult$ : {}; if (!docSearchHit._source) { throw new _errors.FleetFileNotFound(`File with id [${fileId}] not found in index [${this.fileMetaIndex}]`, fileDocSearchResult); } const fleetFile = this.mapIndexedDocToFleetFile(docSearchHit); await this.adjustFileStatusIfNeeded(fleetFile); return fleetFile; } catch (error) { if (error instanceof _errors.FleetFilesClientError) { throw error; } if (error.message.includes('index_not_found')) { throw new _errors.FleetFileNotFound(error.message, error); } throw new _errors.FleetFilesClientError(error.message, error); } } async doesFileHaveData(fileId) { try { var _chunks$hits, _chunks$hits$total; const chunks = await this.esClient.search({ index: this.fileDataIndex, size: 0, body: { query: { bool: { filter: [{ term: { bid: fileId } }] } }, // Setting `_source` to false - we don't need the actual document to be returned _source: false } }); return Boolean((_chunks$hits = chunks.hits) === null || _chunks$hits === void 0 ? void 0 : (_chunks$hits$total = _chunks$hits.total) === null || _chunks$hits$total === void 0 ? void 0 : _chunks$hits$total.value); } catch (err) { throw new _errors.FleetFilesClientError(`Checking if file id [${fileId}] has data in index [${this.fileDataIndex}] failed with: ${err.message}`, err); } } async download(fileId) { try { const file = await this.esFileClient.get({ id: fileId }); const { name: fileName, mimeType } = file.data; return { stream: await file.downloadContent(), fileName, mimeType }; } catch (error) { throw new _errors.FleetFilesClientError(`Attempt to get download stream failed with: ${error.message}`, error); } } /** * Will check if the file actually has data (chunks) if the `status` is `READY`, and if not, it * will set (mutate) the `status` to `DELETED`. * Covers edge case where ILM could have deleted the file data, but the background task has not * yet adjusted the file's meta to reflect that state. * @param file * @protected */ async adjustFileStatusIfNeeded(file) { // if status is `READY` and file is older than 5 minutes, then ensure that file has // chunks if (file.status === 'READY' && (0, _moment.default)().diff(file.created, 'minutes') >= 10) { const fileHasChunks = await this.doesFileHaveData(file.id); if (!fileHasChunks) { this.logger.warn(`File with id [${file.id}] has no data chunks in index [${this.fileDataIndex}]. File status will be adjusted to DELETED`); file.status = 'DELETED'; } } } mapIndexedDocToFleetFile(fileDoc) { if (isSearchHit(fileDoc)) { var _hash$sha; if (!fileDoc._source) { throw new _errors.FleetFilesClientError('Missing file document `_source`'); } const { action_id: actionId, agent_id: agentId, upload_start: created, file: { name, Status: status, mime_type: mimeType = '', size = 0, hash = {} } } = fileDoc._source; const file = { id: fileDoc._id, agents: [agentId], sha256: (_hash$sha = hash === null || hash === void 0 ? void 0 : hash.sha256) !== null && _hash$sha !== void 0 ? _hash$sha : '', created: new Date(created).toISOString(), actionId, name, status, mimeType, size }; return file; } else { var _meta$target_agents, _hash$sha2, _meta$action_id; const { name, meta, id, mimeType = '', size = 0, status, hash, created } = fileDoc.toJSON(); const file = { id, agents: (_meta$target_agents = meta === null || meta === void 0 ? void 0 : meta.target_agents) !== null && _meta$target_agents !== void 0 ? _meta$target_agents : [], sha256: (_hash$sha2 = hash === null || hash === void 0 ? void 0 : hash.sha256) !== null && _hash$sha2 !== void 0 ? _hash$sha2 : '', actionId: (_meta$action_id = meta === null || meta === void 0 ? void 0 : meta.action_id) !== null && _meta$action_id !== void 0 ? _meta$action_id : '', name, status, mimeType, size, created }; return file; } } } exports.FleetFromHostFilesClient = FleetFromHostFilesClient; const isSearchHit = data => { return data !== undefined && data !== null && typeof data === 'object' && '_source' in data && '_id' in data && '_index' in data; };