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

[EPM][Security Solution] Implementing dataset component templates #70517

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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: 7 additions & 0 deletions x-pack/plugins/ingest_manager/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ export interface Dataset {
package: string;
path: string;
ingest_pipeline: string;
elasticsearch?: RegistryElasticsearch;
}

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

// EPR types this as `[]map[string]interface{}`
Expand Down Expand Up @@ -272,6 +278,7 @@ export interface IndexTemplate {
data_stream: {
timestamp_field: string;
};
composed_of: string[];
_meta: object;
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
*/

import Boom from 'boom';
import { Dataset, RegistryPackage, ElasticsearchAssetType, TemplateRef } from '../../../../types';
import {
Dataset,
RegistryPackage,
ElasticsearchAssetType,
TemplateRef,
RegistryElasticsearch,
} from '../../../../types';
import { CallESAsCurrentUser } from '../../../../types';
import { Field, loadFieldsFromYaml, processFields } from '../../fields/field';
import { getPipelineNameForInstallation } from '../ingest_pipeline/install';
Expand Down Expand Up @@ -157,6 +163,95 @@ export async function installTemplateForDataset({
});
}

function putComponentTemplate(
body: object | undefined,
name: string,
callCluster: CallESAsCurrentUser
): { clusterPromise: Promise<any>; name: string } | undefined {
if (body) {
const callClusterParams: {
method: string;
path: string;
ignore: number[];
body: any;
} = {
method: 'PUT',
path: `/_component_template/${name}`,
ignore: [404],
body,
};

return { clusterPromise: callCluster('transport.request', callClusterParams), name };
}
}

function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | undefined) {
let mappingsTemplate;
let settingsTemplate;

if (registryElasticsearch && registryElasticsearch['index_template.mappings']) {
mappingsTemplate = {
template: {
mappings: {
...registryElasticsearch['index_template.mappings'],
// temporary change until https://github.com/elastic/elasticsearch/issues/58956 is resolved
properties: {
'@timestamp': {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll be able to remove this once this: elastic/elasticsearch#58642 is merged.

type: 'date',
},
},
},
},
};
}

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

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

const compTemplates = buildComponentTemplates(registryElasticsearch);

const mappings = putComponentTemplate(
compTemplates.mappingsTemplate,
`${templateName}-mappings`,
callCluster
);

const settings = putComponentTemplate(
compTemplates.settingsTemplate,
`${templateName}-settings`,
callCluster
);

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add/link a ticket?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I created a new ticket here: #70586

I'll update the second link once this PR is merged.

await Promise.all(componentPromises);
return templates;
}

export async function installTemplate({
callCluster,
fields,
Expand All @@ -180,13 +275,22 @@ export async function installTemplate({
packageVersion,
});
}

const composedOfTemplates = await installDatasetComponentTemplates(
templateName,
dataset.elasticsearch,
callCluster
);

const template = getTemplate({
type: dataset.type,
templateName,
mappings,
pipelineName,
packageName,
composedOfTemplates,
});

// TODO: Check return values for errors
const callClusterParams: {
method: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,37 @@ test('get template', () => {
templateName,
packageName: 'nginx',
mappings: { properties: {} },
composedOfTemplates: [],
});
expect(template.index_patterns).toStrictEqual([`${templateName}-*`]);
});

test('adds composed_of correctly', () => {
const composedOfTemplates = ['component1', 'component2'];

const template = getTemplate({
type: 'logs',
templateName: 'name',
packageName: 'nginx',
mappings: { properties: {} },
composedOfTemplates,
});
expect(template.composed_of).toStrictEqual(composedOfTemplates);
});

test('adds empty composed_of correctly', () => {
const composedOfTemplates: string[] = [];

const template = getTemplate({
type: 'logs',
templateName: 'name',
packageName: 'nginx',
mappings: { properties: {} },
composedOfTemplates,
});
expect(template.composed_of).toStrictEqual(composedOfTemplates);
});

test('tests loading base.yml', () => {
const ymlPath = path.join(__dirname, '../../fields/tests/base.yml');
const fieldsYML = readFileSync(ymlPath, 'utf-8');
Expand All @@ -45,6 +72,7 @@ test('tests loading base.yml', () => {
templateName: 'foo',
packageName: 'nginx',
mappings,
composedOfTemplates: [],
});

expect(template).toMatchSnapshot(path.basename(ymlPath));
Expand All @@ -62,6 +90,7 @@ test('tests loading coredns.logs.yml', () => {
templateName: 'foo',
packageName: 'coredns',
mappings,
composedOfTemplates: [],
});

expect(template).toMatchSnapshot(path.basename(ymlPath));
Expand All @@ -79,6 +108,7 @@ test('tests loading system.yml', () => {
templateName: 'whatsthis',
packageName: 'system',
mappings,
composedOfTemplates: [],
});

expect(template).toMatchSnapshot(path.basename(ymlPath));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ export function getTemplate({
mappings,
pipelineName,
packageName,
composedOfTemplates,
}: {
type: string;
templateName: string;
mappings: IndexTemplateMappings;
pipelineName?: string | undefined;
packageName: string;
composedOfTemplates: string[];
}): IndexTemplate {
const template = getBaseTemplate(type, templateName, mappings, packageName);
const template = getBaseTemplate(type, templateName, mappings, packageName, composedOfTemplates);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 params is hefty, but seemingly not introduced here and can be reduced later

if (pipelineName) {
template.template.settings.index.default_pipeline = pipelineName;
}
Expand Down Expand Up @@ -244,7 +246,8 @@ function getBaseTemplate(
type: string,
templateName: string,
mappings: IndexTemplateMappings,
packageName: string
packageName: string,
composedOfTemplates: string[]
): IndexTemplate {
return {
// This takes precedence over all index templates installed by ES by default (logs-*-* and metrics-*-*)
Expand Down Expand Up @@ -308,6 +311,7 @@ function getBaseTemplate(
data_stream: {
timestamp_field: '@timestamp',
},
composed_of: composedOfTemplates,
_meta: {
package: {
name: packageName,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ingest_manager/server/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
PackageInfo,
RegistryVarsEntry,
Dataset,
RegistryElasticsearch,
AssetReference,
ElasticsearchAssetType,
IngestAssetType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) {
templateName,
mappings,
packageName: 'system',
composedOfTemplates: [],
});

// This test is not an API integration test with Kibana
Expand Down