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] optimizing package policy upgrade and dry run #126088

Merged
merged 12 commits into from
Feb 23, 2022
2 changes: 0 additions & 2 deletions x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ export const xpackMocks = {

export const createPackagePolicyServiceMock = (): jest.Mocked<PackagePolicyServiceInterface> => {
return {
_compilePackagePolicyInputs: jest.fn(),
buildPackagePolicyFromPackage: jest.fn(),
buildPackagePolicyFromPackageWithVersion: jest.fn(),
bulkCreate: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,16 @@ import type { PackagePolicy } from '../../types';

import { registerRoutes } from './index';

type PackagePolicyServicePublicInterface = Omit<
PackagePolicyServiceInterface,
'getUpgradePackagePolicyInfo'
>;

const packagePolicyServiceMock = packagePolicyService as jest.Mocked<PackagePolicyServiceInterface>;

jest.mock(
'../../services/package_policy',
(): {
packagePolicyService: jest.Mocked<PackagePolicyServicePublicInterface>;
packagePolicyService: jest.Mocked<PackagePolicyServiceInterface>;
} => {
return {
packagePolicyService: {
_compilePackagePolicyInputs: jest.fn((registryPkgInfo, packageInfo, vars, dataInputs) =>
_compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) =>
Promise.resolve(dataInputs)
),
buildPackagePolicyFromPackage: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,24 @@ describe('upgradeManagedPackagePolicies', () => {
it('should upgrade policies for managed package', async () => {
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const soClient = savedObjectsClientMock.create();
const packagePolicy = {
id: 'managed-package-id',
inputs: {},
version: '',
revision: 1,
updated_at: '',
updated_by: '',
created_at: '',
created_by: '',
package: {
name: 'managed-package',
title: 'Managed Package',
version: '0.0.1',
},
};

(packagePolicyService.list as jest.Mock).mockResolvedValueOnce({
items: [
{
id: 'managed-package-id',
inputs: {},
version: '',
revision: 1,
updated_at: '',
updated_by: '',
created_at: '',
created_by: '',
package: {
name: 'managed-package',
title: 'Managed Package',
version: '0.0.1',
},
},
],
items: [packagePolicy],
});

(packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockResolvedValueOnce({
Expand All @@ -89,7 +88,14 @@ describe('upgradeManagedPackagePolicies', () => {
{ packagePolicyId: 'managed-package-id', diff: [{ id: 'foo' }, { id: 'bar' }], errors: [] },
]);

expect(packagePolicyService.upgrade).toBeCalledWith(soClient, esClient, ['managed-package-id']);
expect(packagePolicyService.upgrade).toBeCalledWith(
soClient,
esClient,
['managed-package-id'],
undefined,
packagePolicy,
'1.0.0'
);
});

it('should not upgrade policy if newer than installed package version', async () => {
Expand Down
29 changes: 21 additions & 8 deletions x-pack/plugins/fleet/server/services/managed_package_policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const upgradeManagedPackagePolicies = async (

for (const packagePolicy of packagePolicies) {
if (isPolicyVersionLtInstalledVersion(packagePolicy, installedPackage)) {
await upgradePackagePolicy(soClient, esClient, packagePolicy.id, results);
await upgradePackagePolicy(soClient, esClient, packagePolicy, installedPackage, results);
}
}
}
Expand Down Expand Up @@ -84,13 +84,19 @@ function isPolicyVersionLtInstalledVersion(
async function upgradePackagePolicy(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
packagePolicyId: string,
packagePolicy: PackagePolicy,
installedPackage: Installation,
results: UpgradeManagedPackagePoliciesResult[]
) {
// Since upgrades don't report diffs/errors, we need to perform a dry run first in order
// to notify the user of any granular policy upgrade errors that occur during Fleet's
// preconfiguration check
const dryRunResults = await packagePolicyService.getUpgradeDryRunDiff(soClient, packagePolicyId);
const dryRunResults = await packagePolicyService.getUpgradeDryRunDiff(
soClient,
packagePolicy.id,
packagePolicy,
installedPackage.version
);

if (dryRunResults.hasErrors) {
const errors = dryRunResults.diff
Expand All @@ -100,17 +106,24 @@ async function upgradePackagePolicy(
appContextService
.getLogger()
.error(
new Error(`Error upgrading package policy ${packagePolicyId}: ${JSON.stringify(errors)}`)
new Error(`Error upgrading package policy ${packagePolicy.id}: ${JSON.stringify(errors)}`)
);

results.push({ packagePolicyId, diff: dryRunResults.diff, errors });
results.push({ packagePolicyId: packagePolicy.id, diff: dryRunResults.diff, errors });
return;
}

try {
await packagePolicyService.upgrade(soClient, esClient, [packagePolicyId]);
results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [] });
await packagePolicyService.upgrade(
soClient,
esClient,
[packagePolicy.id],
undefined,
packagePolicy,
installedPackage.version
);
results.push({ packagePolicyId: packagePolicy.id, diff: dryRunResults.diff, errors: [] });
} catch (error) {
results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [error] });
results.push({ packagePolicyId: packagePolicy.id, diff: dryRunResults.diff, errors: [error] });
}
}
56 changes: 21 additions & 35 deletions x-pack/plugins/fleet/server/services/package_policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import type {
NewPackagePolicy,
NewPackagePolicyInput,
PackagePolicyPackage,
RegistryPackage,
} from '../../common';
import { packageToPackagePolicy } from '../../common';

Expand All @@ -50,9 +49,11 @@ import {
updatePackageInputs,
packagePolicyService,
_applyIndexPrivileges,
_compilePackagePolicyInputs,
} from './package_policy';
import { appContextService } from './app_context';
import { fetchInfo } from './epm/registry';

import { getPackageInfo } from './epm/packages';

async function mockedGetAssetsData(_a: any, _b: any, dataset: string) {
if (dataset === 'dataset1') {
Expand Down Expand Up @@ -113,10 +114,6 @@ async function mockedGetPackageInfo(params: any) {
return Promise.resolve(pkg);
}

function mockedRegistryInfo(): RegistryPackage {
return {} as RegistryPackage;
}

jest.mock('./epm/packages/assets', () => {
return {
getAssetsData: mockedGetAssetsData,
Expand All @@ -125,7 +122,7 @@ jest.mock('./epm/packages/assets', () => {

jest.mock('./epm/packages', () => {
return {
getPackageInfo: mockedGetPackageInfo,
getPackageInfo: jest.fn().mockImplementation(mockedGetPackageInfo),
getInstallation: mockedGetInstallation,
};
});
Expand Down Expand Up @@ -170,18 +167,12 @@ jest.mock('./upgrade_sender', () => {
};
});

const mockedFetchInfo = fetchInfo as jest.Mock<ReturnType<typeof fetchInfo>>;

type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback;

describe('Package policy service', () => {
beforeEach(() => {
mockedFetchInfo.mockResolvedValue({} as RegistryPackage);
});
describe('_compilePackagePolicyInputs', () => {
it('should work with config variables from the stream', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [
{
Expand Down Expand Up @@ -244,8 +235,7 @@ describe('Package policy service', () => {
});

it('should work with a two level dataset name', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [
{
Expand Down Expand Up @@ -297,8 +287,7 @@ describe('Package policy service', () => {
});

it('should work with config variables at the input level', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [
{
Expand Down Expand Up @@ -361,8 +350,7 @@ describe('Package policy service', () => {
});

it('should work with config variables at the package level', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [
{
Expand Down Expand Up @@ -430,8 +418,7 @@ describe('Package policy service', () => {
});

it('should work with an input with a template and no streams', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [],
policy_templates: [
Expand Down Expand Up @@ -473,8 +460,7 @@ describe('Package policy service', () => {
});

it('should work with an input with a template and streams', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
data_streams: [
{
Expand Down Expand Up @@ -579,8 +565,7 @@ describe('Package policy service', () => {
});

it('should work with a package without input', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
policy_templates: [
{
Expand All @@ -596,8 +581,7 @@ describe('Package policy service', () => {
});

it('should work with a package with a empty inputs array', async () => {
const inputs = await packagePolicyService._compilePackagePolicyInputs(
mockedRegistryInfo(),
const inputs = await _compilePackagePolicyInputs(
{
policy_templates: [
{
Expand Down Expand Up @@ -906,14 +890,16 @@ describe('Package policy service', () => {
...mockPackagePolicy,
inputs: [],
};

mockedFetchInfo.mockResolvedValue({
elasticsearch: {
privileges: {
cluster: ['monitor'],
(getPackageInfo as jest.Mock).mockImplementation(async (params) => {
return Promise.resolve({
...(await mockedGetPackageInfo(params)),
elasticsearch: {
privileges: {
cluster: ['monitor'],
},
},
},
} as RegistryPackage);
} as PackageInfo);
});

savedObjectsClient.get.mockResolvedValue({
id: 'test',
Expand Down
Loading