diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts b/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts index ca0fcd3c52c9a..dc61f4898478d 100644 --- a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts @@ -135,4 +135,35 @@ describe('Ingest Manager - isAgentUpgradeable', () => { true ); }); + it('returns false if agent reports upgradeable, with agent snapshot version === kibana version', () => { + expect( + isAgentUpgradeable(getAgent({ version: '7.9.0-SNAPSHOT', upgradeable: true }), '7.9.0') + ).toBe(false); + }); + it('returns false if agent reports upgradeable, with agent version === kibana snapshot version', () => { + expect( + isAgentUpgradeable(getAgent({ version: '7.9.0', upgradeable: true }), '7.9.0-SNAPSHOT') + ).toBe(false); + }); + it('returns true if agent reports upgradeable, with agent snapshot version < kibana snapshot version', () => { + expect( + isAgentUpgradeable( + getAgent({ version: '7.9.0-SNAPSHOT', upgradeable: true }), + '8.0.0-SNAPSHOT' + ) + ).toBe(true); + }); + it('returns false if agent reports upgradeable, with agent snapshot version === kibana snapshot version', () => { + expect( + isAgentUpgradeable( + getAgent({ version: '8.0.0-SNAPSHOT', upgradeable: true }), + '8.0.0-SNAPSHOT' + ) + ).toBe(false); + }); + it('returns true if agent reports upgradeable, with agent version < kibana snapshot version', () => { + expect( + isAgentUpgradeable(getAgent({ version: '7.9.0', upgradeable: true }), '8.0.0-SNAPSHOT') + ).toBe(true); + }); }); diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts b/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts index 7b59fb7b22825..b93e5d99543f6 100644 --- a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts +++ b/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts @@ -14,9 +14,12 @@ export function isAgentUpgradeable(agent: Agent, kibanaVersion: string) { return false; } if (agent.unenrollment_started_at || agent.unenrolled_at) return false; - const kibanaVersionParsed = semver.parse(kibanaVersion); - const agentVersionParsed = semver.parse(agentVersion); - if (!agentVersionParsed || !kibanaVersionParsed) return false; if (!agent.local_metadata.elastic.agent.upgradeable) return false; - return semver.lt(agentVersionParsed, kibanaVersionParsed); + + // make sure versions are only the number before comparison + const agentVersionNumber = semver.coerce(agentVersion); + if (!agentVersionNumber) throw new Error('agent version is invalid'); + const kibanaVersionNumber = semver.coerce(kibanaVersion); + if (!kibanaVersionNumber) throw new Error('kibana version is invalid'); + return semver.lt(agentVersionNumber, kibanaVersionNumber); } diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts index 9c6b50b6d8f09..60dc7c6ee5f2b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts @@ -6,6 +6,7 @@ import { RequestHandler } from 'src/core/server'; import { TypeOf } from '@kbn/config-schema'; +import semver from 'semver'; import { AgentSOAttributes, PostAgentUpgradeResponse, @@ -26,17 +27,18 @@ export const postAgentUpgradeHandler: RequestHandler< > = async (context, request, response) => { const soClient = context.core.savedObjects.client; const { version, source_uri: sourceUri } = request.body; - - // temporarily only allow upgrading to the same version as the installed kibana version const kibanaVersion = appContextService.getKibanaVersion(); - if (kibanaVersion !== version) { + try { + checkVersionIsSame(version, kibanaVersion); + } catch (err) { return response.customError({ statusCode: 400, body: { - message: `cannot upgrade agent to ${version} because it is different than the installed kibana version ${kibanaVersion}`, + message: err.message, }, }); } + const agentSO = await soClient.get( AGENT_SAVED_OBJECT_TYPE, request.params.agentId @@ -82,14 +84,14 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< > = async (context, request, response) => { const soClient = context.core.savedObjects.client; const { version, source_uri: sourceUri, agents } = request.body; - - // temporarily only allow upgrading to the same version as the installed kibana version const kibanaVersion = appContextService.getKibanaVersion(); - if (kibanaVersion !== version) { + try { + checkVersionIsSame(version, kibanaVersion); + } catch (err) { return response.customError({ statusCode: 400, body: { - message: `cannot upgrade agent to ${version} because it is different than the installed kibana version ${kibanaVersion}`, + message: err.message, }, }); } @@ -115,3 +117,17 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< return defaultIngestErrorHandler({ error, response }); } }; + +export const checkVersionIsSame = (version: string, kibanaVersion: string) => { + // get version number only in case "-SNAPSHOT" is in it + const kibanaVersionNumber = semver.coerce(kibanaVersion)?.version; + if (!kibanaVersionNumber) throw new Error(`kibanaVersion ${kibanaVersionNumber} is not valid`); + const versionToUpgradeNumber = semver.coerce(version)?.version; + if (!versionToUpgradeNumber) + throw new Error(`version to upgrade ${versionToUpgradeNumber} is not valid`); + // temporarily only allow upgrading to the same version as the installed kibana version + if (kibanaVersionNumber !== versionToUpgradeNumber) + throw new Error( + `cannot upgrade agent to ${versionToUpgradeNumber} because it is different than the installed kibana version ${kibanaVersionNumber}` + ); +}; diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts index 055877c19c82f..c5426168eb78f 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts @@ -11,6 +11,10 @@ import { setupIngest } from './services'; import { skipIfNoDockerRegistry } from '../../../helpers'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../plugins/ingest_manager/common'; +const makeSnapshotVersion = (version: string) => { + return version.endsWith('-SNAPSHOT') ? version : `${version}-SNAPSHOT`; +}; + export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); @@ -48,6 +52,43 @@ export default function (providerContext: FtrProviderContext) { const res = await supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'); expect(typeof res.body.item.upgrade_started_at).to.be('string'); }); + it('should respond 400 if upgrading agent with version the same as snapshot version', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + await kibanaServer.savedObjects.update({ + id: 'agent1', + type: AGENT_SAVED_OBJECT_TYPE, + attributes: { + local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } }, + }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersionSnapshot, + }) + .expect(400); + }); + it('should respond 200 if upgrading agent with version less than kibana snapshot version', async () => { + const kibanaVersion = await kibanaServer.version.get(); + const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion); + + await kibanaServer.savedObjects.update({ + id: 'agent1', + type: AGENT_SAVED_OBJECT_TYPE, + attributes: { + local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } }, + }, + }); + await supertest + .post(`/api/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersionSnapshot, + }) + .expect(200); + }); it('should respond 200 to upgrade agent and update the agent SO without source_uri', async () => { const kibanaVersion = await kibanaServer.version.get(); await kibanaServer.savedObjects.update({