Skip to content

Commit

Permalink
[Fleet] Add "Keep Policies up to Date" functionality for integrations (
Browse files Browse the repository at this point in the history
…elastic#112702)

* Add initial implementation for keep policies up to date functionality

* Upgrade package policies during preconfiguration check

* Only show keep policies up to date switch for default/auto-update packages

* Fix type error

* Fixup setup policy upgrade logic

* Add migration for keep policies up to date flag

* Move setup package policy logic to new module + add tests

* Update snapshots to include keepPoliciesUpToDate field

* Fix type errors

* Fix some CI failures

* Fix more type errors

* Fix type error in isolation test

* Fix package fixtures types

* Fix another type error

* Move policy upgrade error swallowing up a level in setup

* Address PR feedback

- Move keep policies up to date switch to separate component
- Use PACKAGE_POLICY_SAVED_OBJECT_TYPE instead of magic string

* Fix overwriting user values when upgrading

Fixes elastic#113731

* Add test package

* Fix tests for overridePackageVars

* Address PR feedback

- Don't index keep_policies_up_to_date field
- Use SO_SEARCH_LIMIT constant instead of magic number

* Make toast translation usage more consistent

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
kpollich and kibanamachine committed Oct 5, 2021
1 parent e870b6f commit 426b119
Show file tree
Hide file tree
Showing 41 changed files with 796 additions and 13 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export const epmRouteService = {
getRemovePath: (pkgkey: string) => {
return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash
},

getUpdatePath: (pkgkey: string) => {
return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgkey}', pkgkey);
},
};

export const packagePolicyRouteService = {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ export interface EpmPackageAdditions {
assets: AssetsGroupedByServiceByType;
removable?: boolean;
notice?: string;
keepPoliciesUpToDate?: boolean;
}

type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, keyof SecondType>> &
Expand Down Expand Up @@ -391,6 +392,7 @@ export interface Installation extends SavedObjectAttributes {
install_version: string;
install_started_at: string;
install_source: InstallSource;
keep_policies_up_to_date: boolean;
}

export interface PackageUsageStats {
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/fleet/common/types/rest_spec/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ export interface GetInfoResponse {
response: PackageInfo;
}

export interface UpdatePackageRequest {
params: {
pkgkey: string;
};
body: {
keepPoliciesUpToDate?: boolean;
};
}

export interface UpdatePackageResponse {
response: PackageInfo;
}

export interface GetStatsRequest {
params: {
pkgname: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const Installed = ({ width, ...props }: Args) => {
install_status: 'installed',
install_source: 'registry',
install_started_at: '2020-01-01T00:00:00.000Z',
keep_policies_up_to_date: false,
},
references: [],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const savedObject: SavedObject<Installation> = {
install_status: 'installed',
install_source: 'registry',
install_started_at: '2020-01-01T00:00:00.000Z',
keep_policies_up_to_date: false,
},
references: [],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
export { UpdateIcon } from './update_icon';
export { IntegrationAgentPolicyCount } from './integration_agent_policy_count';
export { IconPanel, LoadingIconPanel } from './icon_panel';
export { KeepPoliciesUpToDateSwitch } from './keep_policies_up_to_date_switch';
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSwitch, EuiSpacer, EuiText, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';

interface Props {
checked: boolean;
onChange: () => void;
}

export const KeepPoliciesUpToDateSwitch: React.FunctionComponent<Props> = ({
checked,
onChange,
}) => (
<>
<EuiSwitch
label={i18n.translate(
'xpack.fleet.integrations.settings.keepIntegrationPoliciesUpToDateLabel',
{ defaultMessage: 'Keep integration policies up to date automatically' }
)}
checked={checked}
onChange={onChange}
/>
<EuiSpacer size="s" />
<EuiText color="subdued" size="xs">
<EuiFlexGroup alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiIcon type="iInCircle" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.fleet.integrations.settings.keepIntegrationPoliciesUpToDateDescription"
defaultMessage="When enabled, Fleet will attempt to upgrade and deploy integration policies automatically"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
</>
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import React, { memo, useEffect, useMemo, useState } from 'react';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { FormattedMessage } from '@kbn/i18n/react';
import semverLt from 'semver/functions/lt';
import { uniq } from 'lodash';

import {
EuiCallOut,
Expand All @@ -29,8 +30,16 @@ import {
useGetPackageInstallStatus,
useLink,
sendUpgradePackagePolicyDryRun,
sendUpdatePackage,
useStartServices,
} from '../../../../../hooks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
import {
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
AUTO_UPDATE_PACKAGES,
DEFAULT_PACKAGES,
} from '../../../../../constants';

import { KeepPoliciesUpToDateSwitch } from '../components';

import { InstallButton } from './install_button';
import { UpdateButton } from './update_button';
Expand Down Expand Up @@ -85,7 +94,7 @@ interface Props {
}

export const SettingsPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
const { name, title, removable, latestVersion, version } = packageInfo;
const { name, title, removable, latestVersion, version, keepPoliciesUpToDate } = packageInfo;
const [dryRunData, setDryRunData] = useState<UpgradePackagePolicyDryRunResponse | null>();
const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState<boolean>(false);
const getPackageInstallStatus = useGetPackageInstallStatus();
Expand All @@ -95,6 +104,67 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`,
});

const { notifications } = useStartServices();

const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => {
const packages = [...DEFAULT_PACKAGES, ...AUTO_UPDATE_PACKAGES];

const packageNames = uniq(packages.map((pkg) => pkg.name));

return packageNames.includes(name);
}, [name]);

const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState<boolean>(
keepPoliciesUpToDate ?? false
);

const handleKeepPoliciesUpToDateSwitchChange = useCallback(() => {
const saveKeepPoliciesUpToDate = async () => {
try {
setKeepPoliciesUpToDateSwitchValue((prev) => !prev);

await sendUpdatePackage(`${packageInfo.name}-${packageInfo.version}`, {
keepPoliciesUpToDate: !keepPoliciesUpToDateSwitchValue,
});

notifications.toasts.addSuccess({
title: i18n.translate('xpack.fleet.integrations.integrationSaved', {
defaultMessage: 'Integration settings saved',
}),
text: !keepPoliciesUpToDateSwitchValue
? i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateEnabledSuccess', {
defaultMessage:
'Fleet will automatically keep integration policies up to date for {title}',
values: { title },
})
: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateDisabledSuccess', {
defaultMessage:
'Fleet will not automatically keep integration policies up to date for {title}',
values: { title },
}),
});
} catch (error) {
notifications.toasts.addError(error, {
title: i18n.translate('xpack.fleet.integrations.integrationSavedError', {
defaultMessage: 'Error saving integration settings',
}),
toastMessage: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateError', {
defaultMessage: 'Error saving integration settings for {title}',
values: { title },
}),
});
}
};

saveKeepPoliciesUpToDate();
}, [
keepPoliciesUpToDateSwitchValue,
notifications.toasts,
packageInfo.name,
packageInfo.version,
title,
]);

const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name);
const packageHasUsages = !!packagePoliciesData?.total;

Expand Down Expand Up @@ -199,6 +269,16 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo }: Props) => {
</tr>
</tbody>
</table>
{shouldShowKeepPoliciesUpToDateSwitch && (
<>
<KeepPoliciesUpToDateSwitch
checked={keepPoliciesUpToDateSwitchValue}
onChange={handleKeepPoliciesUpToDateSwitchChange}
/>
<EuiSpacer size="l" />
</>
)}

{(updateAvailable || isUpgradingPackagePolicies) && (
<>
<UpdatesAvailableMsg latestVersion={latestVersion} />
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/public/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export {
// Fleet Server index
AGENTS_INDEX,
ENROLLMENT_API_KEYS_INDEX,
// Preconfiguration
AUTO_UPDATE_PACKAGES,
DEFAULT_PACKAGES,
} from '../../common/constants';

export * from './page_paths';
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/fleet/public/hooks/use_request/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import type {
GetInfoResponse,
InstallPackageResponse,
DeletePackageResponse,
UpdatePackageRequest,
UpdatePackageResponse,
} from '../../types';
import type { GetStatsResponse } from '../../../common';

Expand Down Expand Up @@ -113,3 +115,11 @@ export const sendRemovePackage = (pkgkey: string) => {
method: 'delete',
});
};

export const sendUpdatePackage = (pkgkey: string, body: UpdatePackageRequest['body']) => {
return sendRequest<UpdatePackageResponse>({
path: epmRouteService.getUpdatePath(pkgkey),
method: 'put',
body,
});
};
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/public/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export {
Installable,
RegistryRelease,
PackageSpecCategory,
UpdatePackageRequest,
UpdatePackageResponse,
} from '../../common';

export * from './intra_app_route_state';
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export {
// Preconfiguration
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
PRECONFIGURATION_LATEST_KEYWORD,
AUTO_UPDATE_PACKAGES,
} from '../../common';

export {
Expand Down
25 changes: 25 additions & 0 deletions x-pack/plugins/fleet/server/routes/epm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
BulkInstallPackagesResponse,
IBulkInstallPackageHTTPError,
GetStatsResponse,
UpdatePackageResponse,
} from '../../../common';
import type {
GetCategoriesRequestSchema,
Expand All @@ -33,6 +34,7 @@ import type {
DeletePackageRequestSchema,
BulkUpgradePackagesFromRegistryRequestSchema,
GetStatsRequestSchema,
UpdatePackageRequestSchema,
} from '../../types';
import {
bulkInstallPackages,
Expand All @@ -53,6 +55,7 @@ import { licenseService } from '../../services';
import { getArchiveEntry } from '../../services/epm/archive/cache';
import { getAsset } from '../../services/epm/archive/storage';
import { getPackageUsageStats } from '../../services/epm/packages/get';
import { updatePackage } from '../../services/epm/packages/update';

export const getCategoriesHandler: RequestHandler<
undefined,
Expand Down Expand Up @@ -201,6 +204,28 @@ export const getInfoHandler: RequestHandler<TypeOf<typeof GetInfoRequestSchema.p
}
};

export const updatePackageHandler: RequestHandler<
TypeOf<typeof UpdatePackageRequestSchema.params>,
unknown,
TypeOf<typeof UpdatePackageRequestSchema.body>
> = async (context, request, response) => {
try {
const { pkgkey } = request.params;
const savedObjectsClient = context.core.savedObjects.client;

const { pkgName } = splitPkgKey(pkgkey);

const res = await updatePackage({ savedObjectsClient, pkgName, ...request.body });
const body: UpdatePackageResponse = {
response: res,
};

return response.ok({ body });
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};

export const getStatsHandler: RequestHandler<TypeOf<typeof GetStatsRequestSchema.params>> = async (
context,
request,
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/fleet/server/routes/epm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
DeletePackageRequestSchema,
BulkUpgradePackagesFromRegistryRequestSchema,
GetStatsRequestSchema,
UpdatePackageRequestSchema,
} from '../../types';

import {
Expand All @@ -31,6 +32,7 @@ import {
deletePackageHandler,
bulkInstallPackagesFromRegistryHandler,
getStatsHandler,
updatePackageHandler,
} from './handlers';

const MAX_FILE_SIZE_BYTES = 104857600; // 100MB
Expand Down Expand Up @@ -90,6 +92,15 @@ export const registerRoutes = (router: IRouter) => {
getInfoHandler
);

router.put(
{
path: EPM_API_ROUTES.INFO_PATTERN,
validate: UpdatePackageRequestSchema,
options: { tags: [`access:${PLUGIN_ID}-all`] },
},
updatePackageHandler
);

router.post(
{
path: EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN,
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from './migrations/to_v7_13_0';
import { migratePackagePolicyToV7140, migrateInstallationToV7140 } from './migrations/to_v7_14_0';
import { migratePackagePolicyToV7150 } from './migrations/to_v7_15_0';
import { migrateInstallationToV7160 } from './migrations/to_v7_16_0';

/*
* Saved object types and mappings
Expand Down Expand Up @@ -298,6 +299,7 @@ const getSavedObjectTypes = (
version: { type: 'keyword' },
internal: { type: 'boolean' },
removable: { type: 'boolean' },
keep_policies_up_to_date: { type: 'boolean', index: false },
es_index_patterns: {
enabled: false,
type: 'object',
Expand Down Expand Up @@ -332,6 +334,7 @@ const getSavedObjectTypes = (
migrations: {
'7.14.0': migrateInstallationToV7140,
'7.14.1': migrateInstallationToV7140,
'7.16.0': migrateInstallationToV7160,
},
},
[ASSETS_SAVED_OBJECT_TYPE]: {
Expand Down
Loading

0 comments on commit 426b119

Please sign in to comment.