"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.buildComponentTemplates = buildComponentTemplates; exports.ensureAliasHasWriteIndex = ensureAliasHasWriteIndex; exports.ensureComponentTemplate = ensureComponentTemplate; exports.ensureDefaultComponentTemplates = ensureDefaultComponentTemplates; exports.getAllTemplateRefs = getAllTemplateRefs; exports.installComponentAndIndexTemplateForDataStream = installComponentAndIndexTemplateForDataStream; exports.prepareTemplate = prepareTemplate; exports.prepareToInstallTemplates = void 0; var _lodash = require("lodash"); var _boom = _interopRequireDefault(require("@hapi/boom")); var _types = require("../../../../types"); var _services = require("../../../../../common/services"); var _field = require("../../fields/field"); var _archive = require("../../archive"); var _constants = require("../../../../constants"); var _meta2 = require("../meta"); var _retry = require("../retry"); var _experimental_datastream_features_helper = require("../../../experimental_datastream_features_helper"); var _app_context = require("../../../app_context"); var _template = require("./template"); var _default_settings = require("./default_settings"); /* * 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. */ const FLEET_COMPONENT_TEMPLATE_NAMES = _constants.FLEET_COMPONENT_TEMPLATES.map(tmpl => tmpl.name); const prepareToInstallTemplates = (installablePackage, paths, esReferences, experimentalDataStreamFeatures = [], onlyForDataStreams) => { // remove package installation's references to index templates const assetsToRemove = esReferences.filter(({ type }) => type === _types.ElasticsearchAssetType.indexTemplate || type === _types.ElasticsearchAssetType.componentTemplate); // build templates per data stream from yml files const dataStreams = onlyForDataStreams || installablePackage.data_streams; if (!dataStreams) return { assetsToAdd: [], assetsToRemove, install: () => Promise.resolve([]) }; const templates = dataStreams.map(dataStream => { const experimentalDataStreamFeature = experimentalDataStreamFeatures.find(datastreamFeature => datastreamFeature.data_stream === (0, _services.getRegistryDataStreamAssetBaseName)(dataStream)); return prepareTemplate({ pkg: installablePackage, dataStream, experimentalDataStreamFeature }); }); const assetsToAdd = getAllTemplateRefs(templates.map(template => template.indexTemplate)); return { assetsToAdd, assetsToRemove, install: async (esClient, logger) => { // install any pre-built index template assets, // atm, this is only the base package's global index templates // Install component templates first, as they are used by the index templates await installPreBuiltComponentTemplates(paths, esClient, logger); await installPreBuiltTemplates(paths, esClient, logger); await Promise.all(templates.map(template => installComponentAndIndexTemplateForDataStream({ esClient, logger, componentTemplates: template.componentTemplates, indexTemplate: template.indexTemplate }))); return templates.map(template => template.indexTemplate); } }; }; exports.prepareToInstallTemplates = prepareToInstallTemplates; const installPreBuiltTemplates = async (paths, esClient, logger) => { const templatePaths = paths.filter(path => isTemplate(path)); const templateInstallPromises = templatePaths.map(async path => { const { file } = (0, _archive.getPathParts)(path); const templateName = file.substr(0, file.lastIndexOf('.')); const content = JSON.parse((0, _archive.getAsset)(path).toString('utf8')); const esClientParams = { name: templateName, body: content }; const esClientRequestOptions = { ignore: [404] }; if (content.hasOwnProperty('template') || content.hasOwnProperty('composed_of')) { // Template is v2 return (0, _retry.retryTransientEsErrors)(() => esClient.indices.putIndexTemplate(esClientParams, esClientRequestOptions), { logger }); } else { // template is V1 return (0, _retry.retryTransientEsErrors)(() => esClient.indices.putTemplate(esClientParams, esClientRequestOptions), { logger }); } }); try { return await Promise.all(templateInstallPromises); } catch (e) { throw new _boom.default.Boom(`Error installing prebuilt index templates ${e.message}`, { statusCode: 400 }); } }; const installPreBuiltComponentTemplates = async (paths, esClient, logger) => { const templatePaths = paths.filter(path => isComponentTemplate(path)); const templateInstallPromises = templatePaths.map(async path => { const { file } = (0, _archive.getPathParts)(path); const templateName = file.substr(0, file.lastIndexOf('.')); const content = JSON.parse((0, _archive.getAsset)(path).toString('utf8')); const esClientParams = { name: templateName, body: content }; return (0, _retry.retryTransientEsErrors)(() => esClient.cluster.putComponentTemplate(esClientParams, { ignore: [404] }), { logger }); }); try { return await Promise.all(templateInstallPromises); } catch (e) { throw new _boom.default.Boom(`Error installing prebuilt component templates ${e.message}`, { statusCode: 400 }); } }; const isTemplate = path => { const pathParts = (0, _archive.getPathParts)(path); return pathParts.type === _types.ElasticsearchAssetType.indexTemplate; }; const isComponentTemplate = path => { const pathParts = (0, _archive.getPathParts)(path); return pathParts.type === _types.ElasticsearchAssetType.componentTemplate; }; /** * installComponentAndIndexTemplateForDataStream installs one template for each data stream * * The template is currently loaded with the pkgkey-package-data_stream */ async function installComponentAndIndexTemplateForDataStream({ esClient, logger, componentTemplates, indexTemplate }) { // update index template first in case TSDS was removed, so that it does not become invalid await updateIndexTemplateIfTsdsDisabled({ esClient, logger, indexTemplate }); await installDataStreamComponentTemplates({ esClient, logger, componentTemplates }); await installTemplate({ esClient, logger, template: indexTemplate }); } async function updateIndexTemplateIfTsdsDisabled({ esClient, logger, indexTemplate }) { try { var _existingIndexTemplat, _existingIndexTemplat2, _existingIndexTemplat3, _existingIndexTemplat4, _existingIndexTemplat5; const existingIndexTemplate = await esClient.indices.getIndexTemplate({ name: indexTemplate.templateName }); if (((_existingIndexTemplat = existingIndexTemplate.index_templates) === null || _existingIndexTemplat === void 0 ? void 0 : (_existingIndexTemplat2 = _existingIndexTemplat[0]) === null || _existingIndexTemplat2 === void 0 ? void 0 : (_existingIndexTemplat3 = _existingIndexTemplat2.index_template.template) === null || _existingIndexTemplat3 === void 0 ? void 0 : (_existingIndexTemplat4 = _existingIndexTemplat3.settings) === null || _existingIndexTemplat4 === void 0 ? void 0 : (_existingIndexTemplat5 = _existingIndexTemplat4.index) === null || _existingIndexTemplat5 === void 0 ? void 0 : _existingIndexTemplat5.mode) === 'time_series' && indexTemplate.indexTemplate.template.settings.index.mode !== 'time_series') { await installTemplate({ esClient, logger, template: indexTemplate }); } } catch (e) { if (e.statusCode === 404) { logger.debug(`Index template ${indexTemplate.templateName} does not exist, skipping time_series check`); } else { logger.warn(`Error while trying to install index template before component template: ${e.message}`); } } } function putComponentTemplate(esClient, logger, params) { const { name, body, create = false } = params; return { clusterPromise: (0, _retry.retryTransientEsErrors)(() => esClient.cluster.putComponentTemplate( // @ts-expect-error lifecycle is not yet supported here { name, body, create }, { ignore: [404] }), { logger }), name }; } const isUserSettingsTemplate = name => name.endsWith(_constants.USER_SETTINGS_TEMPLATE_SUFFIX); function buildComponentTemplates(params) { var _registryElasticsearc, _registryElasticsearc2, _indexTemplateMapping, _mappings$dynamic_tem, _indexTemplateMapping2, _indexTemplateMapping3, _templateSettings$ind, _templateSettings$ind2, _templateSettings$ind3; const { templateName, registryElasticsearch, packageName, defaultSettings, mappings, pipelineName, experimentalDataStreamFeature, lifecycle } = params; const packageTemplateName = `${templateName}${_constants.PACKAGE_TEMPLATE_SUFFIX}`; const userSettingsTemplateName = `${templateName}${_constants.USER_SETTINGS_TEMPLATE_SUFFIX}`; const templatesMap = {}; const _meta = (0, _meta2.getESAssetMetadata)({ packageName }); const indexTemplateSettings = (_registryElasticsearc = registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch['index_template.settings']) !== null && _registryElasticsearc !== void 0 ? _registryElasticsearc : {}; const templateSettings = (0, _lodash.merge)(defaultSettings, indexTemplateSettings); const indexTemplateMappings = (_registryElasticsearc2 = registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch['index_template.mappings']) !== null && _registryElasticsearc2 !== void 0 ? _registryElasticsearc2 : {}; const isDocValueOnlyNumericEnabled = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.doc_value_only_numeric) === true; const isDocValueOnlyOtherEnabled = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.doc_value_only_other) === true; if (isDocValueOnlyNumericEnabled || isDocValueOnlyOtherEnabled) { (0, _experimental_datastream_features_helper.forEachMappings)(mappings.properties, (mappingProp, name) => (0, _experimental_datastream_features_helper.applyDocOnlyValueToMapping)(mappingProp, name, experimentalDataStreamFeature, isDocValueOnlyNumericEnabled, isDocValueOnlyOtherEnabled)); } const mappingsProperties = (0, _lodash.merge)(mappings.properties, (_indexTemplateMapping = indexTemplateMappings.properties) !== null && _indexTemplateMapping !== void 0 ? _indexTemplateMapping : {}); const mappingsDynamicTemplates = (0, _lodash.uniqBy)((0, _lodash.concat)((_mappings$dynamic_tem = mappings.dynamic_templates) !== null && _mappings$dynamic_tem !== void 0 ? _mappings$dynamic_tem : [], (_indexTemplateMapping2 = indexTemplateMappings.dynamic_templates) !== null && _indexTemplateMapping2 !== void 0 ? _indexTemplateMapping2 : []), dynampingTemplate => Object.keys(dynampingTemplate)[0]); const mappingsRuntimeFields = (0, _lodash.merge)(mappings.runtime, (_indexTemplateMapping3 = indexTemplateMappings.runtime) !== null && _indexTemplateMapping3 !== void 0 ? _indexTemplateMapping3 : {}); const isTimeSeriesEnabledByDefault = (registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch.index_mode) === 'time_series'; const isSyntheticSourceEnabledByDefault = (registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch.source_mode) === 'synthetic'; const sourceModeSynthetic = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.synthetic_source) !== false && ((experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.synthetic_source) === true || isSyntheticSourceEnabledByDefault || isTimeSeriesEnabledByDefault); templatesMap[packageTemplateName] = { template: { settings: { ...templateSettings, index: { ...templateSettings.index, ...(pipelineName ? { default_pipeline: pipelineName } : {}), mapping: { ...((_templateSettings$ind = templateSettings.index) === null || _templateSettings$ind === void 0 ? void 0 : _templateSettings$ind.mapping), total_fields: { ...((_templateSettings$ind2 = templateSettings.index) === null || _templateSettings$ind2 === void 0 ? void 0 : (_templateSettings$ind3 = _templateSettings$ind2.mapping) === null || _templateSettings$ind3 === void 0 ? void 0 : _templateSettings$ind3.total_fields), limit: '10000' } } } }, mappings: { properties: mappingsProperties, ...(Object.keys(mappingsRuntimeFields).length > 0 ? { runtime: mappingsRuntimeFields } : {}), dynamic_templates: mappingsDynamicTemplates.length ? mappingsDynamicTemplates : undefined, ...(0, _lodash.omit)(indexTemplateMappings, 'properties', 'dynamic_templates', '_source', 'runtime'), ...(indexTemplateMappings !== null && indexTemplateMappings !== void 0 && indexTemplateMappings._source || sourceModeSynthetic ? { _source: { ...(indexTemplateMappings === null || indexTemplateMappings === void 0 ? void 0 : indexTemplateMappings._source), ...(sourceModeSynthetic ? { mode: 'synthetic' } : {}) } } : {}) }, ...(lifecycle ? { lifecycle } : {}) }, _meta }; // return empty/stub template templatesMap[userSettingsTemplateName] = { template: { settings: {} }, _meta }; return templatesMap; } async function installDataStreamComponentTemplates({ esClient, logger, componentTemplates }) { await Promise.all(Object.entries(componentTemplates).map(async ([name, body]) => { if (isUserSettingsTemplate(name)) { try { // Attempt to create custom component templates, ignore if they already exist const { clusterPromise } = putComponentTemplate(esClient, logger, { body, name, create: true }); return await clusterPromise; } catch (e) { var _e$body, _e$body$error; if ((e === null || e === void 0 ? void 0 : e.statusCode) === 400 && (_e$body = e.body) !== null && _e$body !== void 0 && (_e$body$error = _e$body.error) !== null && _e$body$error !== void 0 && _e$body$error.reason.includes('already exists')) { // ignore } else { throw e; } } } else { const { clusterPromise } = putComponentTemplate(esClient, logger, { body, name }); return clusterPromise; } })); } async function ensureDefaultComponentTemplates(esClient, logger) { return Promise.all(_constants.FLEET_COMPONENT_TEMPLATES.map(({ name, body }) => ensureComponentTemplate(esClient, logger, name, body))); } async function ensureComponentTemplate(esClient, logger, name, body) { var _getTemplateRes$compo; const getTemplateRes = await (0, _retry.retryTransientEsErrors)(() => esClient.cluster.getComponentTemplate({ name }, { ignore: [404] }), { logger }); const existingTemplate = getTemplateRes === null || getTemplateRes === void 0 ? void 0 : (_getTemplateRes$compo = getTemplateRes.component_templates) === null || _getTemplateRes$compo === void 0 ? void 0 : _getTemplateRes$compo[0]; if (!existingTemplate) { await putComponentTemplate(esClient, logger, { name, body }).clusterPromise; } return { isCreated: !existingTemplate }; } async function ensureAliasHasWriteIndex(opts) { const { esClient, logger, aliasName, writeIndexName, body } = opts; const existingIndex = await (0, _retry.retryTransientEsErrors)(() => esClient.indices.exists({ index: [aliasName] }, { ignore: [404] }), { logger }); if (!existingIndex) { logger.info(`Creating write index [${writeIndexName}], alias [${aliasName}]`); await (0, _retry.retryTransientEsErrors)(() => esClient.indices.create({ index: writeIndexName, ...body }, { ignore: [404] }), { logger }); } } function prepareTemplate({ pkg, dataStream, experimentalDataStreamFeature }) { var _dataStream$elasticse, _appContextService$ge, _appContextService$ge2, _appContextService$ge3; const { name: packageName, version: packageVersion } = pkg; const fields = (0, _field.loadFieldsFromYaml)(pkg, dataStream.path); const isIndexModeTimeSeries = ((_dataStream$elasticse = dataStream.elasticsearch) === null || _dataStream$elasticse === void 0 ? void 0 : _dataStream$elasticse.index_mode) === 'time_series' || (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.tsdb); const validFields = (0, _field.processFields)(fields); const mappings = (0, _template.generateMappings)(validFields); const templateName = (0, _template.generateTemplateName)(dataStream); const templateIndexPattern = (0, _template.generateTemplateIndexPattern)(dataStream); const templatePriority = (0, _template.getTemplatePriority)(dataStream); const isILMPolicyDisabled = (_appContextService$ge = (_appContextService$ge2 = _app_context.appContextService.getConfig()) === null || _appContextService$ge2 === void 0 ? void 0 : (_appContextService$ge3 = _appContextService$ge2.internal) === null || _appContextService$ge3 === void 0 ? void 0 : _appContextService$ge3.disableILMPolicies) !== null && _appContextService$ge !== void 0 ? _appContextService$ge : false; const lifecyle = isILMPolicyDisabled && dataStream.lifecycle ? dataStream.lifecycle : undefined; const pipelineName = (0, _services.getPipelineNameForDatastream)({ dataStream, packageVersion }); const defaultSettings = (0, _default_settings.buildDefaultSettings)({ templateName, packageName, fields: validFields, type: dataStream.type, ilmPolicy: dataStream.ilm_policy }); const componentTemplates = buildComponentTemplates({ defaultSettings, mappings, packageName, templateName, pipelineName, registryElasticsearch: dataStream.elasticsearch, experimentalDataStreamFeature, lifecycle: lifecyle }); const template = (0, _template.getTemplate)({ templateIndexPattern, packageName, composedOfTemplates: Object.keys(componentTemplates), templatePriority, hidden: dataStream.hidden, registryElasticsearch: dataStream.elasticsearch, mappings, isIndexModeTimeSeries }); return { componentTemplates, indexTemplate: { templateName, indexTemplate: template } }; } async function installTemplate({ esClient, logger, template }) { // TODO: Check return values for errors const esClientParams = { name: template.templateName, body: template.indexTemplate }; await (0, _retry.retryTransientEsErrors)(() => esClient.indices.putIndexTemplate(esClientParams, { ignore: [404] }), { logger }); } function getAllTemplateRefs(installedTemplates) { return installedTemplates.flatMap(installedTemplate => { const indexTemplates = [{ id: installedTemplate.templateName, type: _types.ElasticsearchAssetType.indexTemplate }]; const componentTemplates = installedTemplate.indexTemplate.composed_of // Filter global component template shared between integrations .filter(componentTemplateId => !FLEET_COMPONENT_TEMPLATE_NAMES.includes(componentTemplateId)).map(componentTemplateId => ({ id: componentTemplateId, type: _types.ElasticsearchAssetType.componentTemplate })); return indexTemplates.concat(componentTemplates); }); }