[Fleet] Support simplfied package policy in preconfiguration (#179087)

This commit is contained in:
Nicolas Chaulet 2024-03-22 09:27:35 -04:00 committed by GitHub
parent 8fcf476cbe
commit 0092bb16aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 236 additions and 94 deletions

View file

@ -137,7 +137,7 @@ List of agent policies that are configured when the {fleet} app starts.
`namespace`::::
String identifying this policy's namespace.
`inputs`::::
Array that overrides any default input settings for this integration. Follows the same schema as integration inputs, with the exception that any object in `vars` can be passed `frozen: true` in order to prevent that specific `var` from being edited by the user.
Map of input for the integration. Follows the same schema as the package policy API inputs, with the exception that any object in `vars` can be passed `frozen: true` in order to prevent that specific `var` from being edited by the user.
=======
=====
+
@ -151,27 +151,25 @@ xpack.fleet.packages:
xpack.fleet.agentPolicies:
- name: Preconfigured Policy
id: 1
id: preconfigured-policy
namespace: test
package_policies:
- package:
name: system
name: System Integration
namespace: test
id: preconfigured-system
inputs:
- type: system/metrics
system-system/metrics:
enabled: true
vars:
- name: system.hostfs
value: home/test
'[system.hostfs]': home/test
streams:
- data_stream:
dataset: system.core
'[system.core]':
enabled: true
vars:
- name: period
value: 20s
- type: winlog
period: 20s
system-winlog:
enabled: false
----

View file

@ -146,6 +146,7 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
vars: packageLevelVars,
} = data;
const packagePolicy = packageToPackagePolicy(packageInfo, policyId, namespace, name, description);
if (packagePolicy.package && options?.experimental_data_stream_features) {
packagePolicy.package.experimental_data_stream_features =
options.experimental_data_stream_features;

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import type { SimplifiedPackagePolicy } from '../../services/simplified_package_policy_helper';
import type {
PackagePolicyPackage,
NewPackagePolicy,
@ -24,12 +26,16 @@ export interface PreconfiguredAgentPolicy extends Omit<NewAgentPolicy, 'namespac
id: string | number;
namespace?: string;
package_policies: Array<
Partial<Omit<NewPackagePolicy, 'inputs' | 'package'>> & {
id?: string | number;
name: string;
package: Partial<PackagePolicyPackage> & { name: string };
inputs?: InputsOverride[];
}
| (Partial<Omit<NewPackagePolicy, 'inputs' | 'package'>> & {
id?: string | number;
name: string;
package: Partial<PackagePolicyPackage> & { name: string };
inputs?: InputsOverride[];
})
| (Omit<SimplifiedPackagePolicy, 'policy_id'> & {
id: string;
package: { name: string };
})
>;
}

View file

@ -233,6 +233,22 @@ jest.mock('./epm/packages/get', () => ({
return {
status: 'installed',
...installedPackage,
policy_templates: [
{
name: 'test_template',
inputs: [
{
type: 'foo',
vars: [
{
name: 'bar',
type: 'text',
},
],
},
],
},
],
};
},
getInstallation({ pkgName }: { pkgName: string }) {
@ -379,6 +395,72 @@ describe('policy preconfiguration', () => {
expect(nonFatalErrors.length).toBe(0);
});
it('should install packages and configure agent policies successfully if using simplified package policy', async () => {
const soClient = getPutPreconfiguredPackagesMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const { policies, packages, nonFatalErrors } = await ensurePreconfiguredPackagesAndPolicies(
soClient,
esClient,
[
{
name: 'Test policy',
namespace: 'default',
id: 'test-id',
package_policies: [
{
id: 'test-1',
name: 'Test package',
namespace: 'default',
description: 'test',
package: { name: 'test_package' },
inputs: {
'test_template-foo': {
vars: {
bar: 'test',
},
},
},
},
],
},
] as PreconfiguredAgentPolicy[],
[{ name: 'test_package', version: '3.0.0' }],
mockDefaultOutput,
mockDefaultDownloadService,
DEFAULT_SPACE_ID
);
expect(policies.length).toEqual(1);
expect(policies[0].id).toBe('test-id');
expect(packages).toEqual(expect.arrayContaining(['test_package-3.0.0']));
expect(nonFatalErrors.length).toBe(0);
expect(mockedPackagePolicyService.create).toBeCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({
description: 'test',
enabled: true,
inputs: [
{
enabled: true,
policy_template: 'test_template',
streams: [],
type: 'foo',
vars: { bar: { type: 'text', value: 'test' } },
},
],
name: 'Test package',
namespace: 'default',
package: { name: 'test_package', title: 'test_package', version: '3.0.0' },
policy_id: 'test-id',
vars: undefined,
}),
expect.objectContaining({ id: 'test-1' })
);
});
it('should install prelease packages if needed', async () => {
const soClient = getPutPreconfiguredPackagesMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
@ -869,7 +951,9 @@ describe('policy preconfiguration', () => {
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
// Install an older version of a test package
mockInstalledPackages.set('test_package', { version: '0.9.0' });
mockInstalledPackages.set('test_package', {
version: '0.9.0',
});
const { policies, packages, nonFatalErrors } =
await ensurePreconfiguredPackagesAndPolicies(

View file

@ -25,6 +25,10 @@ import type {
import type { PreconfigurationError } from '../../common/constants';
import { PRECONFIGURATION_LATEST_KEYWORD } from '../../common/constants';
import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE } from '../constants';
import {
type SimplifiedPackagePolicy,
simplifiedPackagePolicytoNewPackagePolicy,
} from '../../common/services/simplified_package_policy_helper';
import { FleetError } from '../errors';
@ -34,7 +38,7 @@ import { getInstallation, getPackageInfo } from './epm/packages';
import { ensurePackagesCompletedInstall } from './epm/packages/install';
import { bulkInstallPackages } from './epm/packages/bulk_install_packages';
import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy';
import type { InputsOverride } from './package_policy';
import { type InputsOverride, packagePolicyService } from './package_policy';
import { preconfigurePackageInputs } from './package_policy';
import { appContextService } from './app_context';
import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies';
@ -219,7 +223,8 @@ export async function ensurePreconfiguredPackagesAndPolicies(
true
);
const installedPackagePolicies = await Promise.all(
packagePolicies.map(async ({ package: pkg, name, ...newPackagePolicy }) => {
packagePolicies.map(async (preconfiguredPackagePolicy) => {
const { package: pkg, ...newPackagePolicy } = preconfiguredPackagePolicy;
const installedPackage = await getInstallation({
savedObjectsClient: soClient,
pkgName: pkg.name,
@ -245,22 +250,23 @@ export async function ensurePreconfiguredPackagesAndPolicies(
'[{agentPolicyName}] could not be added. [{pkgName}] is not installed, add [{pkgName}] to [{packagesConfigValue}] or remove it from [{packagePolicyName}].',
values: {
agentPolicyName: preconfiguredAgentPolicy.name,
packagePolicyName: name,
packagePolicyName: newPackagePolicy.name,
pkgName: pkg.name,
packagesConfigValue: 'xpack.fleet.packages',
},
})
);
}
return { name, installedPackage, ...newPackagePolicy };
return { installedPackage, packagePolicy: newPackagePolicy };
})
);
const packagePoliciesToAdd = installedPackagePolicies.filter((installablePackagePolicy) => {
return !(agentPolicyWithPackagePolicies?.package_policies as PackagePolicy[]).some(
(packagePolicy) =>
(packagePolicy.id !== undefined && packagePolicy.id === installablePackagePolicy.id) ||
packagePolicy.name === installablePackagePolicy.name
(packagePolicy.id !== undefined &&
packagePolicy.id === installablePackagePolicy.packagePolicy.id) ||
packagePolicy.name === installablePackagePolicy.packagePolicy.name
);
});
logger.debug(`Adding preconfigured package policies ${packagePoliciesToAdd}`);
@ -330,14 +336,16 @@ async function addPreconfiguredPolicyPackages(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentPolicy: AgentPolicy,
installedPackagePolicies: Array<
Partial<Omit<NewPackagePolicy, 'inputs'>> & {
id?: string | number;
name: string;
installedPackage: Installation;
inputs?: InputsOverride[];
}
>,
installedPackagePolicies: Array<{
installedPackage: Installation;
packagePolicy:
| (Partial<Omit<NewPackagePolicy, 'inputs'>> & {
id?: string | number;
name: string;
inputs?: InputsOverride[];
})
| (Omit<SimplifiedPackagePolicy, 'package' | 'policy_id'> & { id: string });
}>,
defaultOutput: Output,
bumpAgentPolicyRevison = false
) {
@ -346,9 +354,8 @@ async function addPreconfiguredPolicyPackages(
const packageInfoMap = new Map<string, PackageInfo>();
// Add packages synchronously to avoid overwriting
for (const { installedPackage, id, name, description, inputs } of installedPackagePolicies) {
for (const { installedPackage, packagePolicy } of installedPackagePolicies) {
let packageInfo: PackageInfo;
if (packageInfoMap.has(installedPackage.name)) {
packageInfo = packageInfoMap.get(installedPackage.name)!;
} else {
@ -359,18 +366,45 @@ async function addPreconfiguredPolicyPackages(
});
}
await addPackageToAgentPolicy(
soClient,
esClient,
installedPackage,
agentPolicy,
defaultOutput,
packageInfo,
name,
id,
description,
(policy) => preconfigurePackageInputs(policy, packageInfo, inputs),
bumpAgentPolicyRevison
);
if (Array.isArray(packagePolicy.inputs)) {
const { id, name, description, inputs } = packagePolicy;
await addPackageToAgentPolicy(
soClient,
esClient,
installedPackage,
agentPolicy,
defaultOutput,
packageInfo,
name,
id,
description,
(policy) => preconfigurePackageInputs(policy, packageInfo, inputs),
bumpAgentPolicyRevison
);
} else {
const simplifiedPackagePolicy = packagePolicy as SimplifiedPackagePolicy;
const id = simplifiedPackagePolicy.id?.toString();
// Simplified package policy
const newPackagePolicy = simplifiedPackagePolicytoNewPackagePolicy(
{
...(simplifiedPackagePolicy as SimplifiedPackagePolicy),
id,
policy_id: agentPolicy.id,
namespace: packagePolicy.namespace || agentPolicy.namespace,
},
packageInfo,
{}
);
await packagePolicyService.create(soClient, esClient, newPackagePolicy, {
id,
bumpRevision: bumpAgentPolicyRevison,
skipEnsureInstalled: true,
skipUniqueNameVerification: true,
overwrite: true,
force: true, // To add package to managed policy we need the force flag
packageInfo,
});
}
}
}

View file

@ -160,18 +160,11 @@ const SimplifiedVarsSchema = schema.recordOf(
)
);
export const SimplifiedCreatePackagePolicyRequestBodySchema = schema.object({
export const SimplifiedPackagePolicyBaseSchema = schema.object({
id: schema.maybe(schema.string()),
name: schema.string(),
description: schema.maybe(schema.string()),
policy_id: schema.string(),
namespace: schema.maybe(schema.string()),
package: schema.object({
name: schema.string(),
version: schema.string(),
experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
}),
force: schema.maybe(schema.boolean()),
vars: schema.maybe(SimplifiedVarsSchema),
inputs: schema.maybe(
schema.recordOf(
@ -193,6 +186,26 @@ export const SimplifiedCreatePackagePolicyRequestBodySchema = schema.object({
),
});
export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolicyBaseSchema.extends(
{
id: schema.string(),
package: schema.object({
name: schema.string(),
}),
}
);
export const SimplifiedCreatePackagePolicyRequestBodySchema =
SimplifiedPackagePolicyBaseSchema.extends({
policy_id: schema.string(),
force: schema.maybe(schema.boolean()),
package: schema.object({
name: schema.string(),
version: schema.string(),
experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
}),
});
export const UpdatePackagePolicyRequestBodySchema = schema.object({
...CreatePackagePolicyProps,
name: schema.maybe(schema.string()),

View file

@ -19,7 +19,10 @@ import {
} from './output';
import { AgentPolicyBaseSchema, AgentPolicyNamespaceSchema } from './agent_policy';
import { PackagePolicyNamespaceSchema } from './package_policy';
import {
PackagePolicyNamespaceSchema,
SimplifiedPackagePolicyPreconfiguredSchema,
} from './package_policy';
const varsSchema = schema.maybe(
schema.arrayOf(
@ -139,47 +142,50 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf(
data_output_id: schema.maybe(schema.string()),
monitoring_output_id: schema.maybe(schema.string()),
package_policies: schema.arrayOf(
schema.object({
id: schema.maybe(schema.oneOf([schema.string(), schema.number()])),
name: schema.string(),
package: schema.object({
name: schema.string({
validate: (value) => {
if (value === 'synthetics') {
return i18n.translate('xpack.fleet.config.disableSynthetics', {
defaultMessage:
'Synthetics package is not supported via kibana.yml config. Please use Synthetics App to create monitors in private locations. https://www.elastic.co/guide/en/observability/current/synthetics-private-location.html',
});
}
},
schema.oneOf([
schema.object({
id: schema.maybe(schema.oneOf([schema.string(), schema.number()])),
name: schema.string(),
package: schema.object({
name: schema.string({
validate: (value) => {
if (value === 'synthetics') {
return i18n.translate('xpack.fleet.config.disableSynthetics', {
defaultMessage:
'Synthetics package is not supported via kibana.yml config. Please use Synthetics App to create monitors in private locations. https://www.elastic.co/guide/en/observability/current/synthetics-private-location.html',
});
}
},
}),
}),
description: schema.maybe(schema.string()),
namespace: schema.maybe(PackagePolicyNamespaceSchema),
inputs: schema.maybe(
schema.arrayOf(
schema.object({
type: schema.string(),
enabled: schema.maybe(schema.boolean()),
keep_enabled: schema.maybe(schema.boolean()),
vars: varsSchema,
streams: schema.maybe(
schema.arrayOf(
schema.object({
data_stream: schema.object({
type: schema.maybe(schema.string()),
dataset: schema.string(),
}),
enabled: schema.maybe(schema.boolean()),
keep_enabled: schema.maybe(schema.boolean()),
vars: varsSchema,
})
)
),
})
)
),
}),
description: schema.maybe(schema.string()),
namespace: schema.maybe(PackagePolicyNamespaceSchema),
inputs: schema.maybe(
schema.arrayOf(
schema.object({
type: schema.string(),
enabled: schema.maybe(schema.boolean()),
keep_enabled: schema.maybe(schema.boolean()),
vars: varsSchema,
streams: schema.maybe(
schema.arrayOf(
schema.object({
data_stream: schema.object({
type: schema.maybe(schema.string()),
dataset: schema.string(),
}),
enabled: schema.maybe(schema.boolean()),
keep_enabled: schema.maybe(schema.boolean()),
vars: varsSchema,
})
)
),
})
)
),
})
SimplifiedPackagePolicyPreconfiguredSchema,
])
),
}),
{