Include failure reason when packages cannot be installed during setup (#116612)

This commit is contained in:
Josh Dover 2021-10-28 16:47:26 +02:00 committed by GitHub
parent b102846c82
commit d5963884b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 22 deletions

View file

@ -9,7 +9,11 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve
import { SavedObjectsErrorHelpers } from '../../../../../src/core/server';
import type { PreconfiguredAgentPolicy, PreconfiguredOutput } from '../../common/types';
import type {
InstallResult,
PreconfiguredAgentPolicy,
PreconfiguredOutput,
} from '../../common/types';
import type { AgentPolicy, NewPackagePolicy, Output } from '../types';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants';
@ -30,6 +34,7 @@ jest.mock('./output');
const mockedOutputService = outputService as jest.Mocked<typeof outputService>;
const mockInstalledPackages = new Map();
const mockInstallPackageErrors = new Map<string, string>();
const mockConfiguredPolicies = new Map();
const mockDefaultOutput: Output = {
@ -99,8 +104,22 @@ function getPutPreconfiguredPackagesMock() {
}
jest.mock('./epm/packages/install', () => ({
installPackage({ pkgkey, force }: { pkgkey: string; force?: boolean }) {
async installPackage({
pkgkey,
force,
}: {
pkgkey: string;
force?: boolean;
}): Promise<InstallResult> {
const [pkgName, pkgVersion] = pkgkey.split('-');
const installError = mockInstallPackageErrors.get(pkgName);
if (installError) {
return {
error: new Error(installError),
installType: 'install',
};
}
const installedPackage = mockInstalledPackages.get(pkgName);
if (installedPackage) {
if (installedPackage.version === pkgVersion) return installedPackage;
@ -109,7 +128,10 @@ jest.mock('./epm/packages/install', () => ({
const packageInstallation = { name: pkgName, version: pkgVersion, title: pkgName };
mockInstalledPackages.set(pkgName, packageInstallation);
return packageInstallation;
return {
status: 'installed',
installType: 'install',
};
},
ensurePackagesCompletedInstall() {
return [];
@ -133,6 +155,8 @@ jest.mock('./epm/packages/get', () => ({
},
}));
jest.mock('./epm/kibana/index_pattern/install');
jest.mock('./package_policy', () => ({
...jest.requireActual('./package_policy'),
packagePolicyService: {
@ -177,6 +201,7 @@ const spyAgentPolicyServicBumpAllAgentPoliciesForOutput = jest.spyOn(
describe('policy preconfiguration', () => {
beforeEach(() => {
mockInstalledPackages.clear();
mockInstallPackageErrors.clear();
mockConfiguredPolicies.clear();
spyAgentPolicyServiceUpdate.mockClear();
spyAgentPolicyServicBumpAllAgentPoliciesForOutput.mockClear();
@ -266,7 +291,38 @@ describe('policy preconfiguration', () => {
);
});
it('should not create a policy if we are not able to add packages ', async () => {
it('should not create a policy and throw an error if install fails for required package', async () => {
const soClient = getPutPreconfiguredPackagesMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const policies: PreconfiguredAgentPolicy[] = [
{
name: 'Test policy',
namespace: 'default',
id: 'test-id',
package_policies: [
{
package: { name: 'test_package' },
name: 'Test package',
},
],
},
];
mockInstallPackageErrors.set('test_package', 'REGISTRY ERROR');
await expect(
ensurePreconfiguredPackagesAndPolicies(
soClient,
esClient,
policies,
[{ name: 'test_package', version: '3.0.0' }],
mockDefaultOutput
)
).rejects.toThrow(
'[Test policy] could not be added. [test_package] could not be installed due to error: [Error: REGISTRY ERROR]'
);
});
it('should not create a policy and throw an error if package is not installed for an unknown reason', async () => {
const soClient = getPutPreconfiguredPackagesMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const policies: PreconfiguredAgentPolicy[] = [
@ -283,22 +339,16 @@ describe('policy preconfiguration', () => {
},
];
let error;
try {
await ensurePreconfiguredPackagesAndPolicies(
await expect(
ensurePreconfiguredPackagesAndPolicies(
soClient,
esClient,
policies,
[{ name: 'CANNOT_MATCH', version: 'x.y.z' }],
mockDefaultOutput
);
} catch (err) {
error = err;
}
expect(error).toBeDefined();
expect(error.message).toEqual(
'Test policy could not be added. test_package is not installed, add test_package to `xpack.fleet.packages` or remove it from Test package.'
)
).rejects.toThrow(
'[Test policy] could not be added. [test_package] is not installed, add [test_package] to [xpack.fleet.packages] or remove it from [Test package].'
);
});
it('should not attempt to recreate or modify an agent policy if its ID is unchanged', async () => {

View file

@ -147,6 +147,8 @@ export async function ensurePreconfiguredPackagesAndPolicies(
packages: PreconfiguredPackage[] = [],
defaultOutput: Output
): Promise<PreconfigurationResult> {
const logger = appContextService.getLogger();
// Validate configured packages to ensure there are no version conflicts
const packageNames = groupBy(packages, (pkg) => pkg.name);
const duplicatePackages = Object.entries(packageNames).filter(
@ -181,15 +183,20 @@ export async function ensurePreconfiguredPackagesAndPolicies(
});
const fulfilledPackages = [];
const rejectedPackages = [];
const rejectedPackages: PreconfigurationError[] = [];
for (let i = 0; i < preconfiguredPackages.length; i++) {
const packageResult = preconfiguredPackages[i];
if ('error' in packageResult)
if ('error' in packageResult) {
logger.warn(
`Failed installing package [${packages[i].name}] due to error: [${packageResult.error}]`
);
rejectedPackages.push({
package: { name: packages[i].name, version: packages[i].version },
error: packageResult.error,
} as PreconfigurationError);
else fulfilledPackages.push(packageResult);
});
} else {
fulfilledPackages.push(packageResult);
}
}
// Keeping this outside of the Promise.all because it introduces a race condition.
@ -264,14 +271,14 @@ export async function ensurePreconfiguredPackagesAndPolicies(
);
const fulfilledPolicies = [];
const rejectedPolicies = [];
const rejectedPolicies: PreconfigurationError[] = [];
for (let i = 0; i < preconfiguredPolicies.length; i++) {
const policyResult = preconfiguredPolicies[i];
if (policyResult.status === 'rejected') {
rejectedPolicies.push({
error: policyResult.reason as Error,
agentPolicy: { name: policies[i].name },
} as PreconfigurationError);
});
continue;
}
fulfilledPolicies.push(policyResult.value);
@ -288,10 +295,25 @@ export async function ensurePreconfiguredPackagesAndPolicies(
pkgName: pkg.name,
});
if (!installedPackage) {
const rejectedPackage = rejectedPackages.find((rp) => rp.package?.name === pkg.name);
if (rejectedPackage) {
throw new Error(
i18n.translate('xpack.fleet.preconfiguration.packageRejectedError', {
defaultMessage: `[{agentPolicyName}] could not be added. [{pkgName}] could not be installed due to error: [{errorMessage}]`,
values: {
agentPolicyName: preconfiguredAgentPolicy.name,
pkgName: pkg.name,
errorMessage: rejectedPackage.error.toString(),
},
})
);
}
throw new Error(
i18n.translate('xpack.fleet.preconfiguration.packageMissingError', {
defaultMessage:
'{agentPolicyName} could not be added. {pkgName} is not installed, add {pkgName} to `{packagesConfigValue}` or remove it from {packagePolicyName}.',
'[{agentPolicyName}] could not be added. [{pkgName}] is not installed, add [{pkgName}] to [{packagesConfigValue}] or remove it from [{packagePolicyName}].',
values: {
agentPolicyName: preconfiguredAgentPolicy.name,
packagePolicyName: name,