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 input-level vars & templates #83878

Merged
merged 9 commits into from
Nov 25, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('Fleet - storedPackagePoliciesToAgentInputs', () => {
).toEqual([]);
});

it('returns agent inputs', () => {
it('returns agent inputs with streams', () => {
expect(
storedPackagePoliciesToAgentInputs([
{
Expand Down Expand Up @@ -143,6 +143,47 @@ describe('Fleet - storedPackagePoliciesToAgentInputs', () => {
]);
});

it('returns agent inputs without streams', () => {
expect(
storedPackagePoliciesToAgentInputs([
{
...mockPackagePolicy,
package: {
name: 'mock-package',
title: 'Mock package',
version: '0.0.0',
},
inputs: [
{
...mockInput,
compiled_input: {
inputVar: 'input-value',
},
streams: [],
},
],
},
])
).toEqual([
{
id: 'some-uuid',
name: 'mock-package-policy',
revision: 1,
type: 'test-logs',
data_stream: { namespace: 'default' },
use_output: 'default',
meta: {
package: {
name: 'mock-package',
version: '0.0.0',
},
},
inputVar: 'input-value',
streams: [],
},
]);
});

it('returns agent inputs without disabled streams', () => {
expect(
storedPackagePoliciesToAgentInputs([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const storedPackagePoliciesToAgentInputs = (
acc[key] = value;
return acc;
}, {} as { [k: string]: any }),
...(input.compiled_input || {}),
streams: input.streams
.filter((stream) => stream.enabled)
.map((stream) => {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export interface RegistryInput {
title: string;
description?: string;
vars?: RegistryVarsEntry[];
template_path?: string;
}

export interface RegistryStream {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/models/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface NewPackagePolicyInput {

export interface PackagePolicyInput extends Omit<NewPackagePolicyInput, 'streams'> {
streams: PackagePolicyInputStream[];
compiled_input?: any;
}

export interface NewPackagePolicy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)`
`;

export const PackagePolicyInputConfig: React.FunctionComponent<{
hasInputStreams: boolean;
packageInputVars?: RegistryVarsEntry[];
packagePolicyInput: NewPackagePolicyInput;
updatePackagePolicyInput: (updatedInput: Partial<NewPackagePolicyInput>) => void;
inputVarsValidationResults: PackagePolicyConfigValidationResults;
forceShowErrors?: boolean;
}> = memo(
({
hasInputStreams,
packageInputVars,
packagePolicyInput,
updatePackagePolicyInput,
Expand Down Expand Up @@ -82,15 +84,19 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{
/>
</h4>
</EuiText>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
<p>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.stepConfigure.inputSettingsDescription"
defaultMessage="The following settings are applicable to all inputs below."
/>
</p>
</EuiText>
{hasInputStreams ? (
<>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
<p>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.stepConfigure.inputSettingsDescription"
defaultMessage="The following settings are applicable to all inputs below."
/>
</p>
</EuiText>
</>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useState, Fragment, memo } from 'react';
import React, { useState, Fragment, memo, useMemo } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
Expand Down Expand Up @@ -85,16 +85,23 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
const errorCount = countValidationErrors(inputValidationResults);
const hasErrors = forceShowErrors && errorCount;

const inputStreams = packageInputStreams
.map((packageInputStream) => {
return {
packageInputStream,
packagePolicyInputStream: packagePolicyInput.streams.find(
(stream) => stream.data_stream.dataset === packageInputStream.data_stream.dataset
),
};
})
.filter((stream) => Boolean(stream.packagePolicyInputStream));
const hasInputStreams = useMemo(() => packageInputStreams.length > 0, [
packageInputStreams.length,
]);
const inputStreams = useMemo(
() =>
packageInputStreams
.map((packageInputStream) => {
return {
packageInputStream,
packagePolicyInputStream: packagePolicyInput.streams.find(
(stream) => stream.data_stream.dataset === packageInputStream.data_stream.dataset
),
};
})
.filter((stream) => Boolean(stream.packagePolicyInputStream)),
[packageInputStreams, packagePolicyInput.streams]
);

return (
<>
Expand Down Expand Up @@ -179,13 +186,14 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
{isShowingStreams && packageInput.vars && packageInput.vars.length ? (
<Fragment>
<PackagePolicyInputConfig
hasInputStreams={hasInputStreams}
packageInputVars={packageInput.vars}
packagePolicyInput={packagePolicyInput}
updatePackagePolicyInput={updatePackagePolicyInput}
inputVarsValidationResults={{ vars: inputValidationResults.vars }}
forceShowErrors={forceShowErrors}
/>
<ShortenedHorizontalRule margin="m" />
{hasInputStreams ? <ShortenedHorizontalRule margin="m" /> : <EuiSpacer size="l" />}
</Fragment>
) : null}

Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const createAppContextStartContractMock = (): FleetAppContext => {

export const createPackagePolicyServiceMock = () => {
return {
assignPackageStream: jest.fn(),
compilePackagePolicyInputs: jest.fn(),
buildPackagePolicyFromPackage: jest.fn(),
bulkCreate: jest.fn(),
create: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jest.mock('../../services/package_policy', (): {
} => {
return {
packagePolicyService: {
assignPackageStream: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
compilePackagePolicyInputs: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
buildPackagePolicyFromPackage: jest.fn(),
bulkCreate: jest.fn(),
create: jest.fn((soClient, callCluster, newData) =>
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ const getSavedObjectTypes = (
enabled: { type: 'boolean' },
vars: { type: 'flattened' },
config: { type: 'flattened' },
compiled_input: { type: 'flattened' },
streams: {
type: 'nested',
properties: {
Expand Down
14 changes: 7 additions & 7 deletions x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { createStream } from './agent';
import { compileTemplate } from './agent';

describe('createStream', () => {
describe('compileTemplate', () => {
it('should work', () => {
const streamTemplate = `
input: log
Expand All @@ -27,7 +27,7 @@ hidden_password: {{password}}
password: { type: 'password', value: '' },
};

const output = createStream(vars, streamTemplate);
const output = compileTemplate(vars, streamTemplate);
expect(output).toEqual({
input: 'log',
paths: ['/usr/local/var/log/nginx/access.log'],
Expand Down Expand Up @@ -67,7 +67,7 @@ foo: bar
password: { type: 'password', value: '' },
};

const output = createStream(vars, streamTemplate);
const output = compileTemplate(vars, streamTemplate);
expect(output).toEqual({
input: 'redis/metrics',
metricsets: ['key'],
Expand Down Expand Up @@ -114,7 +114,7 @@ hidden_password: {{password}}
tags: { value: ['foo', 'bar', 'forwarded'] },
};

const output = createStream(vars, streamTemplate);
const output = compileTemplate(vars, streamTemplate);
expect(output).toEqual({
input: 'log',
paths: ['/usr/local/var/log/nginx/access.log'],
Expand All @@ -133,7 +133,7 @@ hidden_password: {{password}}
tags: { value: ['foo', 'bar'] },
};

const output = createStream(vars, streamTemplate);
const output = compileTemplate(vars, streamTemplate);
expect(output).toEqual({
input: 'log',
paths: ['/usr/local/var/log/nginx/access.log'],
Expand All @@ -157,7 +157,7 @@ input: logs
},
};

const output = createStream(vars, streamTemplate);
const output = compileTemplate(vars, streamTemplate);
expect(output).toEqual({
input: 'logs',
});
Expand Down
35 changes: 19 additions & 16 deletions x-pack/plugins/fleet/server/services/epm/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,30 @@ import { PackagePolicyConfigRecord } from '../../../../common';

const handlebars = Handlebars.create();

export function createStream(variables: PackagePolicyConfigRecord, streamTemplate: string) {
const { vars, yamlValues } = buildTemplateVariables(variables, streamTemplate);
export function compileTemplate(variables: PackagePolicyConfigRecord, templateStr: string) {
const { vars, yamlValues } = buildTemplateVariables(variables, templateStr);

const template = handlebars.compile(streamTemplate, { noEscape: true });
let stream = template(vars);
stream = replaceRootLevelYamlVariables(yamlValues, stream);
const template = handlebars.compile(templateStr, { noEscape: true });
let compiledTemplate = template(vars);
compiledTemplate = replaceRootLevelYamlVariables(yamlValues, compiledTemplate);

const yamlFromStream = safeLoad(stream, {});
const yamlFromCompiledTemplate = safeLoad(compiledTemplate, {});

// Hack to keep empty string ('') values around in the end yaml because
// `safeLoad` replaces empty strings with null
const patchedYamlFromStream = Object.entries(yamlFromStream).reduce((acc, [key, value]) => {
if (value === null && typeof vars[key] === 'string' && vars[key].trim() === '') {
acc[key] = '';
} else {
acc[key] = value;
}
return acc;
}, {} as { [k: string]: any });
const patchedYamlFromCompiledTemplate = Object.entries(yamlFromCompiledTemplate).reduce(
(acc, [key, value]) => {
if (value === null && typeof vars[key] === 'string' && vars[key].trim() === '') {
acc[key] = '';
} else {
acc[key] = value;
}
return acc;
},
{} as { [k: string]: any }
);

return replaceVariablesInYaml(yamlValues, patchedYamlFromStream);
return replaceVariablesInYaml(yamlValues, patchedYamlFromCompiledTemplate);
}

function isValidKey(key: string) {
Expand All @@ -54,7 +57,7 @@ function replaceVariablesInYaml(yamlVariables: { [k: string]: any }, yaml: any)
return yaml;
}

function buildTemplateVariables(variables: PackagePolicyConfigRecord, streamTemplate: string) {
function buildTemplateVariables(variables: PackagePolicyConfigRecord, templateStr: string) {
const yamlValues: { [k: string]: any } = {};
const vars = Object.entries(variables).reduce((acc, [key, recordEntry]) => {
// support variables with . like key.patterns
Expand Down
Loading