Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Support user overrides in composable templates #101769

Merged
merged 26 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a4440c8
Fix issues error_handling test packages
Jun 9, 2021
5c18e47
Support using a different registry Docker image
Jun 9, 2021
ccfa92f
Add support for index_template.user_settings
Jun 9, 2021
4a834c4
Merge branch 'master' into 90454-support-composable-templates
Jun 9, 2021
5ba0cab
Use stub user_settings template
Jun 12, 2021
b609a1c
Include component_templates in SO. Remove on package delete
Jun 14, 2021
7bb5a97
Merge branch 'master' into 90454-support-composable-templates
kibanamachine Jun 15, 2021
d161aa8
Improve index_template types. Fix failing API test
Jun 15, 2021
dddc896
Include component_templates in saved object
Jun 15, 2021
44a1992
Merge branch 'elastic:master' into 90454-support-composable-templates
jfsiii Jun 15, 2021
426621d
Update overrides package FTR test
Jun 16, 2021
cb507a5
Code & tests for correct component templates in SO on reinstall
Jun 16, 2021
f4955c2
Merge branch '90454-support-composable-templates' of github.com:jfsii…
Jun 16, 2021
3c4fcee
Remove overrides-0.2.0 since it's not needed
Jun 16, 2021
4cd08b9
Update more tests to be aware of component templates
Jun 16, 2021
018d638
Merge branch 'master' into 90454-support-composable-templates
kibanamachine Jun 16, 2021
c10aeff
Use .flatMap vs reduce+push+flat
Jun 17, 2021
08dc5b7
Need .map + .flat not .flatMap the results are nested; not the Promises
Jun 17, 2021
22514f4
Add _meta to component templates
Jun 17, 2021
2623d64
Fix tests based on changes in 22514f4
Jun 17, 2021
7144c71
Replace '-mappings' suffix with '\@mappings'
Jun 17, 2021
1248738
Replace '-settings' suffix with '@settings'
Jun 17, 2021
669c310
Replace '-user_settings' suffix with '@custom'
Jun 17, 2021
81d0071
Fix tests/snapshots for installation by upload
Jun 17, 2021
9872757
Merge branch 'master' into 90454-support-composable-templates
Jun 17, 2021
6f5d802
Merge branch 'master' into 90454-support-composable-templates
kibanamachine Jun 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type { estypes } from '@elastic/elasticsearch';
// Follow pattern from https://github.com/elastic/kibana/pull/52447
// TODO: Update when https://github.com/elastic/kibana/issues/53021 is closed
import type { SavedObject, SavedObjectAttributes, SavedObjectReference } from 'src/core/public';
Expand Down Expand Up @@ -301,8 +302,8 @@ export interface RegistryDataStream {
}

export interface RegistryElasticsearch {
'index_template.settings'?: object;
'index_template.mappings'?: object;
'index_template.settings'?: estypes.IndicesIndexSettings;
'index_template.mappings'?: estypes.MappingTypeMapping;
}

