Skip to content

Commit

Permalink
[Fleet] Show pipelines and mappings editor for input packages (#154077)
Browse files Browse the repository at this point in the history
## Summary

Closes #153105

Show the pipelines and mappings editor for input packages. This can be
seen when editing an input package integration policy.

Previously this was hidden for input packages and any integration
package which allowed datastream to be configured. I have tweaked this
logic so that we check if the matching index template for a stream
exists, if it does then show the editor.
<img width="407" alt="Screenshot 2023-03-30 at 17 02 26"
src="https://user-images.githubusercontent.com/3315046/228896543-192e5fc5-3dc3-4a0f-a08f-a7ff83555ef3.png">

This allows us to handle an edge case (which will become more common as
input packages are rolled out) when integration package which has since
been upgraded to an input package, for example:
- user installs the custom logs integration package and sets dataset to
helloworld
- the `logs-helloworld` index template is not created as integration
packages only create index templates on install (custom logs creates
`logs-log.log`)
- there is therefore no ingest pipeline `@custom` or `@package`component
template to customize, we do not show the pipeline editor.
- The user upgrades to an input package version of custom logs (coming
in v2.0.0) there still isn't an index template because we do not create
them on upgrade, so we still don't want to show the pipelines and
mappings editor
- Just checking for input package in this scenario would mean we showed
the editor incorrectly

I have opted to show the editors even if the stream is owned by another
package e.g custom logs data is being sent to an nginx stream.


### Automated tests

I have added cypress tests for checking that the mappings pipelines
editors are shown for input packages.

### Manual test cases

Here is version 2.0.0 of logs which is an input package:
[log-2.0.0.zip](https://github.com/elastic/kibana/files/11113579/log-2.0.0.zip)
it can be added to a deployment using curl:

```
curl -XPOST -H 'content-type: application/zip' -H 'kbn-xsrf: true' http://localhost:5601/mark/api/fleet/epm/packages -u elastic:changeme --data-binary @/path/to/log-2.0.0.zip
```


- Create a policy for an integration package which does not allow
dataset to be configured (e.g Apache)
    - pipelines & editor should be visible when editing package policy
- Create a policy for an integration package which does allow dataset to
be configured (e.g custom logs 1.6.1)
- pipelines & mappings editor should not be shown if dataset doesn't
match the one installed by the package `logs.log`
- Upgrade custom logs to the input package version (2.0.0)
    - pipelines and mappings editor still should not be shown

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
hop-dev and kibanamachine authored Apr 3, 2023
1 parent 7ce1a9a commit b80f4fd
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/dev/precommit_hook/casing_check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const IGNORE_FILE_GLOBS = [
'x-pack/plugins/canvas/server/templates/assets/*.{png,jpg,svg}',
'x-pack/plugins/cases/docs/**/*',
'x-pack/plugins/monitoring/public/lib/jquery_flot/**/*',
'x-pack/plugins/fleet/cypress/packages/*.zip',
'**/.*',
'**/__mocks__/**/*',
'x-pack/docs/**/*',
Expand Down
98 changes: 98 additions & 0 deletions x-pack/plugins/fleet/cypress/e2e/package_policy_real.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.
*/

import {
ADD_INTEGRATION_POLICY_BTN,
CREATE_PACKAGE_POLICY_SAVE_BTN,
INTEGRATION_NAME_LINK,
POLICY_EDITOR,
} from '../screens/integrations';
import { EXISTING_HOSTS_TAB } from '../screens/fleet';
import { CONFIRM_MODAL } from '../screens/navigation';

const TEST_PACKAGE = 'input_package-1.0.0';
const agentPolicyId = 'test-input-package-policy';
const agentPolicyName = 'Test input package policy';
const inputPackagePolicyName = 'input-package-policy';

function editPackagePolicyandShowAdvanced() {
cy.visit(`/app/integrations/detail/${TEST_PACKAGE}/policies`);

cy.getBySel(INTEGRATION_NAME_LINK).contains(inputPackagePolicyName).click();

cy.get('button').contains('Change defaults').click();
cy.get('[data-test-subj^="advancedStreamOptionsToggle"]').click();
}
describe('Input package create and edit package policy', () => {
before(() => {
cy.task('installTestPackage', TEST_PACKAGE);

cy.request({
method: 'POST',
url: `/api/fleet/agent_policies`,
body: {
id: agentPolicyId,
name: agentPolicyName,
description: 'desc',
namespace: 'default',
monitoring_enabled: [],
},
headers: { 'kbn-xsrf': 'cypress' },
});
});
after(() => {
// delete agent policy
cy.request({
method: 'POST',
url: `/api/fleet/agent_policies/delete`,
headers: { 'kbn-xsrf': 'cypress' },
body: JSON.stringify({
agentPolicyId,
}),
});
cy.task('uninstallTestPackage', TEST_PACKAGE);
});
it('should successfully create a package policy', () => {
cy.visit(`/app/integrations/detail/${TEST_PACKAGE}/overview`);
cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click();

cy.getBySel(POLICY_EDITOR.POLICY_NAME_INPUT).click().clear().type(inputPackagePolicyName);
cy.getBySel('multiTextInput-paths')
.find('[data-test-subj="multiTextInputRow-0"]')
.click()
.type('/var/log/test.log');

cy.getBySel('multiTextInput-tags')
.find('[data-test-subj="multiTextInputRow-0"]')
.click()
.type('tag1');

cy.getBySel(POLICY_EDITOR.DATASET_SELECT).click().type('testdataset');

cy.getBySel(EXISTING_HOSTS_TAB).click();

cy.getBySel(POLICY_EDITOR.AGENT_POLICY_SELECT).click().get(`#${agentPolicyId}`).click();
cy.wait(500); // wait for policy id to be set
cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click();

cy.getBySel(CONFIRM_MODAL.CANCEL_BUTTON).click();
});

it('should show pipelines editor with link to pipeline', () => {
editPackagePolicyandShowAdvanced();
cy.getBySel(POLICY_EDITOR.INSPECT_PIPELINES_BTN).click();
cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click();
cy.get('body').should('not.contain', 'Pipeline not found');
cy.get('body').should('contain', '"managed_by": "fleet"');
});
it('should show mappings editor with link to create custom template', () => {
editPackagePolicyandShowAdvanced();
cy.getBySel(POLICY_EDITOR.EDIT_MAPPINGS_BTN).click();
cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click();
cy.get('body').should('contain', 'logs-testdataset@custom');
});
});
Binary file not shown.
47 changes: 47 additions & 0 deletions x-pack/plugins/fleet/cypress/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,42 @@
* 2.0.
*/

import { promisify } from 'util';

import fs from 'fs';

import fetch from 'node-fetch';
import { createEsClientForTesting } from '@kbn/test';

const plugin: Cypress.PluginConfig = (on, config) => {
const client = createEsClientForTesting({
esUrl: config.env.ELASTICSEARCH_URL,
});

async function kibanaFetch(opts: {
method: string;
path: string;
body?: any;
contentType?: string;
}) {
const { method, path, body, contentType } = opts;
const Authorization = `Basic ${Buffer.from(
`elastic:${config.env.ELASTICSEARCH_PASSWORD}`
).toString('base64')}`;

const url = `${config.env.KIBANA_URL}${path}`;
const res = await fetch(url, {
method,
headers: {
'kbn-xsrf': 'cypress',
'Content-Type': contentType || 'application/json',
Authorization,
},
...(body ? { body } : {}),
});

return res.json();
}
on('task', {
async insertDoc({ index, doc, id }: { index: string; doc: any; id: string }) {
return client.create({ id, document: doc, index, refresh: 'wait_for' });
Expand All @@ -37,6 +67,23 @@ const plugin: Cypress.PluginConfig = (on, config) => {
conflicts: 'proceed',
});
},
async installTestPackage(packageName: string) {
const zipPath = require.resolve('../packages/' + packageName + '.zip');
const zipContent = await promisify(fs.readFile)(zipPath, 'base64');
return kibanaFetch({
method: 'POST',
path: '/api/fleet/epm/packages',
body: Buffer.from(zipContent, 'base64'),
contentType: 'application/zip',
});
},

async uninstallTestPackage(packageName: string) {
return kibanaFetch({
method: 'DELETE',
path: `/api/fleet/epm/packages/${packageName}`,
});
},
});
};

Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/fleet/cypress/screens/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export const SETTINGS = {
UNINSTALL_ASSETS_BTN: 'uninstallAssetsButton',
};

export const POLICY_EDITOR = {
POLICY_NAME_INPUT: 'packagePolicyNameInput',
DATASET_SELECT: 'datasetComboBox',
AGENT_POLICY_SELECT: 'agentPolicySelect',
INSPECT_PIPELINES_BTN: 'datastreamInspectPipelineBtn',
EDIT_MAPPINGS_BTN: 'datastreamEditMappingsBtn',
};

export const INTEGRATION_POLICIES_UPGRADE_CHECKBOX = 'epmDetails.upgradePoliciesCheckbox';

export const getIntegrationCard = (integration: string) => `integration-card:epr:${integration}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/

import { useRouteMatch } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';

import { useLink } from '../../../../hooks';
import { sendRequestForRq, useLink } from '../../../../hooks';

export function usePackagePolicyEditorPageUrl(dataStreamId?: string) {
const {
Expand All @@ -27,3 +28,32 @@ export function usePackagePolicyEditorPageUrl(dataStreamId?: string) {

return `${baseUrl}${dataStreamId ? `?datastreamId=${encodeURIComponent(dataStreamId)}` : ''}`;
}

export function useIndexTemplateExists(
templateName: string,
enabled: boolean
): {
exists?: boolean;
isLoading: boolean;
} {
const { data, isLoading } = useQuery(
['indexTemplateExists', templateName],
() =>
sendRequestForRq({
path: `/api/index_management/index_templates/${templateName}`,
method: 'get',
}),
{ enabled: enabled || !!templateName }
);

if (isLoading) {
return {
isLoading: true,
};
}

return {
exists: !!data,
isLoading: false,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { usePackagePolicyEditorPageUrl } from './datastream_hooks';

export interface PackagePolicyEditorDatastreamMappingsProps {
packageInfo: PackageInfo;
packageInputStream: { id?: string; data_stream: { dataset: string; type: string } };
packageInputStream: {
id?: string;
data_stream: { dataset: string; type: string };
};
customDataset?: string;
}

function useComponentTemplates(dataStream: { dataset: string; type: string }) {
Expand All @@ -35,8 +39,10 @@ function useComponentTemplates(dataStream: { dataset: string; type: string }) {

export const PackagePolicyEditorDatastreamMappings: React.FunctionComponent<
PackagePolicyEditorDatastreamMappingsProps
> = ({ packageInputStream, packageInfo }) => {
const dataStream = packageInputStream.data_stream;
> = ({ packageInputStream, packageInfo, customDataset }) => {
const dataStream = customDataset
? { ...packageInputStream.data_stream, dataset: customDataset }
: packageInputStream.data_stream;
const pageUrl = usePackagePolicyEditorPageUrl(packageInputStream.id);

const { application, docLinks } = useStartServices();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { usePackagePolicyEditorPageUrl } from './datastream_hooks';
export interface PackagePolicyEditorDatastreamPipelinesProps {
packageInfo: PackageInfo;
packageInputStream: { id?: string; data_stream: { dataset: string; type: string } };
customDataset?: string;
}

interface PipelineItem {
Expand Down Expand Up @@ -92,8 +93,10 @@ function useDatastreamIngestPipelines(

export const PackagePolicyEditorDatastreamPipelines: React.FunctionComponent<
PackagePolicyEditorDatastreamPipelinesProps
> = ({ packageInputStream, packageInfo }) => {
const dataStream = packageInputStream.data_stream;
> = ({ packageInputStream, packageInfo, customDataset }) => {
const dataStream = customDataset
? { ...packageInputStream.data_stream, dataset: customDataset }
: packageInputStream.data_stream;
const { application, share, docLinks } = useStartServices();
const ingestPipelineLocator = share.url.locators.get('INGEST_PIPELINES_APP_LOCATOR');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const DatasetComboBox: React.FC<{
})}
isClearable={false}
isDisabled={isDisabled}
data-test-subj="datasetComboBox"
/>
{valueAsOption && valueAsOption.value.package !== pkgName && (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface Props {
errors?: Array<{ message: string; index?: number }>;
isInvalid?: boolean;
isDisabled?: boolean;
'data-test-subj'?: string;
}

interface RowProps {
Expand Down Expand Up @@ -69,6 +70,7 @@ const Row: FunctionComponent<RowProps> = ({
autoFocus={autoFocus}
disabled={isDisabled}
onBlur={onBlur}
data-test-subj={`multiTextInputRow-${index}`}
/>
</EuiFlexItem>
{showDeleteButton && (
Expand Down Expand Up @@ -99,6 +101,7 @@ export const MultiTextInput: FunctionComponent<Props> = ({
isInvalid,
isDisabled,
errors,
'data-test-subj': dataTestSubj,
}) => {
const [autoFocus, setAutoFocus] = useState(false);
const [rows, setRows] = useState(() => defaultValue(value));
Expand Down Expand Up @@ -139,7 +142,7 @@ export const MultiTextInput: FunctionComponent<Props> = ({

return (
<>
<EuiFlexGroup gutterSize="s" direction="column">
<EuiFlexGroup gutterSize="s" direction="column" data-test-subj={dataTestSubj}>
{rows.map((row, idx) => (
<EuiFlexItem key={idx}>
<Row
Expand Down
Loading

0 comments on commit b80f4fd

Please sign in to comment.