Skip to content

Commit fcdab35

Browse files
[Ingest Manager] Upgrade Agents in Fleet (#78810)
* add kibanaVersion context and hook, add upgrade available indications * add agent upgrade modals and action buttons * fix import * add bulk actions api and remove source_uri as required * add upgrading to AgentHealth status * buildKueryForUpgradingAgents * bulk actions UI * remove source_uri * add release type to agent details * don't allow upgrade of unenrolled/unenrolling agent * hide upgradeable button when not upgradeable * fix test * add udpating agent status * remove upgrade available filter button for now * update isUpgradeAvailable to use local_metadata upgradeable * add UPDATING to agent event subtype * use saved object for updating agent status * add updating badge type label * add upgrade available button and update agent list endpoint to accept showUpgradeable * add schema and type for UPDATING * fix type * dont try to upgrade local_metadata * exclude from AAD upgrade_started_at and upgraded_at Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 40bae24 commit fcdab35

35 files changed

Lines changed: 901 additions & 57 deletions

File tree

x-pack/plugins/ingest_manager/common/constants/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const AGENT_API_ROUTES = {
9191
BULK_REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/bulk_reassign`,
9292
STATUS_PATTERN: `${FLEET_API_ROOT}/agent-status`,
9393
UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/upgrade`,
94+
BULK_UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/bulk_upgrade`,
9495
};
9596

9697
export const ENROLLMENT_API_KEY_ROUTES = {

x-pack/plugins/ingest_manager/common/services/agent_status.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
1919
if (!agent.last_checkin) {
2020
return 'enrolling';
2121
}
22-
if (agent.upgrade_started_at && !agent.upgraded_at) {
23-
return 'upgrading';
24-
}
2522

2623
const msLastCheckIn = new Date(lastCheckIn || 0).getTime();
2724
const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn;
@@ -33,6 +30,9 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
3330
if (agent.last_checkin_status === 'degraded') {
3431
return 'degraded';
3532
}
33+
if (agent.upgrade_started_at && !agent.upgraded_at) {
34+
return 'updating';
35+
}
3636
if (intervalsSinceLastCheckIn >= 4) {
3737
return 'offline';
3838
}
@@ -61,3 +61,7 @@ export function buildKueryForOfflineAgents() {
6161
(4 * AGENT_POLLING_THRESHOLD_MS) / 1000
6262
}s AND not (${buildKueryForErrorAgents()})`;
6363
}
64+
65+
export function buildKueryForUpdatingAgents() {
66+
return `${AGENT_SAVED_OBJECT_TYPE}.upgrade_started_at:*`;
67+
}

x-pack/plugins/ingest_manager/common/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export { decodeCloudId } from './decode_cloud_id';
1313
export { isValidNamespace } from './is_valid_namespace';
1414
export { isDiffPathProtocol } from './is_diff_path_protocol';
1515
export { LicenseService } from './license';
16+
export { isAgentUpgradeable } from './is_agent_upgradeable';
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { isAgentUpgradeable } from './is_agent_upgradeable';
7+
import { Agent } from '../types/models/agent';
8+
9+
const getAgent = (version: string, upgradeable: boolean): Agent => {
10+
const agent: Agent = {
11+
id: 'de9006e1-54a7-4320-b24e-927e6fe518a8',
12+
active: true,
13+
policy_id: '63a284b0-0334-11eb-a4e0-09883c57114b',
14+
type: 'PERMANENT',
15+
enrolled_at: '2020-09-30T20:24:08.347Z',
16+
user_provided_metadata: {},
17+
local_metadata: {
18+
elastic: {
19+
agent: {
20+
id: 'de9006e1-54a7-4320-b24e-927e6fe518a8',
21+
version,
22+
snapshot: false,
23+
'build.original':
24+
'8.0.0 (build: e2ef4fc375a5ece83d5d38f57b2977d7866b5819 at 2020-09-30 20:21:35 +0000 UTC)',
25+
},
26+
},
27+
host: {
28+
architecture: 'x86_64',
29+
hostname: 'Sandras-MBP.fios-router.home',
30+
name: 'Sandras-MBP.fios-router.home',
31+
id: '1112D0AD-526D-5268-8E86-765D35A0F484',
32+
ip: [
33+
'127.0.0.1/8',
34+
'::1/128',
35+
'fe80::1/64',
36+
'fe80::aede:48ff:fe00:1122/64',
37+
'fe80::4fc:2526:7d51:19cc/64',
38+
'192.168.1.161/24',
39+
'fe80::3083:5ff:fe30:4b00/64',
40+
'fe80::3083:5ff:fe30:4b00/64',
41+
'fe80::f7fb:518e:2c3c:7815/64',
42+
'fe80::2abd:20e3:9bc3:c054/64',
43+
'fe80::531a:20ab:1f38:7f9/64',
44+
],
45+
mac: [
46+
'a6:83:e7:b0:1a:d2',
47+
'ac:de:48:00:11:22',
48+
'a4:83:e7:b0:1a:d2',
49+
'82:c5:c2:25:b0:01',
50+
'82:c5:c2:25:b0:00',
51+
'82:c5:c2:25:b0:05',
52+
'82:c5:c2:25:b0:04',
53+
'82:c5:c2:25:b0:01',
54+
'06:83:e7:b0:1a:d2',
55+
'32:83:05:30:4b:00',
56+
'32:83:05:30:4b:00',
57+
],
58+
},
59+
os: {
60+
family: 'darwin',
61+
kernel: '19.4.0',
62+
platform: 'darwin',
63+
version: '10.15.4',
64+
name: 'Mac OS X',
65+
full: 'Mac OS X(10.15.4)',
66+
},
67+
},
68+
access_api_key_id: 'A_6v4HQBEEDXi-A9vxPE',
69+
default_api_key_id: 'BP6v4HQBEEDXi-A95xMk',
70+
policy_revision: 1,
71+
packages: ['system'],
72+
last_checkin: '2020-10-01T14:43:27.255Z',
73+
current_error_events: [],
74+
status: 'online',
75+
};
76+
if (upgradeable) {
77+
agent.local_metadata.elastic.agent.upgradeable = true;
78+
}
79+
return agent;
80+
};
81+
describe('Ingest Manager - isAgentUpgradeable', () => {
82+
it('returns false if agent reports not upgradeable with agent version < kibana version', () => {
83+
expect(isAgentUpgradeable(getAgent('7.9.0', false), '8.0.0')).toBe(false);
84+
});
85+
it('returns false if agent reports not upgradeable with agent version > kibana version', () => {
86+
expect(isAgentUpgradeable(getAgent('8.0.0', false), '7.9.0')).toBe(false);
87+
});
88+
it('returns false if agent reports not upgradeable with agent version === kibana version', () => {
89+
expect(isAgentUpgradeable(getAgent('8.0.0', false), '8.0.0')).toBe(false);
90+
});
91+
it('returns false if agent reports upgradeable, with agent version === kibana version', () => {
92+
expect(isAgentUpgradeable(getAgent('8.0.0', true), '8.0.0')).toBe(false);
93+
});
94+
it('returns false if agent reports upgradeable, with agent version > kibana version', () => {
95+
expect(isAgentUpgradeable(getAgent('8.0.0', true), '7.9.0')).toBe(false);
96+
});
97+
it('returns true if agent reports upgradeable, with agent version < kibana version', () => {
98+
expect(isAgentUpgradeable(getAgent('7.9.0', true), '8.0.0')).toBe(true);
99+
});
100+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import semver from 'semver';
7+
import { Agent } from '../types';
8+
9+
export function isAgentUpgradeable(agent: Agent, kibanaVersion: string) {
10+
let agentVersion: string;
11+
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
12+
agentVersion = agent.local_metadata.elastic.agent.version;
13+
} else {
14+
return false;
15+
}
16+
const kibanaVersionParsed = semver.parse(kibanaVersion);
17+
const agentVersionParsed = semver.parse(agentVersion);
18+
if (!agentVersionParsed || !kibanaVersionParsed) return false;
19+
if (!agent.local_metadata.elastic.agent.upgradeable) return false;
20+
return semver.lt(agentVersionParsed, kibanaVersionParsed);
21+
}

x-pack/plugins/ingest_manager/common/services/routes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ export const agentRouteService = {
135135
getReassignPath: (agentId: string) =>
136136
AGENT_API_ROUTES.REASSIGN_PATTERN.replace('{agentId}', agentId),
137137
getBulkReassignPath: () => AGENT_API_ROUTES.BULK_REASSIGN_PATTERN,
138+
getUpgradePath: (agentId: string) =>
139+
AGENT_API_ROUTES.UPGRADE_PATTERN.replace('{agentId}', agentId),
140+
getBulkUpgradePath: () => AGENT_API_ROUTES.BULK_UPGRADE_PATTERN,
138141
getListPath: () => AGENT_API_ROUTES.LIST_PATTERN,
139142
getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN,
140143
};

x-pack/plugins/ingest_manager/common/types/models/agent.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type AgentStatus =
1919
| 'warning'
2020
| 'enrolling'
2121
| 'unenrolling'
22-
| 'upgrading'
22+
| 'updating'
2323
| 'degraded';
2424

2525
export type AgentActionType = 'POLICY_CHANGE' | 'UNENROLL' | 'UPGRADE';
@@ -89,6 +89,7 @@ export interface NewAgentEvent {
8989
| 'STOPPING'
9090
| 'STOPPED'
9191
| 'DEGRADED'
92+
| 'UPDATING'
9293
// Action results
9394
| 'DATA_DUMP'
9495
// Actions
@@ -109,10 +110,8 @@ export interface AgentEvent extends NewAgentEvent {
109110

110111
export type AgentEventSOAttributes = NewAgentEvent;
111112

112-
type MetadataValue = string | AgentMetadata;
113-
114113
export interface AgentMetadata {
115-
[x: string]: MetadataValue;
114+
[x: string]: any;
116115
}
117116
interface AgentBase {
118117
type: AgentType;
@@ -129,7 +128,7 @@ interface AgentBase {
129128
policy_id?: string;
130129
policy_revision?: number | null;
131130
last_checkin?: string;
132-
last_checkin_status?: 'error' | 'online' | 'degraded';
131+
last_checkin_status?: 'error' | 'online' | 'degraded' | 'updating';
133132
user_provided_metadata: AgentMetadata;
134133
local_metadata: AgentMetadata;
135134
}

x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface GetAgentsRequest {
2020
perPage: number;
2121
kuery?: string;
2222
showInactive: boolean;
23+
showUpgradeable?: boolean;
2324
};
2425
}
2526

@@ -113,22 +114,38 @@ export interface PostAgentUnenrollRequest {
113114
// eslint-disable-next-line @typescript-eslint/no-empty-interface
114115
export interface PostAgentUnenrollResponse {}
115116

117+
export interface PostBulkAgentUnenrollRequest {
118+
body: {
119+
agents: string[] | string;
120+
force?: boolean;
121+
};
122+
}
123+
124+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
125+
export interface PostBulkAgentUnenrollResponse {}
126+
116127
export interface PostAgentUpgradeRequest {
117128
params: {
118129
agentId: string;
119130
};
131+
body: {
132+
source_uri?: string;
133+
version: string;
134+
};
120135
}
121-
export interface PostBulkAgentUnenrollRequest {
136+
137+
export interface PostBulkAgentUpgradeRequest {
122138
body: {
123139
agents: string[] | string;
124-
force?: boolean;
140+
source_uri?: string;
141+
version: string;
125142
};
126143
}
144+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
145+
export interface PostBulkAgentUpgradeResponse {}
127146

128147
// eslint-disable-next-line @typescript-eslint/no-empty-interface
129148
export interface PostAgentUpgradeResponse {}
130-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
131-
export interface PostBulkAgentUnenrollResponse {}
132149

133150
export interface PutAgentReassignRequest {
134151
params: {

x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
export { useCapabilities } from './use_capabilities';
88
export { useCore } from './use_core';
99
export { useConfig, ConfigContext } from './use_config';
10+
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
1011
export { useSetupDeps, useStartDeps, DepsContext } from './use_deps';
1112
export { licenseService, useLicense } from './use_license';
1213
export { useBreadcrumbs } from './use_breadcrumbs';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { useContext } from 'react';
8+
9+
export const KibanaVersionContext = React.createContext<string | null>(null);
10+
11+
export function useKibanaVersion() {
12+
const version = useContext(KibanaVersionContext);
13+
if (version === null) {
14+
throw new Error('KibanaVersionContext is not initialized');
15+
}
16+
return version;
17+
}

0 commit comments

Comments
 (0)