From ae4ceed55d5cae738ba447a674883e839b7f79d1 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:07:09 +0200 Subject: [PATCH] Allow agent force upgrading to a newer patch release --- .../routes/agent/upgrade_handler.test.ts | 14 ++++++ .../server/routes/agent/upgrade_handler.ts | 44 ++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) 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..7241f84068b76 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,32 @@ 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}` ); + } + + if ( + force && + !( + semverMajor(kibanaVersionNumber) > semverMajor(versionToUpgradeNumber) || + (semverMajor(kibanaVersionNumber) === semverMajor(versionToUpgradeNumber) && + semverMinor(kibanaVersionNumber) >= semverMinor(versionToUpgradeNumber)) + ) + ) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisify 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 +204,19 @@ 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}` ); } + + if ( + force && + semverMajor(maxFleetServerVersion) !== semverMajor(versionToUpgradeNumber) && + semverMinor(maxFleetServerVersion) !== semverMinor(versionToUpgradeNumber) + ) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it is not the same major and minor version as the latest fleet server version ${maxFleetServerVersion}` + ); + } };