mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Disallow downgrades and filter out old versions from modal (#133136)
* [Fleet] Disallow downgrades and filter out old versions from modal * Remove a console.log * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Address code review comments * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Remove an import to decrease bundle size * Code review comment * Increase limits size * Update packages/kbn-optimizer/limits.yml Co-authored-by: Jonathan Budzenski <jon@budzenski.me> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jonathan Budzenski <jon@budzenski.me>
This commit is contained in:
parent
bac00e290a
commit
c2b4645d90
12 changed files with 280 additions and 78 deletions
|
@ -27,7 +27,7 @@ pageLoadAssetSize:
|
|||
indexLifecycleManagement: 107090
|
||||
indexManagement: 140608
|
||||
infra: 184320
|
||||
fleet: 95000
|
||||
fleet: 100000
|
||||
ingestPipelines: 58003
|
||||
inputControlVis: 172675
|
||||
inspector: 148711
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getMaxVersion } from './get_max_version';
|
||||
|
||||
describe('Fleet - getMaxVersion', () => {
|
||||
it('returns the maximum version', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.3.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.3.1');
|
||||
});
|
||||
|
||||
it('returns the maximum version when there are duplicates', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.2.0', '7.15.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.3.0');
|
||||
});
|
||||
|
||||
it('returns the maximum version when there is a snapshot version', () => {
|
||||
const versions = ['8.1.0', '8.2.0-SNAPSHOT', '7.16.0', '7.16.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.2.0-SNAPSHOT');
|
||||
});
|
||||
|
||||
it('returns the maximum version and prefers the major version to the snapshot', () => {
|
||||
const versions = ['8.1.0', '8.2.0-SNAPSHOT', '8.2.0', '7.16.0', '7.16.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.2.0');
|
||||
});
|
||||
|
||||
it('when there is only a version returns it', () => {
|
||||
const versions = ['8.1.0'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.1.0');
|
||||
});
|
||||
|
||||
it('returns an empty string when the passed array is empty', () => {
|
||||
const versions: string[] = [];
|
||||
expect(getMaxVersion(versions)).toEqual('');
|
||||
});
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { uniq } from 'lodash';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
|
||||
// Find max version from an array of string versions
|
||||
export function getMaxVersion(versions: string[]) {
|
||||
const uniqVersions: string[] = uniq(versions);
|
||||
|
||||
if (uniqVersions.length === 1) {
|
||||
const semverVersion = semverCoerce(uniqVersions[0])?.version;
|
||||
return semverVersion ? semverVersion : '';
|
||||
} else if (uniqVersions.length > 1) {
|
||||
const sorted = uniqVersions.sort((a, b) => (semverGt(a, b) ? 1 : -1));
|
||||
return sorted[sorted.length - 1];
|
||||
}
|
||||
return '';
|
||||
}
|
112
x-pack/plugins/fleet/common/services/get_min_max_version.test.ts
Normal file
112
x-pack/plugins/fleet/common/services/get_min_max_version.test.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getMaxVersion, getMinVersion, sortVersions } from './get_min_max_version';
|
||||
|
||||
describe('Fleet - sortVersions', () => {
|
||||
it('returns the array ordered in ascending order', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.3.1'];
|
||||
expect(sortVersions(versions)).toEqual([
|
||||
'7.16.0',
|
||||
'7.16.1',
|
||||
'8.1.0',
|
||||
'8.2.0',
|
||||
'8.2.1',
|
||||
'8.3.0',
|
||||
'8.3.1',
|
||||
]);
|
||||
});
|
||||
it('returns the array ordered in ascending order and removes duplicates', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.0', '7.16.0', '8.2.0', '7.16.0', '8.3.1'];
|
||||
expect(sortVersions(versions)).toEqual(['7.16.0', '8.1.0', '8.2.0', '8.3.0', '8.3.1']);
|
||||
});
|
||||
it('returns the array ordered in ascending order when there are snapshot versions', () => {
|
||||
const versions = ['8.1.0', '8.2.0-SNAPSHOT', '8.2.0', '7.16.0', '7.16.1'];
|
||||
expect(sortVersions(versions)).toEqual([
|
||||
'7.16.0',
|
||||
'7.16.1',
|
||||
'8.1.0',
|
||||
'8.2.0-SNAPSHOT',
|
||||
'8.2.0',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fleet - getMaxVersion', () => {
|
||||
it('returns the maximum version', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.3.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.3.1');
|
||||
});
|
||||
|
||||
it('returns the maximum version if the array has a single element', () => {
|
||||
const versions = ['8.1.0'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.1.0');
|
||||
});
|
||||
|
||||
it('returns the maximum version when there are duplicates', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.2.0', '7.15.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.3.0');
|
||||
});
|
||||
|
||||
it('returns the maximum version and prefers the major version to the snapshot', () => {
|
||||
const versions = ['8.1.0', '8.2.0-SNAPSHOT', '8.2.0', '7.16.0', '7.16.1'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.2.0');
|
||||
});
|
||||
|
||||
it('when there is only a version returns it', () => {
|
||||
const versions = ['8.1.0'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.1.0');
|
||||
});
|
||||
|
||||
it('returns an empty string when the passed array is empty', () => {
|
||||
const versions: string[] = [];
|
||||
expect(getMaxVersion(versions)).toEqual('');
|
||||
});
|
||||
|
||||
it('returns empty string if the passed array is empty', () => {
|
||||
expect(getMaxVersion([])).toEqual('');
|
||||
});
|
||||
|
||||
it('returns empty string if the array contains invalid strings', () => {
|
||||
expect(getMaxVersion(['bla', 'not-a-version'])).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fleet - getMinVersion', () => {
|
||||
it('returns the minimum version', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '8.0.0', '8.2.0', '8.2.1'];
|
||||
expect(getMinVersion(versions)).toEqual('8.0.0');
|
||||
});
|
||||
|
||||
it('returns the minimum version if the array has a single element', () => {
|
||||
const versions = ['8.1.0'];
|
||||
expect(getMaxVersion(versions)).toEqual('8.1.0');
|
||||
});
|
||||
|
||||
it('returns the minimum version when there are duplicates', () => {
|
||||
const versions = ['8.1.0', '8.3.0', '8.2.1', '7.16.0', '8.2.0', '7.16.1', '8.2.0', '7.15.1'];
|
||||
expect(getMinVersion(versions)).toEqual('7.15.1');
|
||||
});
|
||||
|
||||
it('when there is only a version returns it', () => {
|
||||
const versions = ['8.1.0'];
|
||||
expect(getMinVersion(versions)).toEqual('8.1.0');
|
||||
});
|
||||
|
||||
it('returns an empty string when the passed array is empty', () => {
|
||||
const versions: string[] = [];
|
||||
expect(getMinVersion(versions)).toEqual('');
|
||||
});
|
||||
|
||||
it('returns empty string if the passed array is empty', () => {
|
||||
expect(getMaxVersion([])).toEqual('');
|
||||
});
|
||||
|
||||
it('returns empty string if the array contains invalid strings', () => {
|
||||
expect(getMaxVersion(['bla', 'not-a-version'])).toEqual('');
|
||||
});
|
||||
});
|
38
x-pack/plugins/fleet/common/services/get_min_max_version.ts
Normal file
38
x-pack/plugins/fleet/common/services/get_min_max_version.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { uniq } from 'lodash';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
|
||||
// Sort array in ascending order
|
||||
export function sortVersions(versions: string[]) {
|
||||
// remove duplicates and filter out invalid versions
|
||||
const uniqVersions = uniq(versions).filter((v) => semverCoerce(v)?.version !== undefined);
|
||||
|
||||
if (uniqVersions.length > 1) {
|
||||
return uniqVersions.sort((a, b) => (semverGt(a, b) ? 1 : -1));
|
||||
}
|
||||
return uniqVersions;
|
||||
}
|
||||
|
||||
// Find max version from an array of string versions
|
||||
export function getMaxVersion(versions: string[]) {
|
||||
const sorted = sortVersions(versions);
|
||||
if (sorted.length >= 1) {
|
||||
return sorted[sorted.length - 1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Find min version from an array of string versions
|
||||
export function getMinVersion(versions: string[]) {
|
||||
const sorted = sortVersions(versions);
|
||||
if (sorted.length >= 1) {
|
||||
return sorted[0];
|
||||
}
|
||||
return '';
|
||||
}
|
|
@ -35,3 +35,4 @@ export {
|
|||
export { normalizeHostsForAgents } from './hosts_utils';
|
||||
export { splitPkgKey } from './split_pkg_key';
|
||||
export { getMaxPackageName } from './max_package_name';
|
||||
export { getMinVersion, getMaxVersion } from './get_min_max_version';
|
||||
|
|
|
@ -168,4 +168,22 @@ describe('Fleet - isAgentUpgradeable', () => {
|
|||
isAgentUpgradeable(getAgent({ version: '7.9.0', upgradeable: true }), '8.0.0-SNAPSHOT')
|
||||
).toBe(true);
|
||||
});
|
||||
it('returns false if agent reports upgradeable, with target version < current agent version ', () => {
|
||||
expect(
|
||||
isAgentUpgradeable(
|
||||
getAgent({ version: '7.9.0', upgradeable: true }),
|
||||
'8.0.0-SNAPSHOT',
|
||||
'7.8.0'
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
it('returns false if agent reports upgradeable, with target version == current agent version ', () => {
|
||||
expect(
|
||||
isAgentUpgradeable(
|
||||
getAgent({ version: '7.9.0', upgradeable: true }),
|
||||
'8.0.0-SNAPSHOT',
|
||||
'7.9.0'
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
|
||||
import type { Agent } from '../types';
|
||||
|
||||
export function isAgentUpgradeable(agent: Agent, kibanaVersion: string) {
|
||||
export function isAgentUpgradeable(agent: Agent, kibanaVersion: string, versionToUpgrade?: string) {
|
||||
let agentVersion: string;
|
||||
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
|
||||
agentVersion = agent.local_metadata.elastic.agent.version;
|
||||
|
@ -23,7 +24,12 @@ export function isAgentUpgradeable(agent: Agent, kibanaVersion: string) {
|
|||
if (!agent.local_metadata.elastic.agent.upgradeable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (versionToUpgrade !== undefined) {
|
||||
return (
|
||||
isNotDowngrade(agentVersion, versionToUpgrade) &&
|
||||
isAgentVersionLessThanKibana(agentVersion, kibanaVersion)
|
||||
);
|
||||
}
|
||||
return isAgentVersionLessThanKibana(agentVersion, kibanaVersion);
|
||||
}
|
||||
|
||||
|
@ -36,3 +42,12 @@ export const isAgentVersionLessThanKibana = (agentVersion: string, kibanaVersion
|
|||
|
||||
return semverLt(agentVersionNumber, kibanaVersionNumber);
|
||||
};
|
||||
|
||||
export const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {
|
||||
const agentVersionNumber = semverCoerce(agentVersion);
|
||||
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
||||
const versionToUpgradeNumber = semverCoerce(versionToUpgrade);
|
||||
if (!versionToUpgradeNumber) throw new Error('target version is not valid');
|
||||
|
||||
return semverGt(versionToUpgradeNumber, agentVersionNumber);
|
||||
};
|
||||
|
|
|
@ -25,8 +25,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
|
||||
import { getMinVersion } from '../../../../../../../common/services/get_min_max_version';
|
||||
import type { Agent } from '../../../../types';
|
||||
import {
|
||||
sendPostAgentUpgrade,
|
||||
|
@ -62,12 +63,24 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
|
|||
const isAllAgents = agents === '';
|
||||
|
||||
const fallbackVersions = [kibanaVersion].concat(FALLBACK_VERSIONS);
|
||||
const fallbackOptions: Array<EuiComboBoxOptionOption<string>> = fallbackVersions.map(
|
||||
(option) => ({
|
||||
|
||||
const minVersion = useMemo(() => {
|
||||
const versions = (agents as Agent[]).map(
|
||||
(agent) => agent.local_metadata?.elastic?.agent?.version
|
||||
);
|
||||
return getMinVersion(versions);
|
||||
}, [agents]);
|
||||
|
||||
const versionOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(() => {
|
||||
const displayVersions = minVersion
|
||||
? fallbackVersions.filter((v) => semverGt(v, minVersion))
|
||||
: fallbackVersions;
|
||||
return displayVersions.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
})
|
||||
);
|
||||
}));
|
||||
}, [fallbackVersions, minVersion]);
|
||||
|
||||
const maintainanceWindows =
|
||||
isSmallBatch && !isScheduled ? [0].concat(MAINTAINANCE_VALUES) : MAINTAINANCE_VALUES;
|
||||
const maintainanceOptions: Array<EuiComboBoxOptionOption<number>> = maintainanceWindows.map(
|
||||
|
@ -84,7 +97,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
|
|||
value: option === 0 ? 0 : option * 3600,
|
||||
})
|
||||
);
|
||||
const [selectedVersion, setSelectedVersion] = useState([fallbackOptions[0]]);
|
||||
const [selectedVersion, setSelectedVersion] = useState([versionOptions[0]]);
|
||||
const [selectedMantainanceWindow, setSelectedMantainanceWindow] = useState([
|
||||
maintainanceOptions[0],
|
||||
]);
|
||||
|
@ -183,7 +196,12 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
|
|||
|
||||
const onCreateOption = (searchValue: string) => {
|
||||
const agentVersionNumber = semverCoerce(searchValue);
|
||||
if (agentVersionNumber?.version && semverLt(agentVersionNumber?.version, kibanaVersion)) {
|
||||
if (
|
||||
agentVersionNumber?.version &&
|
||||
semverGt(kibanaVersion, agentVersionNumber?.version) &&
|
||||
minVersion &&
|
||||
semverGt(agentVersionNumber?.version, minVersion)
|
||||
) {
|
||||
const newOption = {
|
||||
label: searchValue,
|
||||
value: searchValue,
|
||||
|
@ -274,7 +292,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<Props> = ({
|
|||
data-test-subj="agentUpgradeModal.VersionCombobox"
|
||||
fullWidth
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={fallbackOptions}
|
||||
options={versionOptions}
|
||||
selectedOptions={selectedVersion}
|
||||
onChange={(selected: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
setSelectedVersion(selected);
|
||||
|
|
|
@ -21,7 +21,7 @@ import * as AgentService from '../../services/agents';
|
|||
import { appContextService } from '../../services';
|
||||
import { defaultIngestErrorHandler } from '../../errors';
|
||||
import { isAgentUpgradeable } from '../../../common/services';
|
||||
import { getMaxVersion } from '../../../common/services/get_max_version';
|
||||
import { getMaxVersion } from '../../../common/services/get_min_max_version';
|
||||
import { getAgentById } from '../../services/agents';
|
||||
import type { Agent } from '../../types';
|
||||
|
||||
|
@ -57,7 +57,7 @@ export const postAgentUpgradeHandler: RequestHandler<
|
|||
},
|
||||
});
|
||||
}
|
||||
if (!force && !isAgentUpgradeable(agent, kibanaVersion)) {
|
||||
if (!force && !isAgentUpgradeable(agent, kibanaVersion, version)) {
|
||||
return response.customError({
|
||||
statusCode: 400,
|
||||
body: {
|
||||
|
@ -181,6 +181,10 @@ const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgen
|
|||
|
||||
const maxFleetServerVersion = getMaxVersion(fleetServerVersions);
|
||||
|
||||
if (!maxFleetServerVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (semverGt(versionToUpgradeNumber, maxFleetServerVersion)) {
|
||||
throw new Error(
|
||||
`cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the latest fleet server version ${maxFleetServerVersion}`
|
||||
|
|
|
@ -133,8 +133,9 @@ export async function sendUpgradeAgentsActions(
|
|||
const upgradeableResults = await Promise.allSettled(
|
||||
agentsToCheckUpgradeable.map(async (agent) => {
|
||||
// Filter out agents currently unenrolling, unenrolled, or not upgradeable b/c of version check
|
||||
const isAllowed = options.force || isAgentUpgradeable(agent, kibanaVersion);
|
||||
if (!isAllowed) {
|
||||
const isNotAllowed =
|
||||
!options.force && !isAgentUpgradeable(agent, kibanaVersion, options.version);
|
||||
if (isNotAllowed) {
|
||||
throw new IngestManagerError(`${agent.id} is not upgradeable`);
|
||||
}
|
||||
|
||||
|
|
|
@ -160,6 +160,26 @@ export default function (providerContext: FtrProviderContext) {
|
|||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should respond 400 if trying to downgrade version', async () => {
|
||||
await es.update({
|
||||
id: 'agent1',
|
||||
refresh: 'wait_for',
|
||||
index: AGENTS_INDEX,
|
||||
body: {
|
||||
doc: {
|
||||
local_metadata: { elastic: { agent: { upgradeable: true, version: '7.0.0' } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
await supertest
|
||||
.post(`/api/fleet/agents/agent1/upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
version: '6.0.0',
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should respond 400 if trying to upgrade with source_uri set', async () => {
|
||||
const kibanaVersion = await kibanaServer.version.get();
|
||||
const res = await supertest
|
||||
|
@ -710,6 +730,44 @@ export default function (providerContext: FtrProviderContext) {
|
|||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should prevent any agent to downgrade', async () => {
|
||||
await es.update({
|
||||
id: 'agent1',
|
||||
refresh: 'wait_for',
|
||||
index: AGENTS_INDEX,
|
||||
body: {
|
||||
doc: {
|
||||
policy_id: `agent-policy-1`,
|
||||
local_metadata: { elastic: { agent: { upgradeable: true, version: '6.0.0' } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
await es.update({
|
||||
id: 'agent2',
|
||||
refresh: 'wait_for',
|
||||
index: AGENTS_INDEX,
|
||||
body: {
|
||||
doc: {
|
||||
policy_id: `agent-policy-2`,
|
||||
local_metadata: { elastic: { agent: { upgradeable: true, version: '6.0.0' } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
await supertest
|
||||
.post(`/api/fleet/agents/bulk_upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
agents: ['agent1', 'agent2'],
|
||||
version: '5.0.0',
|
||||
})
|
||||
.expect(200);
|
||||
const [agent1data, agent2data] = await Promise.all([
|
||||
supertest.get(`/api/fleet/agents/agent1`).set('kbn-xsrf', 'xxx'),
|
||||
supertest.get(`/api/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'),
|
||||
]);
|
||||
expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined');
|
||||
expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined');
|
||||
});
|
||||
|
||||
it('should throw an error if source_uri parameter is passed', async () => {
|
||||
const kibanaVersion = await kibanaServer.version.get();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue