mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Display message explaining why agent is not upgradeable (#173253)
Closes https://github.com/elastic/kibana/issues/171840 Also implements the UI part of https://github.com/elastic/kibana/issues/173281 ## Summary When trying to upgrade a single agent that is not upgradeable, the error message doesn't specify _why_ the agent cannot be upgraded. This PR is introducing: - More granular error messages in the endpoint `POST agent/{agent_id}/upgrade`. The messages are now different depending on which conditions are met. For the case when the agent is reported not upgradeable from elastic agent, there is now an error message the states it explicitly. - When clicking on the `upgrade 1 agent` from the bulk actions, if the agent is not upgradeable the submit button is now greyed out and a message explaining the reason is shown: -  The same warning appears when clicking on other upgrade actions in the bulk action menu, but only for a single agent. Multiple upgrades have not been changed. - In the agents list, reuse the existing tooltip besides the version to show the same messages when the agent is not upgradeable:  ### Checklist - [ ] [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: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8ff91980ca
commit
63f4e38f65
9 changed files with 392 additions and 41 deletions
|
@ -11,6 +11,7 @@ import {
|
|||
getRecentUpgradeInfoForAgent,
|
||||
isAgentUpgradeable,
|
||||
isAgentUpgrading,
|
||||
getNotUpgradeableMessage,
|
||||
} from './is_agent_upgradeable';
|
||||
|
||||
const getAgent = ({
|
||||
|
@ -241,6 +242,154 @@ describe('Fleet - isAgentUpgradeable', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Fleet - getNotUpgradeableMessage', () => {
|
||||
it('if agent reports not upgradeable with agent version < latest agent version', () => {
|
||||
expect(getNotUpgradeableMessage(getAgent({ version: '7.9.0' }), '8.0.0')).toBe(
|
||||
'agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service.'
|
||||
);
|
||||
});
|
||||
|
||||
it('if agent reports not upgradeable with agent version > latest agent version', () => {
|
||||
expect(getNotUpgradeableMessage(getAgent({ version: '8.0.0' }), '7.9.0')).toBe(
|
||||
'agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service.'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if agent reports not upgradeable with agent version === latest agent version', () => {
|
||||
expect(getNotUpgradeableMessage(getAgent({ version: '8.0.0' }), '8.0.0')).toBe(
|
||||
'agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service.'
|
||||
);
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, with agent version === latest agent version', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '8.0.0', upgradeable: true }), '8.0.0')
|
||||
).toBe('agent is already running on the latest available version.');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, with agent version > latest agent version', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '8.0.0', upgradeable: true }), '7.9.0')
|
||||
).toBe('agent is running on a version greater than the latest available version.');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, but agent is unenrolling', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '7.9.0', upgradeable: true, unenrolling: true }),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBe('agent is being unenrolled.');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, but agent is unenrolled', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '7.9.0', upgradeable: true, unenrolled: true }),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBe('agent has been unenrolled.');
|
||||
});
|
||||
|
||||
it('Returns no error message if agent reports upgradeable, with agent version < latest agent version', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '7.9.0', upgradeable: true }), '8.0.0')
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, with agent snapshot version === latest agent version', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '7.9.0-SNAPSHOT', upgradeable: true }), '7.9.0')
|
||||
).toBe('agent is already running on the latest available version.');
|
||||
});
|
||||
|
||||
it('it does not return message if agent reports upgradeable, with upgrade to agent snapshot version newer than latest agent version', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '8.10.2', upgradeable: true }),
|
||||
'8.10.2',
|
||||
'8.11.0-SNAPSHOT'
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, with target version < current agent version ', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '7.9.0', upgradeable: true }), '8.0.0', '7.8.0')
|
||||
).toBe('agent does not support downgrades.');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, with target version == current agent version ', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(getAgent({ version: '7.9.0', upgradeable: true }), '8.0.0', '7.9.0')
|
||||
).toBe('agent is already running on the selected version.');
|
||||
});
|
||||
|
||||
it('if agent with no upgrade details reports upgradeable, but is already upgrading', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '7.9.0', upgradeable: true, upgrading: true }),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBe('agent is already being upgraded.');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable, but has an upgrade status other than failed', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({
|
||||
version: '7.9.0',
|
||||
upgradeable: true,
|
||||
upgradeDetails: {
|
||||
target_version: '8.0.0',
|
||||
action_id: 'XXX',
|
||||
state: 'UPG_REQUESTED',
|
||||
},
|
||||
}),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBe('agent is already being upgraded.');
|
||||
});
|
||||
|
||||
it('it does not return a message if agent reports upgradeable and has a failed upgrade status', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({
|
||||
version: '7.9.0',
|
||||
upgradeable: true,
|
||||
upgradeDetails: {
|
||||
target_version: '8.0.0',
|
||||
action_id: 'XXX',
|
||||
state: 'UPG_FAILED',
|
||||
metadata: {
|
||||
error_msg: 'Upgrade timed out',
|
||||
},
|
||||
},
|
||||
}),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('if the agent reports upgradeable but was upgraded less than 10 minutes ago', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '7.9.0', upgradeable: true, minutesSinceUpgrade: 9 }),
|
||||
'8.0.0'
|
||||
)
|
||||
).toContain('please wait');
|
||||
});
|
||||
|
||||
it('if agent reports upgradeable and was upgraded more than 10 minutes ago', () => {
|
||||
expect(
|
||||
getNotUpgradeableMessage(
|
||||
getAgent({ version: '7.9.0', upgradeable: true, minutesSinceUpgrade: 11 }),
|
||||
'8.0.0'
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAgentBeenUpgradedRecently', () => {
|
||||
it('returns true if the agent was upgraded less than 10 minutes ago', () => {
|
||||
expect(
|
||||
|
|
|
@ -8,11 +8,27 @@
|
|||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
import semverEq from 'semver/functions/eq';
|
||||
import moment from 'moment';
|
||||
|
||||
import type { Agent } from '../types';
|
||||
|
||||
export const AGENT_UPGRADE_COOLDOWN_IN_MIN = 10;
|
||||
|
||||
// Error messages for agent not upgradeable
|
||||
export const VERSION_MISSING_ERROR = `agent version is missing.`;
|
||||
export const UNENROLLED_ERROR = `agent has been unenrolled.`;
|
||||
export const ONGOING_UNEROLLMENT_ERROR = `agent is being unenrolled.`;
|
||||
export const NOT_UPGRADEABLE_ERROR = `agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service.`;
|
||||
export const ALREADY_UPGRADED_ERROR = `agent is already being upgraded.`;
|
||||
export const INVALID_VERSION_ERROR = 'agent version is not valid.';
|
||||
export const SELECTED_VERSION_ERROR = 'the selected version is not valid.';
|
||||
export const RUNNING_SELECTED_VERSION_ERROR = `agent is already running on the selected version.`;
|
||||
export const DOWNGRADE_NOT_ALLOWED_ERROR = `agent does not support downgrades.`;
|
||||
export const LATEST_VERSION_NOT_VALID_ERROR = 'latest version is not valid.';
|
||||
export const AGENT_ALREADY_ON_LATEST_ERROR = `agent is already running on the latest available version.`;
|
||||
export const AGENT_ON_GREATER_VERSION_ERROR = `agent is running on a version greater than the latest available version.`;
|
||||
|
||||
export function isAgentUpgradeable(
|
||||
agent: Agent,
|
||||
latestAgentVersion: string,
|
||||
|
@ -42,21 +58,76 @@ export function isAgentUpgradeable(
|
|||
return isAgentVersionLessThanLatest(agentVersion, latestAgentVersion);
|
||||
}
|
||||
|
||||
// Based on the previous, returns a detailed message explaining why the agent is not upgradeable
|
||||
export const getNotUpgradeableMessage = (
|
||||
agent: Agent,
|
||||
latestAgentVersion?: string,
|
||||
versionToUpgrade?: string
|
||||
) => {
|
||||
let agentVersion: string;
|
||||
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
|
||||
agentVersion = agent.local_metadata.elastic.agent.version;
|
||||
} else {
|
||||
return VERSION_MISSING_ERROR;
|
||||
}
|
||||
if (agent.unenrolled_at) {
|
||||
return UNENROLLED_ERROR;
|
||||
}
|
||||
if (agent.unenrollment_started_at) {
|
||||
return ONGOING_UNEROLLMENT_ERROR;
|
||||
}
|
||||
if (!agent.local_metadata.elastic.agent.upgradeable) {
|
||||
return NOT_UPGRADEABLE_ERROR;
|
||||
}
|
||||
if (isAgentUpgrading(agent)) {
|
||||
return ALREADY_UPGRADED_ERROR;
|
||||
}
|
||||
if (getRecentUpgradeInfoForAgent(agent).hasBeenUpgradedRecently) {
|
||||
const timeToWaitMins = getRecentUpgradeInfoForAgent(agent).timeToWaitMins;
|
||||
const elapsedMinsSinceUpgrade = getRecentUpgradeInfoForAgent(agent).elapsedMinsSinceUpgrade;
|
||||
return `agent was upgraded ${elapsedMinsSinceUpgrade} minutes ago, please wait ${timeToWaitMins} minutes before attempting the upgrade again.`;
|
||||
}
|
||||
const agentVersionNumber = semverCoerce(agentVersion);
|
||||
if (!agentVersionNumber) return INVALID_VERSION_ERROR;
|
||||
|
||||
if (versionToUpgrade !== undefined) {
|
||||
const versionToUpgradeNumber = semverCoerce(versionToUpgrade);
|
||||
if (!versionToUpgradeNumber) return SELECTED_VERSION_ERROR;
|
||||
|
||||
if (semverEq(agentVersionNumber, versionToUpgradeNumber)) return RUNNING_SELECTED_VERSION_ERROR;
|
||||
|
||||
if (semverLt(versionToUpgradeNumber, agentVersionNumber)) return DOWNGRADE_NOT_ALLOWED_ERROR;
|
||||
|
||||
// explicitly allow this case - the agent is upgradeable
|
||||
if (semverGt(versionToUpgradeNumber, agentVersionNumber)) return undefined;
|
||||
}
|
||||
|
||||
const latestAgentVersionNumber = semverCoerce(latestAgentVersion);
|
||||
if (!latestAgentVersionNumber) return LATEST_VERSION_NOT_VALID_ERROR;
|
||||
|
||||
if (semverEq(agentVersionNumber, latestAgentVersionNumber)) return AGENT_ALREADY_ON_LATEST_ERROR;
|
||||
|
||||
if (semverGt(agentVersionNumber, latestAgentVersionNumber)) return AGENT_ON_GREATER_VERSION_ERROR;
|
||||
|
||||
// in all the other cases, the agent is upgradeable; don't return any message.
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const isAgentVersionLessThanLatest = (agentVersion: string, latestAgentVersion: string) => {
|
||||
// make sure versions are only the number before comparison
|
||||
const agentVersionNumber = semverCoerce(agentVersion);
|
||||
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
||||
if (!agentVersionNumber) throw new Error(`${INVALID_VERSION_ERROR}`);
|
||||
const latestAgentVersionNumber = semverCoerce(latestAgentVersion);
|
||||
if (!latestAgentVersionNumber) throw new Error('latest version is not valid');
|
||||
if (!latestAgentVersionNumber) throw new Error(`${LATEST_VERSION_NOT_VALID_ERROR}`);
|
||||
|
||||
return semverLt(agentVersionNumber, latestAgentVersionNumber);
|
||||
};
|
||||
|
||||
const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {
|
||||
const agentVersionNumber = semverCoerce(agentVersion);
|
||||
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
||||
if (!agentVersionNumber) throw new Error(`${INVALID_VERSION_ERROR}`);
|
||||
const versionToUpgradeNumber = semverCoerce(versionToUpgrade);
|
||||
if (!versionToUpgradeNumber) throw new Error('target version is not valid');
|
||||
if (!versionToUpgradeNumber) throw new Error(`${SELECTED_VERSION_ERROR}`);
|
||||
|
||||
return semverGt(versionToUpgradeNumber, agentVersionNumber);
|
||||
};
|
||||
|
@ -64,19 +135,27 @@ const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {
|
|||
export function getRecentUpgradeInfoForAgent(agent: Agent): {
|
||||
hasBeenUpgradedRecently: boolean;
|
||||
timeToWaitMs: number;
|
||||
elapsedMinsSinceUpgrade: number;
|
||||
timeToWaitMins: number;
|
||||
} {
|
||||
if (!agent.upgraded_at) {
|
||||
return {
|
||||
hasBeenUpgradedRecently: false,
|
||||
timeToWaitMs: 0,
|
||||
timeToWaitMins: 0,
|
||||
elapsedMinsSinceUpgrade: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const elaspedSinceUpgradeInMillis = Date.now() - Date.parse(agent.upgraded_at);
|
||||
const timeToWaitMs = AGENT_UPGRADE_COOLDOWN_IN_MIN * 6e4 - elaspedSinceUpgradeInMillis;
|
||||
const hasBeenUpgradedRecently = elaspedSinceUpgradeInMillis / 6e4 < AGENT_UPGRADE_COOLDOWN_IN_MIN;
|
||||
const elapsedSinceUpgradeInMillis = Date.now() - Date.parse(agent.upgraded_at);
|
||||
const elapsedMins = moment.duration(elapsedSinceUpgradeInMillis, 'milliseconds').asMinutes();
|
||||
const elapsedMinsSinceUpgrade = Math.ceil(elapsedMins);
|
||||
|
||||
return { hasBeenUpgradedRecently, timeToWaitMs };
|
||||
const timeToWaitMs = AGENT_UPGRADE_COOLDOWN_IN_MIN * 6e4 - elapsedSinceUpgradeInMillis;
|
||||
const hasBeenUpgradedRecently = elapsedSinceUpgradeInMillis / 6e4 < AGENT_UPGRADE_COOLDOWN_IN_MIN;
|
||||
const timeToWait = moment.duration(timeToWaitMs, 'milliseconds').asMinutes();
|
||||
const timeToWaitMins = Math.ceil(timeToWait);
|
||||
return { hasBeenUpgradedRecently, timeToWaitMs, elapsedMinsSinceUpgrade, timeToWaitMins };
|
||||
}
|
||||
|
||||
export function isAgentUpgrading(agent: Agent) {
|
||||
|
|
|
@ -31,6 +31,8 @@ import { Tags } from '../../components/tags';
|
|||
import type { AgentMetrics } from '../../../../../../../common/types';
|
||||
import { formatAgentCPU, formatAgentMemory } from '../../services/agent_metrics';
|
||||
|
||||
import { getNotUpgradeableMessage } from '../../../../../../../common/services/is_agent_upgradeable';
|
||||
|
||||
import { AgentUpgradeStatus } from './agent_upgrade_status';
|
||||
|
||||
import { EmptyPrompt } from './empty_prompt';
|
||||
|
@ -303,6 +305,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
|
|||
agentUpgradeStartedAt={agent.upgrade_started_at}
|
||||
agentUpgradedAt={agent.upgraded_at}
|
||||
agentUpgradeDetails={agent.upgrade_details}
|
||||
notUpgradeableMessage={getNotUpgradeableMessage(agent, latestAgentVersion)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -219,7 +219,14 @@ export const AgentUpgradeStatus: React.FC<{
|
|||
agentUpgradeStartedAt?: string | null;
|
||||
agentUpgradedAt?: string | null;
|
||||
agentUpgradeDetails?: AgentUpgradeDetails;
|
||||
}> = ({ isAgentUpgradable, agentUpgradeStartedAt, agentUpgradedAt, agentUpgradeDetails }) => {
|
||||
notUpgradeableMessage?: string | null;
|
||||
}> = ({
|
||||
isAgentUpgradable,
|
||||
agentUpgradeStartedAt,
|
||||
agentUpgradedAt,
|
||||
agentUpgradeDetails,
|
||||
notUpgradeableMessage,
|
||||
}) => {
|
||||
const isAgentUpgrading = useMemo(
|
||||
() => agentUpgradeStartedAt && !agentUpgradedAt,
|
||||
[agentUpgradeStartedAt, agentUpgradedAt]
|
||||
|
@ -227,6 +234,24 @@ export const AgentUpgradeStatus: React.FC<{
|
|||
const status = useMemo(() => getStatusComponents(agentUpgradeDetails), [agentUpgradeDetails]);
|
||||
const minVersion = '8.12';
|
||||
|
||||
if (!isAgentUpgradable && notUpgradeableMessage) {
|
||||
return (
|
||||
<EuiIconTip
|
||||
type="iInCircle"
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentUpgradeStatusBadge.notUpgradeable"
|
||||
defaultMessage="Agent not upgradeable: {reason}"
|
||||
values={{
|
||||
reason: notUpgradeableMessage,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
color="subdued"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isAgentUpgradable) {
|
||||
return (
|
||||
<EuiBadge color="hollow" iconType="sortUp">
|
||||
|
|
|
@ -29,6 +29,7 @@ jest.mock('../../../../hooks', () => {
|
|||
}),
|
||||
sendPostBulkAgentUpgrade: jest.fn(),
|
||||
useAgentVersion: jest.fn().mockReturnValue('8.10.2'),
|
||||
useKibanaVersion: jest.fn().mockReturnValue('8.10.2'),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -203,4 +204,28 @@ describe('AgentUpgradeAgentModal', () => {
|
|||
expect(el).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable submit button and display a warning for a single agent that is not upgradeable', async () => {
|
||||
const { utils } = renderAgentUpgradeAgentModal({
|
||||
agents: [
|
||||
{
|
||||
status: 'offline',
|
||||
upgrade_started_at: '2022-11-21T12:27:24Z',
|
||||
id: 'agent1',
|
||||
local_metadata: { elastic: { agent: { version: '8.9.0' } } },
|
||||
},
|
||||
] as any,
|
||||
agentCount: 2,
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(utils.queryByText(/The selected agent is not upgradeable/)).toBeInTheDocument();
|
||||
expect(
|
||||
utils.queryByText(
|
||||
/Reason: agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service./
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
const el = utils.getByTestId('confirmModalConfirmButton');
|
||||
expect(el).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,10 @@ import {
|
|||
} from '../../../../hooks';
|
||||
|
||||
import { sendGetAgentsAvailableVersions } from '../../../../hooks';
|
||||
import {
|
||||
isAgentUpgradeable,
|
||||
getNotUpgradeableMessage,
|
||||
} from '../../../../../../../common/services/is_agent_upgradeable';
|
||||
|
||||
import {
|
||||
FALLBACK_VERSIONS,
|
||||
|
@ -127,7 +131,6 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
|||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isUpdating) return;
|
||||
|
||||
getStuckUpdatingAgentCount(agents);
|
||||
|
@ -238,7 +241,9 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
|||
const getQuery = (agentsOrQuery: Agent[] | string) =>
|
||||
Array.isArray(agentsOrQuery) ? agentsOrQuery.map((agent) => agent.id) : agentsOrQuery;
|
||||
const { error } =
|
||||
isSingleAgent && !isScheduled
|
||||
isSingleAgent &&
|
||||
!isScheduled &&
|
||||
isAgentUpgradeable(agents[0], latestAgentVersion || '', selectedVersion[0].value)
|
||||
? await sendPostAgentUpgrade((agents[0] as Agent).id, {
|
||||
version,
|
||||
force: isUpdating,
|
||||
|
@ -332,7 +337,13 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
|||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
confirmButtonDisabled={isSubmitting || noVersions || (isUpdating && updatingAgents === 0)}
|
||||
confirmButtonDisabled={
|
||||
isSubmitting ||
|
||||
noVersions ||
|
||||
(isUpdating && updatingAgents === 0) ||
|
||||
(isSingleAgent &&
|
||||
!isAgentUpgradeable(agents[0], latestAgentVersion || '', selectedVersion[0].value))
|
||||
}
|
||||
confirmButtonText={
|
||||
isSingleAgent ? (
|
||||
<FormattedMessage
|
||||
|
@ -366,32 +377,58 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
|||
defaultMessage="No selected agents are eligible for an upgrade. Please select one or more eligible agents."
|
||||
/>
|
||||
) : isSingleAgent ? (
|
||||
<>
|
||||
<p>
|
||||
!isAgentUpgradeable(agents[0], latestAgentVersion || '', selectedVersion[0].value) ? (
|
||||
<EuiCallOut
|
||||
data-test-subj="agentUpgradeModal.notUpgradeableCallout"
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.notUpgradeable"
|
||||
defaultMessage="The selected agent is not upgradeable."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.upgradeSingleDescription"
|
||||
defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?"
|
||||
id="xpack.fleet.upgradeAgents.notUpgradeableMsg"
|
||||
defaultMessage="Reason: {reason}"
|
||||
values={{
|
||||
hostName: ((agents[0] as Agent).local_metadata.host as any).hostname,
|
||||
version: getVersion(selectedVersion),
|
||||
reason: getNotUpgradeableMessage(
|
||||
agents[0],
|
||||
latestAgentVersion,
|
||||
selectedVersion[0].value
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
{isUpdating && (
|
||||
</EuiCallOut>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
<em>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.upgradeSingleTimeout"
|
||||
// TODO: Add link to docs regarding agent upgrade cooldowns
|
||||
defaultMessage="Note that you may only restart an upgrade every {minutes} minutes to ensure that the upgrade will not be rolled back."
|
||||
values={{
|
||||
minutes: AGENT_UPGRADE_COOLDOWN_IN_MIN,
|
||||
}}
|
||||
/>
|
||||
</em>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.upgradeSingleDescription"
|
||||
defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?"
|
||||
values={{
|
||||
hostName: ((agents[0] as Agent).local_metadata.host as any).hostname,
|
||||
version: getVersion(selectedVersion),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
{isUpdating && (
|
||||
<p>
|
||||
<em>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.upgradeSingleTimeout"
|
||||
// TODO: Add link to docs regarding agent upgrade cooldowns
|
||||
defaultMessage="Note that you may only restart an upgrade every {minutes} minutes to ensure that the upgrade will not be rolled back."
|
||||
values={{
|
||||
minutes: AGENT_UPGRADE_COOLDOWN_IN_MIN,
|
||||
}}
|
||||
/>
|
||||
</em>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.upgradeMultipleDescription"
|
||||
|
@ -520,7 +557,15 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
|||
'Error upgrading the selected {count, plural, one {agent} other {{count} agents}}',
|
||||
values: { count: isSingleAgent },
|
||||
})}
|
||||
/>
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.upgradeAgents.warningCalloutErrorMessage"
|
||||
defaultMessage="{originalMessage}"
|
||||
values={{
|
||||
originalMessage: errors,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
) : null}
|
||||
</EuiConfirmModal>
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
isAgentUpgradeable,
|
||||
AGENT_UPGRADE_COOLDOWN_IN_MIN,
|
||||
isAgentUpgrading,
|
||||
getNotUpgradeableMessage,
|
||||
} from '../../../common/services';
|
||||
import { getMaxVersion } from '../../../common/services/get_min_max_version';
|
||||
import { getAgentById } from '../../services/agents';
|
||||
|
@ -114,7 +115,11 @@ export const postAgentUpgradeHandler: RequestHandler<
|
|||
return response.customError({
|
||||
statusCode: 400,
|
||||
body: {
|
||||
message: `agent ${request.params.agentId} is not upgradeable`,
|
||||
message: `Agent ${request.params.agentId} is not upgradeable: ${getNotUpgradeableMessage(
|
||||
agent,
|
||||
latestAgentVersion,
|
||||
version
|
||||
)}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,11 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getRecentUpgradeInfoForAgent, isAgentUpgradeable } from '../../../common/services';
|
||||
import {
|
||||
getRecentUpgradeInfoForAgent,
|
||||
isAgentUpgradeable,
|
||||
getNotUpgradeableMessage,
|
||||
} from '../../../common/services';
|
||||
|
||||
import type { Agent } from '../../types';
|
||||
|
||||
|
@ -86,7 +90,13 @@ export async function upgradeBatch(
|
|||
getRecentUpgradeInfoForAgent(agent).hasBeenUpgradedRecently ||
|
||||
(!options.force && !isAgentUpgradeable(agent, latestAgentVersion, options.version));
|
||||
if (isNotAllowed) {
|
||||
throw new FleetError(`Agent ${agent.id} is not upgradeable`);
|
||||
throw new FleetError(
|
||||
`Agent ${agent.id} is not upgradeable: ${getNotUpgradeableMessage(
|
||||
agent,
|
||||
latestAgentVersion,
|
||||
options.version
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.force && isHostedAgent(hostedPolicies, agent)) {
|
||||
|
|
|
@ -141,13 +141,16 @@ export default function (providerContext: FtrProviderContext) {
|
|||
},
|
||||
},
|
||||
});
|
||||
await supertest
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agents/agent1/upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
version: fleetServerVersionSnapshot,
|
||||
})
|
||||
.expect(400);
|
||||
expect(res.body.message).to.equal(
|
||||
'Agent agent1 is not upgradeable: agent is already running on the selected version.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should respond 200 if upgrading agent with version the same as snapshot version and force flag is passed', async () => {
|
||||
|
@ -248,26 +251,30 @@ export default function (providerContext: FtrProviderContext) {
|
|||
},
|
||||
},
|
||||
});
|
||||
await supertest
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agents/agent1/upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
version: '6.0.0',
|
||||
})
|
||||
.expect(400);
|
||||
expect(res.body.message).to.equal(
|
||||
'Agent agent1 is not upgradeable: agent does not support downgrades.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should respond 400 if trying to upgrade an agent that is unenrolling', async () => {
|
||||
await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({
|
||||
revoke: true,
|
||||
});
|
||||
await supertest
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agents/agent1/upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
version: fleetServerVersion,
|
||||
})
|
||||
.expect(400);
|
||||
expect(res.body.message).to.equal('cannot upgrade an unenrolling or unenrolled agent');
|
||||
});
|
||||
|
||||
it('should respond 400 if trying to upgrade an agent that is unenrolled', async () => {
|
||||
|
@ -281,13 +288,14 @@ export default function (providerContext: FtrProviderContext) {
|
|||
},
|
||||
},
|
||||
});
|
||||
await supertest
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agents/agent1/upgrade`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
version: fleetServerVersion,
|
||||
})
|
||||
.expect(400);
|
||||
expect(res.body.message).to.equal('cannot upgrade an unenrolling or unenrolled agent');
|
||||
});
|
||||
|
||||
it('should respond 400 if trying to upgrade an agent that is not upgradeable', async () => {
|
||||
|
@ -298,7 +306,9 @@ export default function (providerContext: FtrProviderContext) {
|
|||
version: fleetServerVersion,
|
||||
})
|
||||
.expect(400);
|
||||
expect(res.body.message).to.equal('agent agent1 is not upgradeable');
|
||||
expect(res.body.message).to.equal(
|
||||
'Agent agent1 is not upgradeable: agent cannot be upgraded through Fleet. It may be running in a container or it is not installed as a service.'
|
||||
);
|
||||
});
|
||||
|
||||
it('enrolled in a hosted agent policy should respond 400 to upgrade and not update the agent SOs', async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue