diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts index 5eafcf1a94104..aefcbfc5cd87f 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts @@ -22,5 +22,19 @@ describe('upgrade handler', () => { it('should not throw if upgrade version is equal to kibana version with snapshot', () => { expect(() => checkKibanaVersion('8.4.0', '8.4.0-SNAPSHOT')).not.toThrowError(); }); + + it('should not throw if force is specified and patch is newer', () => { + expect(() => checkKibanaVersion('8.4.1', '8.4.0', true)).not.toThrowError(); + expect(() => checkKibanaVersion('8.4.1-SNAPSHOT', '8.4.0', true)).not.toThrowError(); + }); + + it('should throw if force is specified and minor is newer', () => { + expect(() => checkKibanaVersion('8.5.0', '8.4.0', true)).toThrowError(); + }); + + it('should not throw if force is specified and major and minor is newer', () => { + expect(() => checkKibanaVersion('7.5.0', '8.4.0', true)).not.toThrowError(); + expect(() => checkKibanaVersion('8.4.0', '8.4.0', true)).not.toThrowError(); + }); }); }); diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index a79edbaa36856..d3fffac7d9050 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -10,6 +10,8 @@ import type { TypeOf } from '@kbn/config-schema'; import semverCoerce from 'semver/functions/coerce'; import semverGt from 'semver/functions/gt'; +import semverMajor from 'semver/functions/major'; +import semverMinor from 'semver/functions/minor'; import type { PostAgentUpgradeResponse, GetCurrentUpgradesResponse } from '../../../common/types'; import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; @@ -34,7 +36,7 @@ export const postAgentUpgradeHandler: RequestHandler< const { version, source_uri: sourceUri, force } = request.body; const kibanaVersion = appContextService.getKibanaVersion(); try { - checkKibanaVersion(version, kibanaVersion); + checkKibanaVersion(version, kibanaVersion, force); } catch (err) { return response.customError({ statusCode: 400, @@ -114,9 +116,9 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< } = request.body; const kibanaVersion = appContextService.getKibanaVersion(); try { - checkKibanaVersion(version, kibanaVersion); + checkKibanaVersion(version, kibanaVersion, force); const fleetServerAgents = await getAllFleetServerAgents(soClient, esClient); - checkFleetServerVersion(version, fleetServerAgents); + checkFleetServerVersion(version, fleetServerAgents, force); } catch (err) { return response.customError({ statusCode: 400, @@ -158,7 +160,7 @@ export const getCurrentUpgradesHandler: RequestHandler = async (context, request } }; -export const checkKibanaVersion = (version: string, kibanaVersion: string) => { +export const checkKibanaVersion = (version: string, kibanaVersion: string, force = false) => { // get version number only in case "-SNAPSHOT" is in it const kibanaVersionNumber = semverCoerce(kibanaVersion)?.version; if (!kibanaVersionNumber) throw new Error(`kibanaVersion ${kibanaVersionNumber} is not valid`); @@ -166,14 +168,31 @@ export const checkKibanaVersion = (version: string, kibanaVersion: string) => { if (!versionToUpgradeNumber) throw new Error(`version to upgrade ${versionToUpgradeNumber} is not valid`); - if (semverGt(versionToUpgradeNumber, kibanaVersionNumber)) + if (!force && semverGt(versionToUpgradeNumber, kibanaVersionNumber)) { throw new Error( `cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the installed kibana version ${kibanaVersionNumber}` ); + } + + const kibanaMajorGt = semverMajor(kibanaVersionNumber) > semverMajor(versionToUpgradeNumber); + const kibanaMajorEqMinorGte = + semverMajor(kibanaVersionNumber) === semverMajor(versionToUpgradeNumber) && + semverMinor(kibanaVersionNumber) >= semverMinor(versionToUpgradeNumber); + + // When force is enabled, only the major and minor versions are checked + if (force && !(kibanaMajorGt || kibanaMajorEqMinorGte)) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the installed kibana version ${kibanaVersionNumber}` + ); + } }; // Check the installed fleet server version -const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgents: Agent[]) => { +const checkFleetServerVersion = ( + versionToUpgradeNumber: string, + fleetServerAgents: Agent[], + force = false +) => { const fleetServerVersions = fleetServerAgents.map( (agent) => agent.local_metadata.elastic.agent.version ) as string[]; @@ -184,9 +203,22 @@ const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgen return; } - if (semverGt(versionToUpgradeNumber, maxFleetServerVersion)) { + if (!force && semverGt(versionToUpgradeNumber, maxFleetServerVersion)) { throw new Error( `cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the latest fleet server version ${maxFleetServerVersion}` ); } + + const fleetServerMajorGt = + semverMajor(maxFleetServerVersion) > semverMajor(versionToUpgradeNumber); + const fleetServerMajorEqMinorGte = + semverMajor(maxFleetServerVersion) === semverMajor(versionToUpgradeNumber) && + semverMinor(maxFleetServerVersion) >= semverMinor(versionToUpgradeNumber); + + // When force is enabled, only the major and minor versions are checked + if (force && !(fleetServerMajorGt || fleetServerMajorEqMinorGte)) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the latest fleet server version ${maxFleetServerVersion}` + ); + } };