export interface RegistryDataStreamPermissions {
Expand Down Expand Up @@ -431,7 +432,7 @@ export interface IndexTemplate {
_meta: object;
}

export interface TemplateRef {
export interface IndexTemplateEntry {
templateName: string;
indexTemplate: IndexTemplate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s
import { ElasticsearchAssetType } from '../../../../types';
import type {
RegistryDataStream,
TemplateRef,
IndexTemplateEntry,
RegistryElasticsearch,
InstallablePackage,
} from '../../../../types';
import { loadFieldsFromYaml, processFields } from '../../fields/field';
import type { Field } from '../../fields/field';
import { getPipelineNameForInstallation } from '../ingest_pipeline/install';
import { getAsset, getPathParts } from '../../archive';
import { removeAssetsFromInstalledEsByType, saveInstalledEsRefs } from '../../packages/install';
import { removeAssetTypesFromInstalledEs, saveInstalledEsRefs } from '../../packages/install';

import {
generateMappings,
Expand All @@ -34,52 +34,44 @@ export const installTemplates = async (
esClient: ElasticsearchClient,
paths: string[],
savedObjectsClient: SavedObjectsClientContract
): Promise<TemplateRef[]> => {
): Promise<IndexTemplateEntry[]> => {
// 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);
await installPreBuiltTemplates(paths, esClient);

// remove package installation's references to index templates
await removeAssetsFromInstalledEsByType(
savedObjectsClient,
installablePackage.name,
ElasticsearchAssetType.indexTemplate
);
await removeAssetTypesFromInstalledEs(savedObjectsClient, installablePackage.name, [
ElasticsearchAssetType.indexTemplate,
ElasticsearchAssetType.componentTemplate,
]);
// build templates per data stream from yml files
const dataStreams = installablePackage.data_streams;
if (!dataStreams) return [];

const installedTemplatesNested = await Promise.all(
dataStreams.map((dataStream) =>
installTemplateForDataStream({
pkg: installablePackage,
esClient,
dataStream,
})
)
);
const installedTemplates = installedTemplatesNested.flat();

// get template refs to save
const installedTemplateRefs = dataStreams.map((dataStream) => ({
id: generateTemplateName(dataStream),
type: ElasticsearchAssetType.indexTemplate,
}));
const installedIndexTemplateRefs = getAllTemplateRefs(installedTemplates);

// add package installation's references to index templates
await saveInstalledEsRefs(savedObjectsClient, installablePackage.name, installedTemplateRefs);

if (dataStreams) {
const installTemplatePromises = dataStreams.reduce<Array<Promise<TemplateRef>>>(
(acc, dataStream) => {
acc.push(
installTemplateForDataStream({
pkg: installablePackage,
esClient,
dataStream,
})
);
return acc;
},
[]
);

const res = await Promise.all(installTemplatePromises);
const installedTemplates = res.flat();
await saveInstalledEsRefs(
savedObjectsClient,
installablePackage.name,
installedIndexTemplateRefs
);

return installedTemplates;
}
return [];
return installedTemplates;
};

const installPreBuiltTemplates = async (paths: string[], esClient: ElasticsearchClient) => {
Expand Down Expand Up @@ -160,7 +152,7 @@ export async function installTemplateForDataStream({
pkg: InstallablePackage;
esClient: ElasticsearchClient;
dataStream: RegistryDataStream;
}): Promise<TemplateRef> {
}): Promise<IndexTemplateEntry> {
const fields = await loadFieldsFromYaml(pkg, dataStream.path);
return installTemplate({
esClient,
Expand All @@ -171,84 +163,109 @@ export async function installTemplateForDataStream({
});
}

interface TemplateMapEntry {
template:
| {
mappings: NonNullable<RegistryElasticsearch['index_template.mappings']>;
}
| {
settings: NonNullable<RegistryElasticsearch['index_template.settings']> | object;
};
}
type TemplateMap = Record<string, TemplateMapEntry>;
function putComponentTemplate(
body: object | undefined,
name: string,
esClient: ElasticsearchClient
): { clusterPromise: Promise<any>; name: string } | undefined {
if (body) {
const esClientParams = {
name,
body,
};

return {
// @ts-expect-error body expected to be ClusterPutComponentTemplateRequest
clusterPromise: esClient.cluster.putComponentTemplate(esClientParams, { ignore: [404] }),
name,
};
esClient: ElasticsearchClient,
params: {
body: TemplateMapEntry;
name: string;
create?: boolean;
}
): { clusterPromise: Promise<any>; name: string } {
const { name, body, create = false } = params;
return {
clusterPromise: esClient.cluster.putComponentTemplate(
// @ts-expect-error body is missing required key `settings`. TemplateMapEntry has settings *or* mappings
{ name, body, create },
{ ignore: [404] }
),
name,
};
}

function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | undefined) {
let mappingsTemplate;
let settingsTemplate;
const mappingsSuffix = '-mappings';
const settingsSuffix = '-settings';
const userSettingsSuffix = '-user_settings';
type TemplateBaseName = string;
type UserSettingsTemplateName = `${TemplateBaseName}${typeof userSettingsSuffix}`;

const isUserSettingsTemplate = (name: string): name is UserSettingsTemplateName =>
name.endsWith(userSettingsSuffix);

function buildComponentTemplates(
templateName: string,
registryElasticsearch: RegistryElasticsearch | undefined
) {
const mappingsTemplateName = `${templateName}${mappingsSuffix}`;
const settingsTemplateName = `${templateName}${settingsSuffix}`;
const userSettingsTemplateName = `${templateName}${userSettingsSuffix}`;

const templatesMap: TemplateMap = {};

if (registryElasticsearch && registryElasticsearch['index_template.mappings']) {
mappingsTemplate = {
templatesMap[mappingsTemplateName] = {
template: {
mappings: {
...registryElasticsearch['index_template.mappings'],
},
mappings: registryElasticsearch['index_template.mappings'],
},
};
}

if (registryElasticsearch && registryElasticsearch['index_template.settings']) {
settingsTemplate = {
templatesMap[settingsTemplateName] = {
template: {
settings: registryElasticsearch['index_template.settings'],
},
};

// return empty/stub template
templatesMap[userSettingsTemplateName] = {
template: {
settings: {},
},
};
}
return { settingsTemplate, mappingsTemplate };

return templatesMap;
}

async function installDataStreamComponentTemplates(
templateName: string,
registryElasticsearch: RegistryElasticsearch | undefined,
esClient: ElasticsearchClient
) {
const templates: string[] = [];
const componentPromises: Array<Promise<any>> = [];

const compTemplates = buildComponentTemplates(registryElasticsearch);

const mappings = putComponentTemplate(
compTemplates.mappingsTemplate,
`${templateName}-mappings`,
esClient
);
const templates = buildComponentTemplates(templateName, registryElasticsearch);
const templateNames = Object.keys(templates);
const templateEntries = Object.entries(templates);

const settings = putComponentTemplate(
compTemplates.settingsTemplate,
`${templateName}-settings`,
esClient
// TODO: Check return values for errors
await Promise.all(
templateEntries.map(async ([name, body]) => {
if (isUserSettingsTemplate(name)) {
// look for existing user_settings template
const result = await esClient.cluster.getComponentTemplate({ name }, { ignore: [404] });
const hasUserSettingsTemplate = result.body.component_templates?.length === 1;
if (!hasUserSettingsTemplate) {
// only add if one isn't already present
const { clusterPromise } = putComponentTemplate(esClient, { body, name, create: true });
return clusterPromise;
}
} else {
const { clusterPromise } = putComponentTemplate(esClient, { body, name });
return clusterPromise;
}
})
);

if (mappings) {
templates.push(mappings.name);
componentPromises.push(mappings.clusterPromise);
}

if (settings) {
templates.push(settings.name);
componentPromises.push(settings.clusterPromise);
}

// TODO: Check return values for errors
await Promise.all(componentPromises);
return templates;
return templateNames;
}

export async function installTemplate({
Expand All @@ -263,7 +280,7 @@ export async function installTemplate({
dataStream: RegistryDataStream;
packageVersion: string;
packageName: string;
}): Promise<TemplateRef> {
}): Promise<IndexTemplateEntry> {
const validFields = processFields(fields);
const mappings = generateMappings(validFields);
const templateName = generateTemplateName(dataStream);
Expand Down Expand Up @@ -342,3 +359,21 @@ export async function installTemplate({
indexTemplate: template,
};
}

export function getAllTemplateRefs(installedTemplates: IndexTemplateEntry[]) {
return installedTemplates.flatMap((installedTemplate) => {
const indexTemplates = [
{
id: installedTemplate.templateName,
type: ElasticsearchAssetType.indexTemplate,
},
];
const componentTemplates = installedTemplate.indexTemplate.composed_of.map(
(componentTemplateId) => ({
id: componentTemplateId,
type: ElasticsearchAssetType.componentTemplate,
})
);
return indexTemplates.concat(componentTemplates);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ElasticsearchClient } from 'kibana/server';
import type { Field, Fields } from '../../fields/field';
import type {
RegistryDataStream,
TemplateRef,
IndexTemplateEntry,
IndexTemplate,
IndexTemplateMappings,
} from '../../../../types';
Expand Down Expand Up @@ -456,7 +456,7 @@ function getBaseTemplate(

export const updateCurrentWriteIndices = async (
esClient: ElasticsearchClient,
templates: TemplateRef[]
templates: IndexTemplateEntry[]
): Promise<void> => {
if (!templates.length) return;

Expand All @@ -471,7 +471,7 @@ function isCurrentDataStream(item: CurrentDataStream[] | undefined): item is Cur

const queryDataStreamsFromTemplates = async (
esClient: ElasticsearchClient,
templates: TemplateRef[]
templates: IndexTemplateEntry[]
): Promise<CurrentDataStream[]> => {
const dataStreamPromises = templates.map((template) => {
return getDataStreams(esClient, template);
Expand All @@ -482,7 +482,7 @@ const queryDataStreamsFromTemplates = async (

const getDataStreams = async (
esClient: ElasticsearchClient,
template: TemplateRef
template: IndexTemplateEntry
): Promise<CurrentDataStream[] | undefined> => {
const { templateName, indexTemplate } = template;
const { body } = await esClient.indices.getDataStream({ name: `${templateName}-*` });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } fro
import { MAX_TIME_COMPLETE_INSTALL, ASSETS_SAVED_OBJECT_TYPE } from '../../../../common';
import type { InstallablePackage, InstallSource, PackageAssetReference } from '../../../../common';
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
import { ElasticsearchAssetType } from '../../../types';
import type { AssetReference, Installation, InstallType } from '../../../types';
import { installTemplates } from '../elasticsearch/template/install';
import { installPipelines, deletePreviousPipelines } from '../elasticsearch/ingest_pipeline/';
import { getAllTemplateRefs } from '../elasticsearch/template/install';
import { installILMPolicy } from '../elasticsearch/ilm/install';
import { installKibanaAssets, getKibanaAssets } from '../kibana/assets/install';
import { updateCurrentWriteIndices } from '../elasticsearch/template/template';
Expand Down Expand Up @@ -170,10 +170,7 @@ export async function _installPackage({
installedPkg.attributes.install_version
);
}
const installedTemplateRefs = installedTemplates.map((template) => ({
id: template.templateName,
type: ElasticsearchAssetType.indexTemplate,
}));
const installedTemplateRefs = getAllTemplateRefs(installedTemplates);

// make sure the assets are installed (or didn't error)
if (installKibanaAssetsError) throw installKibanaAssetsError;
Expand Down
Loading