mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Fleet] Agent upgrade available should use latest agent version (#167410)
## Summary Closes https://github.com/elastic/kibana/issues/167387 Replaced using kibana version when deciding if agent upgrade is available (only in serverless, in stateful kibana version is still returned as an available version). To verify locally: - [to test stateless] add this to `kibana.dev.yml`: `xpack.fleet.internal.onlyAllowAgentUpgradeToKnownVersions: true` - extract the `agent_versions_list.json` to local kibana folder `~/kibana/x-pack/plugins/fleet/target` [agent_versions_list.json.zip](12739519/agent_versions_list.json.zip
) - verify that upgrade available warnings still work if agent is < latest agent version (8.10.2) - when trying to upgrade agent, verify that the default version is the latest agent version, and 8.11 is not in the list Agent list: <img width="1475" alt="image" src="f06b7bc8
-97e6-4ff9-b872-736ede5e969a"> Upgrade available filter - 1 agent on latest version, 9 upgradeable: <img width="1314" alt="image" src="4ff5ac02
-903b-493b-94df-68b1b7ad6846"> Agent details: <img width="1512" alt="image" src="3ff6e1d5
-2ccc-4814-83e5-c4760ad63722"> Agent on latest version has disable `Upgrade agent` action: <img width="1322" alt="image" src="f461dbf5
-04e5-4bcc-8801-48c2b1a90225"> Bulk action with one agent that is not upgradeable (already on latest version), expected error: <img width="1597" alt="image" src="8bfa46ae
-6684-4748-9fca-e908c142b642"> ### Checklist - [x] [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
This commit is contained in:
parent
c7bb851ded
commit
474c8eaf52
14 changed files with 223 additions and 65 deletions
|
@ -11,7 +11,11 @@ import semverGt from 'semver/functions/gt';
|
||||||
|
|
||||||
import type { Agent } from '../types';
|
import type { Agent } from '../types';
|
||||||
|
|
||||||
export function isAgentUpgradeable(agent: Agent, kibanaVersion: string, versionToUpgrade?: string) {
|
export function isAgentUpgradeable(
|
||||||
|
agent: Agent,
|
||||||
|
latestAgentVersion: string,
|
||||||
|
versionToUpgrade?: string
|
||||||
|
) {
|
||||||
let agentVersion: string;
|
let agentVersion: string;
|
||||||
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
|
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
|
||||||
agentVersion = agent.local_metadata.elastic.agent.version;
|
agentVersion = agent.local_metadata.elastic.agent.version;
|
||||||
|
@ -31,23 +35,23 @@ export function isAgentUpgradeable(agent: Agent, kibanaVersion: string, versionT
|
||||||
if (versionToUpgrade !== undefined) {
|
if (versionToUpgrade !== undefined) {
|
||||||
return (
|
return (
|
||||||
isNotDowngrade(agentVersion, versionToUpgrade) &&
|
isNotDowngrade(agentVersion, versionToUpgrade) &&
|
||||||
isAgentVersionLessThanKibana(agentVersion, kibanaVersion)
|
isAgentVersionLessThanLatest(agentVersion, latestAgentVersion)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return isAgentVersionLessThanKibana(agentVersion, kibanaVersion);
|
return isAgentVersionLessThanLatest(agentVersion, latestAgentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isAgentVersionLessThanKibana = (agentVersion: string, kibanaVersion: string) => {
|
const isAgentVersionLessThanLatest = (agentVersion: string, latestAgentVersion: string) => {
|
||||||
// make sure versions are only the number before comparison
|
// make sure versions are only the number before comparison
|
||||||
const agentVersionNumber = semverCoerce(agentVersion);
|
const agentVersionNumber = semverCoerce(agentVersion);
|
||||||
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
||||||
const kibanaVersionNumber = semverCoerce(kibanaVersion);
|
const latestAgentVersionNumber = semverCoerce(latestAgentVersion);
|
||||||
if (!kibanaVersionNumber) throw new Error('kibana version is not valid');
|
if (!latestAgentVersionNumber) throw new Error('latest version is not valid');
|
||||||
|
|
||||||
return semverLt(agentVersionNumber, kibanaVersionNumber);
|
return semverLt(agentVersionNumber, latestAgentVersionNumber);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {
|
const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {
|
||||||
const agentVersionNumber = semverCoerce(agentVersion);
|
const agentVersionNumber = semverCoerce(agentVersion);
|
||||||
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
if (!agentVersionNumber) throw new Error('agent version is not valid');
|
||||||
const versionToUpgradeNumber = semverCoerce(versionToUpgrade);
|
const versionToUpgradeNumber = semverCoerce(versionToUpgrade);
|
||||||
|
|
|
@ -12,14 +12,17 @@ import { createFleetTestRendererMock } from '../../../../../../mock';
|
||||||
import type { Agent, AgentPolicy } from '../../../../types';
|
import type { Agent, AgentPolicy } from '../../../../types';
|
||||||
import { ExperimentalFeaturesService } from '../../../../services';
|
import { ExperimentalFeaturesService } from '../../../../services';
|
||||||
import { useAuthz } from '../../../../../../hooks/use_authz';
|
import { useAuthz } from '../../../../../../hooks/use_authz';
|
||||||
|
import { useAgentVersion } from '../../../../../../hooks/use_agent_version';
|
||||||
|
|
||||||
import { AgentDetailsActionMenu } from './actions_menu';
|
import { AgentDetailsActionMenu } from './actions_menu';
|
||||||
|
|
||||||
jest.mock('../../../../../../services/experimental_features');
|
jest.mock('../../../../../../services/experimental_features');
|
||||||
jest.mock('../../../../../../hooks/use_authz');
|
jest.mock('../../../../../../hooks/use_authz');
|
||||||
|
jest.mock('../../../../../../hooks/use_agent_version');
|
||||||
|
|
||||||
const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
|
const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
|
||||||
const mockedUseAuthz = jest.mocked(useAuthz);
|
const mockedUseAuthz = jest.mocked(useAuthz);
|
||||||
|
const mockedUseAgentVersion = jest.mocked(useAgentVersion);
|
||||||
|
|
||||||
function renderActions({ agent, agentPolicy }: { agent: Agent; agentPolicy?: AgentPolicy }) {
|
function renderActions({ agent, agentPolicy }: { agent: Agent; agentPolicy?: AgentPolicy }) {
|
||||||
const renderer = createFleetTestRendererMock();
|
const renderer = createFleetTestRendererMock();
|
||||||
|
@ -48,6 +51,7 @@ describe('AgentDetailsActionMenu', () => {
|
||||||
all: true,
|
all: true,
|
||||||
},
|
},
|
||||||
} as any);
|
} as any);
|
||||||
|
mockedUseAgentVersion.mockReturnValue('8.10.2');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Request Diagnotics action', () => {
|
describe('Request Diagnotics action', () => {
|
||||||
|
@ -162,7 +166,6 @@ describe('AgentDetailsActionMenu', () => {
|
||||||
expect(res).toBeEnabled();
|
expect(res).toBeEnabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('Restart upgrade action', () => {
|
describe('Restart upgrade action', () => {
|
||||||
function renderAndGetRestartUpgradeButton({
|
function renderAndGetRestartUpgradeButton({
|
||||||
|
@ -204,3 +207,49 @@ describe('Restart upgrade action', () => {
|
||||||
expect(res).toBe(null);
|
expect(res).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Upgrade action', () => {
|
||||||
|
function renderAndGetUpgradeButton({
|
||||||
|
agent,
|
||||||
|
agentPolicy,
|
||||||
|
}: {
|
||||||
|
agent: Agent;
|
||||||
|
agentPolicy?: AgentPolicy;
|
||||||
|
}) {
|
||||||
|
const { utils } = renderActions({
|
||||||
|
agent,
|
||||||
|
agentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
return utils.queryByTestId('upgradeBtn');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should render an active action button if agent version is not the latest', async () => {
|
||||||
|
const res = renderAndGetUpgradeButton({
|
||||||
|
agent: {
|
||||||
|
active: true,
|
||||||
|
status: 'online',
|
||||||
|
local_metadata: { elastic: { agent: { version: '8.8.0', upgradeable: true } } },
|
||||||
|
} as any,
|
||||||
|
agentPolicy: {} as AgentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a disabled action button if agent version is latest', async () => {
|
||||||
|
const res = renderAndGetUpgradeButton({
|
||||||
|
agent: {
|
||||||
|
active: true,
|
||||||
|
status: 'online',
|
||||||
|
local_metadata: { elastic: { agent: { version: '8.10.2', upgradeable: true } } },
|
||||||
|
} as any,
|
||||||
|
agentPolicy: {} as AgentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).not.toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { isAgentRequestDiagnosticsSupported } from '../../../../../../../common/
|
||||||
import { isStuckInUpdating } from '../../../../../../../common/services/agent_status';
|
import { isStuckInUpdating } from '../../../../../../../common/services/agent_status';
|
||||||
|
|
||||||
import type { Agent, AgentPolicy } from '../../../../types';
|
import type { Agent, AgentPolicy } from '../../../../types';
|
||||||
import { useAuthz, useKibanaVersion } from '../../../../hooks';
|
import { useAgentVersion, useAuthz } from '../../../../hooks';
|
||||||
import { ContextMenuActions } from '../../../../components';
|
import { ContextMenuActions } from '../../../../components';
|
||||||
import {
|
import {
|
||||||
AgentUnenrollAgentModal,
|
AgentUnenrollAgentModal,
|
||||||
|
@ -34,7 +34,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
||||||
onCancelReassign?: () => void;
|
onCancelReassign?: () => void;
|
||||||
}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign, agentPolicy }) => {
|
}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign, agentPolicy }) => {
|
||||||
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const kibanaVersion = useKibanaVersion();
|
const latestAgentVersion = useAgentVersion();
|
||||||
const refreshAgent = useAgentRefresh();
|
const refreshAgent = useAgentRefresh();
|
||||||
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault);
|
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault);
|
||||||
const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false);
|
const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false);
|
||||||
|
@ -102,11 +102,12 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
icon="refresh"
|
icon="refresh"
|
||||||
disabled={!isAgentUpgradeable(agent, kibanaVersion)}
|
disabled={!!latestAgentVersion && !isAgentUpgradeable(agent, latestAgentVersion)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsUpgradeModalOpen(true);
|
setIsUpgradeModalOpen(true);
|
||||||
}}
|
}}
|
||||||
key="upgradeAgent"
|
key="upgradeAgent"
|
||||||
|
data-test-subj="upgradeBtn"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.agentList.upgradeOneButton"
|
id="xpack.fleet.agentList.upgradeOneButton"
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
|
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
|
||||||
|
|
||||||
import type { Agent, AgentPolicy } from '../../../../../types';
|
import type { Agent, AgentPolicy } from '../../../../../types';
|
||||||
import { useKibanaVersion } from '../../../../../hooks';
|
import { useAgentVersion } from '../../../../../hooks';
|
||||||
import { ExperimentalFeaturesService, isAgentUpgradeable } from '../../../../../services';
|
import { ExperimentalFeaturesService, isAgentUpgradeable } from '../../../../../services';
|
||||||
import { AgentPolicySummaryLine } from '../../../../../components';
|
import { AgentPolicySummaryLine } from '../../../../../components';
|
||||||
import { AgentHealth } from '../../../components';
|
import { AgentHealth } from '../../../components';
|
||||||
|
@ -39,7 +39,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{
|
||||||
agent: Agent;
|
agent: Agent;
|
||||||
agentPolicy?: AgentPolicy;
|
agentPolicy?: AgentPolicy;
|
||||||
}> = memo(({ agent, agentPolicy }) => {
|
}> = memo(({ agent, agentPolicy }) => {
|
||||||
const kibanaVersion = useKibanaVersion();
|
const latestAgentVersion = useAgentVersion();
|
||||||
const { displayAgentMetrics } = ExperimentalFeaturesService.get();
|
const { displayAgentMetrics } = ExperimentalFeaturesService.get();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -173,7 +173,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{
|
||||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||||
{agent.local_metadata.elastic.agent.version}
|
{agent.local_metadata.elastic.agent.version}
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
{isAgentUpgradeable(agent, kibanaVersion) ? (
|
{latestAgentVersion && isAgentUpgradeable(agent, latestAgentVersion) ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
position="right"
|
position="right"
|
||||||
|
|
|
@ -23,7 +23,8 @@ import { isAgentUpgradeable, ExperimentalFeaturesService } from '../../../../ser
|
||||||
import { AgentHealth } from '../../components';
|
import { AgentHealth } from '../../components';
|
||||||
|
|
||||||
import type { Pagination } from '../../../../hooks';
|
import type { Pagination } from '../../../../hooks';
|
||||||
import { useLink, useKibanaVersion, useAuthz } from '../../../../hooks';
|
import { useAgentVersion } from '../../../../hooks';
|
||||||
|
import { useLink, useAuthz } from '../../../../hooks';
|
||||||
|
|
||||||
import { AgentPolicySummaryLine } from '../../../../components';
|
import { AgentPolicySummaryLine } from '../../../../components';
|
||||||
import { Tags } from '../../components/tags';
|
import { Tags } from '../../components/tags';
|
||||||
|
@ -91,7 +92,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
|
||||||
const { displayAgentMetrics } = ExperimentalFeaturesService.get();
|
const { displayAgentMetrics } = ExperimentalFeaturesService.get();
|
||||||
|
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const kibanaVersion = useKibanaVersion();
|
const latestAgentVersion = useAgentVersion();
|
||||||
|
|
||||||
const isAgentSelectable = (agent: Agent) => {
|
const isAgentSelectable = (agent: Agent) => {
|
||||||
if (!agent.active) return false;
|
if (!agent.active) return false;
|
||||||
|
@ -284,7 +285,9 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
|
||||||
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
<EuiFlexItem grow={false} className="eui-textNoWrap">
|
||||||
{safeMetadata(version)}
|
{safeMetadata(version)}
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
{isAgentSelectable(agent) && isAgentUpgradeable(agent, kibanaVersion) ? (
|
{isAgentSelectable(agent) &&
|
||||||
|
latestAgentVersion &&
|
||||||
|
isAgentUpgradeable(agent, latestAgentVersion) ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiText color="subdued" size="xs" className="eui-textNoWrap">
|
<EuiText color="subdued" size="xs" className="eui-textNoWrap">
|
||||||
<EuiIcon size="m" type="warning" color="warning" />
|
<EuiIcon size="m" type="warning" color="warning" />
|
||||||
|
@ -324,7 +327,10 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
|
||||||
totalAgents
|
totalAgents
|
||||||
? showUpgradeable
|
? showUpgradeable
|
||||||
? agents.filter(
|
? agents.filter(
|
||||||
(agent) => isAgentSelectable(agent) && isAgentUpgradeable(agent, kibanaVersion)
|
(agent) =>
|
||||||
|
isAgentSelectable(agent) &&
|
||||||
|
latestAgentVersion &&
|
||||||
|
isAgentUpgradeable(agent, latestAgentVersion)
|
||||||
)
|
)
|
||||||
: agents
|
: agents
|
||||||
: []
|
: []
|
||||||
|
|
|
@ -12,14 +12,17 @@ import { createFleetTestRendererMock } from '../../../../../../mock';
|
||||||
import type { Agent, AgentPolicy } from '../../../../types';
|
import type { Agent, AgentPolicy } from '../../../../types';
|
||||||
import { ExperimentalFeaturesService } from '../../../../services';
|
import { ExperimentalFeaturesService } from '../../../../services';
|
||||||
import { useAuthz } from '../../../../../../hooks/use_authz';
|
import { useAuthz } from '../../../../../../hooks/use_authz';
|
||||||
|
import { useAgentVersion } from '../../../../../../hooks/use_agent_version';
|
||||||
|
|
||||||
import { TableRowActions } from './table_row_actions';
|
import { TableRowActions } from './table_row_actions';
|
||||||
|
|
||||||
jest.mock('../../../../../../services/experimental_features');
|
jest.mock('../../../../../../services/experimental_features');
|
||||||
jest.mock('../../../../../../hooks/use_authz');
|
jest.mock('../../../../../../hooks/use_authz');
|
||||||
|
jest.mock('../../../../../../hooks/use_agent_version');
|
||||||
|
|
||||||
const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
|
const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
|
||||||
const mockedUseAuthz = jest.mocked(useAuthz);
|
const mockedUseAuthz = jest.mocked(useAuthz);
|
||||||
|
const mockedUseAgentVersion = jest.mocked(useAgentVersion);
|
||||||
|
|
||||||
function renderTableRowActions({
|
function renderTableRowActions({
|
||||||
agent,
|
agent,
|
||||||
|
@ -57,6 +60,7 @@ describe('TableRowActions', () => {
|
||||||
all: true,
|
all: true,
|
||||||
},
|
},
|
||||||
} as any);
|
} as any);
|
||||||
|
mockedUseAgentVersion.mockReturnValue('8.10.2');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Request Diagnotics action', () => {
|
describe('Request Diagnotics action', () => {
|
||||||
|
@ -179,4 +183,53 @@ describe('TableRowActions', () => {
|
||||||
expect(res).toBe(null);
|
expect(res).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Upgrade action', () => {
|
||||||
|
function renderAndGetUpgradeButton({
|
||||||
|
agent,
|
||||||
|
agentPolicy,
|
||||||
|
}: {
|
||||||
|
agent: Agent;
|
||||||
|
agentPolicy?: AgentPolicy;
|
||||||
|
}) {
|
||||||
|
const { utils } = renderTableRowActions({
|
||||||
|
agent,
|
||||||
|
agentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
return utils.queryByTestId('upgradeBtn');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should render an active action button if agent version is not the latest', async () => {
|
||||||
|
const res = renderAndGetUpgradeButton({
|
||||||
|
agent: {
|
||||||
|
active: true,
|
||||||
|
status: 'online',
|
||||||
|
local_metadata: { elastic: { agent: { version: '8.8.0', upgradeable: true } } },
|
||||||
|
} as any,
|
||||||
|
agentPolicy: {
|
||||||
|
is_managed: false,
|
||||||
|
} as AgentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a disabled action button if agent version is latest', async () => {
|
||||||
|
const res = renderAndGetUpgradeButton({
|
||||||
|
agent: {
|
||||||
|
active: true,
|
||||||
|
status: 'online',
|
||||||
|
local_metadata: { elastic: { agent: { version: '8.10.2', upgradeable: true } } },
|
||||||
|
} as any,
|
||||||
|
agentPolicy: {
|
||||||
|
is_managed: false,
|
||||||
|
} as AgentPolicy,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).not.toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { isAgentRequestDiagnosticsSupported } from '../../../../../../../common/
|
||||||
import { isStuckInUpdating } from '../../../../../../../common/services/agent_status';
|
import { isStuckInUpdating } from '../../../../../../../common/services/agent_status';
|
||||||
|
|
||||||
import type { Agent, AgentPolicy } from '../../../../types';
|
import type { Agent, AgentPolicy } from '../../../../types';
|
||||||
import { useAuthz, useLink, useKibanaVersion } from '../../../../hooks';
|
import { useAuthz, useLink, useAgentVersion } from '../../../../hooks';
|
||||||
import { ContextMenuActions } from '../../../../components';
|
import { ContextMenuActions } from '../../../../components';
|
||||||
import { isAgentUpgradeable } from '../../../../services';
|
import { isAgentUpgradeable } from '../../../../services';
|
||||||
import { ExperimentalFeaturesService } from '../../../../services';
|
import { ExperimentalFeaturesService } from '../../../../services';
|
||||||
|
@ -42,7 +42,7 @@ export const TableRowActions: React.FunctionComponent<{
|
||||||
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
|
|
||||||
const isUnenrolling = agent.status === 'unenrolling';
|
const isUnenrolling = agent.status === 'unenrolling';
|
||||||
const kibanaVersion = useKibanaVersion();
|
const latestAgentVersion = useAgentVersion();
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const { diagnosticFileUploadEnabled, agentTamperProtectionEnabled } =
|
const { diagnosticFileUploadEnabled, agentTamperProtectionEnabled } =
|
||||||
ExperimentalFeaturesService.get();
|
ExperimentalFeaturesService.get();
|
||||||
|
@ -107,10 +107,11 @@ export const TableRowActions: React.FunctionComponent<{
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
key="agentUpgradeBtn"
|
key="agentUpgradeBtn"
|
||||||
icon="refresh"
|
icon="refresh"
|
||||||
disabled={!isAgentUpgradeable(agent, kibanaVersion)}
|
disabled={!!latestAgentVersion && !isAgentUpgradeable(agent, latestAgentVersion)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onUpgradeClick();
|
onUpgradeClick();
|
||||||
}}
|
}}
|
||||||
|
data-test-subj="upgradeBtn"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.agentList.upgradeOneButton"
|
id="xpack.fleet.agentList.upgradeOneButton"
|
||||||
|
|
|
@ -21,13 +21,14 @@ jest.mock('../../../../hooks', () => {
|
||||||
...jest.requireActual('../../../../hooks'),
|
...jest.requireActual('../../../../hooks'),
|
||||||
sendGetAgentsAvailableVersions: jest.fn().mockResolvedValue({
|
sendGetAgentsAvailableVersions: jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
items: ['8.7.0'],
|
items: ['8.10.2', '8.7.0'],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sendGetAgentStatus: jest.fn().mockResolvedValue({
|
sendGetAgentStatus: jest.fn().mockResolvedValue({
|
||||||
data: { results: { updating: 2 } },
|
data: { results: { updating: 2 } },
|
||||||
}),
|
}),
|
||||||
sendPostBulkAgentUpgrade: jest.fn(),
|
sendPostBulkAgentUpgrade: jest.fn(),
|
||||||
|
useAgentVersion: jest.fn().mockReturnValue('8.10.2'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -86,6 +87,18 @@ describe('AgentUpgradeAgentModal', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should default the version combo to latest agent version', async () => {
|
||||||
|
const { utils } = renderAgentUpgradeAgentModal({
|
||||||
|
agents: [{ id: 'agent1', local_metadata: { host: 'abc' } }] as any,
|
||||||
|
agentCount: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const el = utils.getByTestId('agentUpgradeModal.VersionCombobox');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(el.textContent).toEqual('8.10.2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should restart uprade on updating agents if some agents in updating', async () => {
|
it('should restart uprade on updating agents if some agents in updating', async () => {
|
||||||
const { utils } = renderAgentUpgradeAgentModal({
|
const { utils } = renderAgentUpgradeAgentModal({
|
||||||
agents: [
|
agents: [
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
useKibanaVersion,
|
useKibanaVersion,
|
||||||
useConfig,
|
useConfig,
|
||||||
sendGetAgentStatus,
|
sendGetAgentStatus,
|
||||||
|
useAgentVersion,
|
||||||
} from '../../../../hooks';
|
} from '../../../../hooks';
|
||||||
|
|
||||||
import { sendGetAgentsAvailableVersions } from '../../../../hooks';
|
import { sendGetAgentsAvailableVersions } from '../../../../hooks';
|
||||||
|
@ -196,6 +197,20 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const [selectedVersion, setSelectedVersion] = useState(preselected);
|
const [selectedVersion, setSelectedVersion] = useState(preselected);
|
||||||
|
|
||||||
|
// latest agent version might be earlier than kibana version
|
||||||
|
const latestAgentVersion = useAgentVersion();
|
||||||
|
useEffect(() => {
|
||||||
|
if (latestAgentVersion) {
|
||||||
|
setSelectedVersion([
|
||||||
|
{
|
||||||
|
label: latestAgentVersion,
|
||||||
|
value: latestAgentVersion,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [latestAgentVersion]);
|
||||||
|
|
||||||
const [selectedMaintenanceWindow, setSelectedMaintenanceWindow] = useState([
|
const [selectedMaintenanceWindow, setSelectedMaintenanceWindow] = useState([
|
||||||
isSmallBatch ? maintenanceOptions[0] : maintenanceOptions[1],
|
isSmallBatch ? maintenanceOptions[0] : maintenanceOptions[1],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { getAgentById } from '../../services/agents';
|
||||||
import type { Agent } from '../../types';
|
import type { Agent } from '../../types';
|
||||||
|
|
||||||
import { getAllFleetServerAgents } from '../../collectors/get_all_fleet_server_agents';
|
import { getAllFleetServerAgents } from '../../collectors/get_all_fleet_server_agents';
|
||||||
|
import { getLatestAvailableVersion } from '../../services/agents/versions';
|
||||||
|
|
||||||
export const postAgentUpgradeHandler: RequestHandler<
|
export const postAgentUpgradeHandler: RequestHandler<
|
||||||
TypeOf<typeof PostAgentUpgradeRequestSchema.params>,
|
TypeOf<typeof PostAgentUpgradeRequestSchema.params>,
|
||||||
|
@ -35,6 +36,7 @@ export const postAgentUpgradeHandler: RequestHandler<
|
||||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||||
const { version, source_uri: sourceUri, force } = request.body;
|
const { version, source_uri: sourceUri, force } = request.body;
|
||||||
const kibanaVersion = appContextService.getKibanaVersion();
|
const kibanaVersion = appContextService.getKibanaVersion();
|
||||||
|
const latestAgentVersion = await getLatestAvailableVersion();
|
||||||
try {
|
try {
|
||||||
checkKibanaVersion(version, kibanaVersion, force);
|
checkKibanaVersion(version, kibanaVersion, force);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -73,7 +75,7 @@ export const postAgentUpgradeHandler: RequestHandler<
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!force && !isAgentUpgradeable(agent, kibanaVersion, version)) {
|
if (!force && !isAgentUpgradeable(agent, latestAgentVersion, version)) {
|
||||||
return response.customError({
|
return response.customError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { auditLoggingService } from '../audit_logging';
|
||||||
import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';
|
import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';
|
||||||
|
|
||||||
import { buildAgentStatusRuntimeField } from './build_status_runtime_field';
|
import { buildAgentStatusRuntimeField } from './build_status_runtime_field';
|
||||||
|
import { getLatestAvailableVersion } from './versions';
|
||||||
|
|
||||||
const INACTIVE_AGENT_CONDITION = `status:inactive OR status:unenrolled`;
|
const INACTIVE_AGENT_CONDITION = `status:inactive OR status:unenrolled`;
|
||||||
const ACTIVE_AGENT_CONDITION = `NOT (${INACTIVE_AGENT_CONDITION})`;
|
const ACTIVE_AGENT_CONDITION = `NOT (${INACTIVE_AGENT_CONDITION})`;
|
||||||
|
@ -302,6 +303,7 @@ export async function getAgentsByKuery(
|
||||||
// filtering for a range on the version string will not work,
|
// filtering for a range on the version string will not work,
|
||||||
// nor does filtering on a flattened field (local_metadata), so filter here
|
// nor does filtering on a flattened field (local_metadata), so filter here
|
||||||
if (showUpgradeable) {
|
if (showUpgradeable) {
|
||||||
|
const latestAgentVersion = await getLatestAvailableVersion();
|
||||||
// fixing a bug where upgradeable filter was not returning right results https://github.com/elastic/kibana/issues/117329
|
// fixing a bug where upgradeable filter was not returning right results https://github.com/elastic/kibana/issues/117329
|
||||||
// query all agents, then filter upgradeable, and return the requested page and correct total
|
// query all agents, then filter upgradeable, and return the requested page and correct total
|
||||||
// if there are more than SO_SEARCH_LIMIT agents, the logic falls back to same as before
|
// if there are more than SO_SEARCH_LIMIT agents, the logic falls back to same as before
|
||||||
|
@ -309,14 +311,12 @@ export async function getAgentsByKuery(
|
||||||
const response = await queryAgents(0, SO_SEARCH_LIMIT);
|
const response = await queryAgents(0, SO_SEARCH_LIMIT);
|
||||||
agents = response.hits.hits
|
agents = response.hits.hits
|
||||||
.map(searchHitToAgent)
|
.map(searchHitToAgent)
|
||||||
.filter((agent) => isAgentUpgradeable(agent, appContextService.getKibanaVersion()));
|
.filter((agent) => isAgentUpgradeable(agent, latestAgentVersion));
|
||||||
total = agents.length;
|
total = agents.length;
|
||||||
const start = (page - 1) * perPage;
|
const start = (page - 1) * perPage;
|
||||||
agents = agents.slice(start, start + perPage);
|
agents = agents.slice(start, start + perPage);
|
||||||
} else {
|
} else {
|
||||||
agents = agents.filter((agent) =>
|
agents = agents.filter((agent) => isAgentUpgradeable(agent, latestAgentVersion));
|
||||||
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { createErrorActionResults, createAgentAction } from './actions';
|
||||||
import { getHostedPolicies, isHostedAgent } from './hosted_agent';
|
import { getHostedPolicies, isHostedAgent } from './hosted_agent';
|
||||||
import { BulkActionTaskType } from './bulk_action_types';
|
import { BulkActionTaskType } from './bulk_action_types';
|
||||||
import { getCancelledActions } from './action_status';
|
import { getCancelledActions } from './action_status';
|
||||||
|
import { getLatestAvailableVersion } from './versions';
|
||||||
|
|
||||||
export class UpgradeActionRunner extends ActionRunner {
|
export class UpgradeActionRunner extends ActionRunner {
|
||||||
protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> {
|
protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> {
|
||||||
|
@ -72,12 +73,12 @@ export async function upgradeBatch(
|
||||||
? givenAgents.filter((agent: Agent) => !isHostedAgent(hostedPolicies, agent))
|
? givenAgents.filter((agent: Agent) => !isHostedAgent(hostedPolicies, agent))
|
||||||
: givenAgents;
|
: givenAgents;
|
||||||
|
|
||||||
const kibanaVersion = appContextService.getKibanaVersion();
|
const latestAgentVersion = await getLatestAvailableVersion();
|
||||||
const upgradeableResults = await Promise.allSettled(
|
const upgradeableResults = await Promise.allSettled(
|
||||||
agentsToCheckUpgradeable.map(async (agent) => {
|
agentsToCheckUpgradeable.map(async (agent) => {
|
||||||
// Filter out agents currently unenrolling, unenrolled, or not upgradeable b/c of version check
|
// Filter out agents currently unenrolling, unenrolled, or not upgradeable b/c of version check
|
||||||
const isNotAllowed =
|
const isNotAllowed =
|
||||||
!options.force && !isAgentUpgradeable(agent, kibanaVersion, options.version);
|
!options.force && !isAgentUpgradeable(agent, latestAgentVersion, options.version);
|
||||||
if (isNotAllowed) {
|
if (isNotAllowed) {
|
||||||
throw new FleetError(`Agent ${agent.id} is not upgradeable`);
|
throw new FleetError(`Agent ${agent.id} is not upgradeable`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe('getAvailableVersions', () => {
|
||||||
mockKibanaVersion = '300.0.0';
|
mockKibanaVersion = '300.0.0';
|
||||||
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
|
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
|
||||||
|
|
||||||
const res = await getAvailableVersions({ cached: false });
|
const res = await getAvailableVersions({ cached: false, includeCurrentVersion: true });
|
||||||
|
|
||||||
expect(res).toEqual(['300.0.0', '8.1.0', '8.0.0', '7.17.0']);
|
expect(res).toEqual(['300.0.0', '8.1.0', '8.0.0', '7.17.0']);
|
||||||
});
|
});
|
||||||
|
@ -39,11 +39,11 @@ describe('getAvailableVersions', () => {
|
||||||
mockKibanaVersion = '300.0.0-SNAPSHOT';
|
mockKibanaVersion = '300.0.0-SNAPSHOT';
|
||||||
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
|
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
|
||||||
|
|
||||||
const res = await getAvailableVersions({ cached: false });
|
const res = await getAvailableVersions({ cached: false, includeCurrentVersion: true });
|
||||||
expect(res).toEqual(['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0']);
|
expect(res).toEqual(['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include the current version if onlyAllowAgentUpgradeToKnownVersions = true', async () => {
|
it('should not include the current version if includeCurrentVersion is not set', async () => {
|
||||||
mockKibanaVersion = '300.0.0-SNAPSHOT';
|
mockKibanaVersion = '300.0.0-SNAPSHOT';
|
||||||
mockConfig = {
|
mockConfig = {
|
||||||
internal: {
|
internal: {
|
||||||
|
@ -65,4 +65,18 @@ describe('getAvailableVersions', () => {
|
||||||
|
|
||||||
expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
|
expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return kibana version only if cannot read versions', async () => {
|
||||||
|
mockKibanaVersion = '300.0.0';
|
||||||
|
mockConfig = {
|
||||||
|
internal: {
|
||||||
|
onlyAllowAgentUpgradeToKnownVersions: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockedReadFile.mockRejectedValue({ code: 'ENOENT' });
|
||||||
|
|
||||||
|
const res = await getAvailableVersions({ cached: false });
|
||||||
|
|
||||||
|
expect(res).toEqual(['300.0.0']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,8 +60,7 @@ export const getAvailableVersions = async ({
|
||||||
.sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1));
|
.sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1));
|
||||||
versionsToDisplay = uniq(versions) as string[];
|
versionsToDisplay = uniq(versions) as string[];
|
||||||
|
|
||||||
const appendCurrentVersion =
|
const appendCurrentVersion = includeCurrentVersion;
|
||||||
includeCurrentVersion ?? !config?.internal?.onlyAllowAgentUpgradeToKnownVersions;
|
|
||||||
|
|
||||||
if (appendCurrentVersion) {
|
if (appendCurrentVersion) {
|
||||||
// Add current version if not already present
|
// Add current version if not already present
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue