Skip to content

Commit d126c89

Browse files
[Fleet] Added required_versions to agent policy and API with validation (elastic#206600)
Part of elastic/ingest-dev#4721 Added `required_versions` to agent policy and API with validation, added unit tests for the validation. UI change will come in another pr To test: - enable FF in `kibana.dev.yml` - `xpack.fleet.enableExperimental: ['enableAutomaticAgentUpgrades']` - create/update an agent policy with `required_versions` - add to preconfiguration - `required_versions` is not added to the full agent policy in `.fleet-policies` ``` POST kbn:/api/fleet/agent_policies { "name": "Test versions", "namespace": "default", "required_versions": [ { "version": "9.0.0", "percentage": 5 } ] } POST kbn:/api/fleet/agent_policies { "name": "Test versions 2", "namespace": "default", "required_versions": [ { "version": "9.0.0", "percentage": 5 }, { "version": "9.0.0", "percentage": 5 } ] } { "statusCode": 400, "error": "Bad Request", "message": """Policy "Test versions 2" failed validation: duplicate versions not allowed in required_versions""" } PUT kbn:/api/fleet/agent_policies/fleet-first-agent-policy { "name": "My first agent policy", "namespace": "default", "required_versions": [ { "version": "8.18.0", "percentage": 10 }, { "version": "8.19.0", "percentage": 5 } ] } GET kbn:/api/fleet/agent_policies/test-preconfigured GET .fleet-policies/_search?q=policy_id:fleet-first-agent-policy { "size": 1, "sort": [ { "revision_idx": { "order": "desc" } } ] } xpack.fleet.agentPolicies: - name: Test preconfigured id: test-preconfigured is_managed: true namespace: default monitoring_enabled: [] package_policies: [] required_versions: - version: "9.0.0" percentage: 10 - version: "9.1.0" percentage: 5 ``` - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <[email protected]>
1 parent 26a3136 commit d126c89

File tree

10 files changed

+387
-32
lines changed

10 files changed

+387
-32
lines changed

oas_docs/output/kibana.serverless.yaml

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14249,6 +14249,24 @@ paths:
1424914249
- created_at
1425014250
- created_by
1425114251
type: array
14252+
required_versions:
14253+
items:
14254+
additionalProperties: false
14255+
type: object
14256+
properties:
14257+
percentage:
14258+
description: Target percentage of agents to auto upgrade
14259+
maximum: 100
14260+
minimum: 0
14261+
type: number
14262+
version:
14263+
description: Target version for automatic agent upgrade
14264+
type: string
14265+
required:
14266+
- version
14267+
- percentage
14268+
nullable: true
14269+
type: array
1425214270
revision:
1425314271
type: number
1425414272
schema_version:
@@ -14517,6 +14535,24 @@ paths:
1451714535
description: Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure.
1451814536
nullable: true
1451914537
type: object
14538+
required_versions:
14539+
items:
14540+
additionalProperties: false
14541+
type: object
14542+
properties:
14543+
percentage:
14544+
description: Target percentage of agents to auto upgrade
14545+
maximum: 100
14546+
minimum: 0
14547+
type: number
14548+
version:
14549+
description: Target version for automatic agent upgrade
14550+
type: string
14551+
required:
14552+
- version
14553+
- percentage
14554+
nullable: true
14555+
type: array
1452014556
space_ids:
1452114557
items:
1452214558
type: string
@@ -15097,6 +15133,24 @@ paths:
1509715133
- created_at
1509815134
- created_by
1509915135
type: array
15136+
required_versions:
15137+
items:
15138+
additionalProperties: false
15139+
type: object
15140+
properties:
15141+
percentage:
15142+
description: Target percentage of agents to auto upgrade
15143+
maximum: 100
15144+
minimum: 0
15145+
type: number
15146+
version:
15147+
description: Target version for automatic agent upgrade
15148+
type: string
15149+
required:
15150+
- version
15151+
- percentage
15152+
nullable: true
15153+
type: array
1510015154
revision:
1510115155
type: number
1510215156
schema_version:
@@ -15769,6 +15823,24 @@ paths:
1576915823
- created_at
1577015824
- created_by
1577115825
type: array
15826+
required_versions:
15827+
items:
15828+
additionalProperties: false
15829+
type: object
15830+
properties:
15831+
percentage:
15832+
description: Target percentage of agents to auto upgrade
15833+
maximum: 100
15834+
minimum: 0
15835+
type: number
15836+
version:
15837+
description: Target version for automatic agent upgrade
15838+
type: string
15839+
required:
15840+
- version
15841+
- percentage
15842+
nullable: true
15843+
type: array
1577215844
revision:
1577315845
type: number
1577415846
schema_version:
@@ -16421,6 +16493,24 @@ paths:
1642116493
- created_at
1642216494
- created_by
1642316495
type: array
16496+
required_versions:
16497+
items:
16498+
additionalProperties: false
16499+
type: object
16500+
properties:
16501+
percentage:
16502+
description: Target percentage of agents to auto upgrade
16503+
maximum: 100
16504+
minimum: 0
16505+
type: number
16506+
version:
16507+
description: Target version for automatic agent upgrade
16508+
type: string
16509+
required:
16510+
- version
16511+
- percentage
16512+
nullable: true
16513+
type: array
1642416514
revision:
1642516515
type: number
1642616516
schema_version:
@@ -16688,6 +16778,24 @@ paths:
1668816778
description: Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure.
1668916779
nullable: true
1669016780
type: object
16781+
required_versions:
16782+
items:
16783+
additionalProperties: false
16784+
type: object
16785+
properties:
16786+
percentage:
16787+
description: Target percentage of agents to auto upgrade
16788+
maximum: 100
16789+
minimum: 0
16790+
type: number
16791+
version:
16792+
description: Target version for automatic agent upgrade
16793+
type: string
16794+
required:
16795+
- version
16796+
- percentage
16797+
nullable: true
16798+
type: array
1669116799
space_ids:
1669216800
items:
1669316801
type: string
@@ -17268,6 +17376,24 @@ paths:
1726817376
- created_at
1726917377
- created_by
1727017378
type: array
17379+
required_versions:
17380+
items:
17381+
additionalProperties: false
17382+
type: object
17383+
properties:
17384+
percentage:
17385+
description: Target percentage of agents to auto upgrade
17386+
maximum: 100
17387+
minimum: 0
17388+
type: number
17389+
version:
17390+
description: Target version for automatic agent upgrade
17391+
type: string
17392+
required:
17393+
- version
17394+
- percentage
17395+
nullable: true
17396+
type: array
1727117397
revision:
1727217398
type: number
1727317399
schema_version:
@@ -17940,6 +18066,24 @@ paths:
1794018066
- created_at
1794118067
- created_by
1794218068
type: array
18069+
required_versions:
18070+
items:
18071+
additionalProperties: false
18072+
type: object
18073+
properties:
18074+
percentage:
18075+
description: Target percentage of agents to auto upgrade
18076+
maximum: 100
18077+
minimum: 0
18078+
type: number
18079+
version:
18080+
description: Target version for automatic agent upgrade
18081+
type: string
18082+
required:
18083+
- version
18084+
- percentage
18085+
nullable: true
18086+
type: array
1794318087
revision:
1794418088
type: number
1794518089
schema_version:

x-pack/platform/plugins/shared/fleet/common/experimental_features.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const _allowedExperimentalValues = {
2828
asyncDeployPolicies: true,
2929
enableExportCSV: true,
3030
enabledUpgradeAgentlessDeploymentsTask: false,
31+
enableAutomaticAgentUpgrades: false,
3132
};
3233

3334
/**

x-pack/platform/plugins/shared/fleet/common/types/models/agent_policy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export interface NewAgentPolicy {
6262
max_dur?: string;
6363
};
6464
};
65+
required_versions?: AgentTargetVersion[] | null;
66+
}
67+
68+
export interface AgentTargetVersion {
69+
version: string;
70+
percentage: number;
6571
}
6672

6773
export interface AgentlessPolicy {

x-pack/platform/plugins/shared/fleet/server/services/agent_policies/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export {
1111
storedPackagePoliciesToAgentInputs,
1212
} from './package_policies_to_agent_inputs';
1313
export { getDataOutputForAgentPolicy, validateOutputForPolicy } from './outputs_helpers';
14+
export { validateRequiredVersions } from './required_versions';
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { appContextService } from '..';
9+
import { AgentPolicyInvalidError } from '../../errors';
10+
11+
import { validateRequiredVersions } from './required_versions';
12+
13+
describe('validateRequiredVersions', () => {
14+
it('should throw error if feature flag is disabled', () => {
15+
jest
16+
.spyOn(appContextService, 'getExperimentalFeatures')
17+
.mockReturnValue({ enableAutomaticAgentUpgrades: false } as any);
18+
19+
expect(() => {
20+
validateRequiredVersions('test policy', [{ version: '9.0.0', percentage: 100 }]);
21+
}).toThrow(
22+
new AgentPolicyInvalidError(
23+
`Policy "test policy" failed validation: required_versions are not allowed when automatic upgrades feature is disabled`
24+
)
25+
);
26+
});
27+
28+
describe('feature flag enabled', () => {
29+
beforeEach(() => {
30+
jest
31+
.spyOn(appContextService, 'getExperimentalFeatures')
32+
.mockReturnValue({ enableAutomaticAgentUpgrades: true } as any);
33+
});
34+
35+
it('should throw error if duplicate versions', () => {
36+
expect(() => {
37+
validateRequiredVersions('test policy', [
38+
{ version: '9.0.0', percentage: 10 },
39+
{ version: '9.0.0', percentage: 10 },
40+
]);
41+
}).toThrow(
42+
new AgentPolicyInvalidError(
43+
`Policy "test policy" failed validation: duplicate versions not allowed in required_versions`
44+
)
45+
);
46+
});
47+
48+
it('should throw error if has invalid semver version', () => {
49+
expect(() => {
50+
validateRequiredVersions('test policy', [
51+
{ version: '9.0.0', percentage: 10 },
52+
{ version: '9.0.0invalid', percentage: 10 },
53+
]);
54+
}).toThrow(
55+
new AgentPolicyInvalidError(
56+
`Policy "test policy" failed validation: invalid semver version 9.0.0invalid in required_versions`
57+
)
58+
);
59+
});
60+
61+
it('should throw error if sum of percentages exceeds 100', () => {
62+
expect(() => {
63+
validateRequiredVersions('test policy', [
64+
{ version: '9.0.0', percentage: 100 },
65+
{ version: '9.1.0', percentage: 10 },
66+
]);
67+
}).toThrow(
68+
new AgentPolicyInvalidError(
69+
`Policy "test policy" failed validation: sum of required_versions percentages cannot exceed 100`
70+
)
71+
);
72+
});
73+
74+
it('should not throw error if valid required_versions', () => {
75+
validateRequiredVersions('test policy', [
76+
{ version: '9.0.0', percentage: 90 },
77+
{ version: '9.1.0', percentage: 10 },
78+
]);
79+
});
80+
81+
it('should not throw error if required_versions undefined', () => {
82+
validateRequiredVersions('test policy');
83+
});
84+
});
85+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import semverValid from 'semver/functions/valid';
9+
10+
import type { AgentTargetVersion } from '../../../common/types';
11+
12+
import { AgentPolicyInvalidError } from '../../errors';
13+
import { appContextService } from '..';
14+
15+
export function validateRequiredVersions(
16+
name: string,
17+
requiredVersions?: AgentTargetVersion[] | null
18+
): void {
19+
if (!requiredVersions) {
20+
return;
21+
}
22+
if (!appContextService.getExperimentalFeatures().enableAutomaticAgentUpgrades) {
23+
throw new AgentPolicyInvalidError(
24+
`Policy "${name}" failed validation: required_versions are not allowed when automatic upgrades feature is disabled`
25+
);
26+
}
27+
const versions = requiredVersions.map((v) => v.version);
28+
const uniqueVersions = new Set(versions);
29+
if (versions.length !== uniqueVersions.size) {
30+
throw new AgentPolicyInvalidError(
31+
`Policy "${name}" failed validation: duplicate versions not allowed in required_versions`
32+
);
33+
}
34+
versions.forEach((version) => {
35+
if (!semverValid(version)) {
36+
throw new AgentPolicyInvalidError(
37+
`Policy "${name}" failed validation: invalid semver version ${version} in required_versions`
38+
);
39+
}
40+
});
41+
const sumOfPercentages = requiredVersions.reduce((acc, v) => acc + v.percentage, 0);
42+
if (sumOfPercentages > 100) {
43+
throw new AgentPolicyInvalidError(
44+
`Policy "${name}" failed validation: sum of required_versions percentages cannot exceed 100`
45+
);
46+
}
47+
}

x-pack/platform/plugins/shared/fleet/server/services/agent_policy.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ import { incrementPackagePolicyCopyName } from './package_policies';
110110
import { outputService } from './output';
111111
import { agentPolicyUpdateEventHandler } from './agent_policy_update';
112112
import { escapeSearchQueryPhrase, normalizeKuery as _normalizeKuery } from './saved_object';
113-
import { getFullAgentPolicy, validateOutputForPolicy } from './agent_policies';
113+
import {
114+
getFullAgentPolicy,
115+
validateOutputForPolicy,
116+
validateRequiredVersions,
117+
} from './agent_policies';
114118
import { auditLoggingService } from './audit_logging';
115119
import { licenseService } from './license';
116120
import { createSoFindIterable } from './utils/create_so_find_iterable';
@@ -408,6 +412,7 @@ class AgentPolicyService {
408412
{},
409413
getAllowedOutputTypesForAgentPolicy(agentPolicy)
410414
);
415+
validateRequiredVersions(agentPolicy.name, agentPolicy.required_versions);
411416

412417
const newSo = await soClient.create<AgentPolicySOAttributes>(
413418
savedObjectType,
@@ -708,6 +713,7 @@ class AgentPolicyService {
708713
namespace: agentPolicy.namespace,
709714
});
710715
}
716+
validateRequiredVersions(agentPolicy.name ?? id, agentPolicy.required_versions);
711717

712718
const existingAgentPolicy = await this.get(soClient, id, true);
713719

0 commit comments

Comments
 (0)