From 0ec98c2fc284e445d8fae79a73489368adcddc17 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 30 Sep 2022 13:12:58 +0200 Subject: [PATCH 01/24] request diagnostics action API --- .../plugins/fleet/common/constants/routes.ts | 1 + .../fleet/common/types/models/agent.ts | 3 +- .../fleet/server/routes/agent/index.ts | 13 +++++++++ .../agent/request_diagnostics_handler.ts | 29 +++++++++++++++++++ .../fleet/server/services/agents/index.ts | 1 + .../services/agents/request_diagnostics.ts | 18 ++++++++++++ .../fleet/server/types/rest_spec/agent.ts | 6 ++++ 7 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts create mode 100644 x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index fa9e074899163..fd7d350f0eacb 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -118,6 +118,7 @@ export const AGENT_API_ROUTES = { BULK_UNENROLL_PATTERN: `${API_ROOT}/agents/bulk_unenroll`, REASSIGN_PATTERN: `${API_ROOT}/agents/{agentId}/reassign`, BULK_REASSIGN_PATTERN: `${API_ROOT}/agents/bulk_reassign`, + REQUEST_DIAGNOSTICS_PATTERN: `${API_ROOT}/agents/{agentId}/request_diagnostics`, AVAILABLE_VERSIONS_PATTERN: `${API_ROOT}/agents/available_versions`, STATUS_PATTERN: `${API_ROOT}/agent_status`, DATA_PATTERN: `${API_ROOT}/agent_status/data`, diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index fa54f8c943e27..55c3da175b481 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -37,7 +37,8 @@ export type AgentActionType = | 'POLICY_REASSIGN' | 'CANCEL' | 'FORCE_UNENROLL' - | 'UPDATE_TAGS'; + | 'UPDATE_TAGS' + | 'REQUEST_DIAGNOSTICS'; type FleetServerAgentComponentStatusTuple = typeof FleetServerAgentComponentStatuses; export type FleetServerAgentComponentStatus = FleetServerAgentComponentStatusTuple[number]; diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index a6ee368a775ed..c6d52dcf65a2c 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -23,6 +23,7 @@ import { PostBulkAgentUpgradeRequestSchema, PostCancelActionRequestSchema, GetActionStatusRequestSchema, + PostRequestDiagnosticsActionRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; import type { FleetConfigType } from '../..'; @@ -54,6 +55,7 @@ import { postAgentUpgradeHandler, postBulkAgentsUpgradeHandler, } from './upgrade_handler'; +import { requestDiagnosticsHandler } from './request_diagnostics_handler'; export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => { // Get one @@ -178,6 +180,17 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT putAgentsReassignHandler ); + router.post( + { + path: AGENT_API_ROUTES.REQUEST_DIAGNOSTICS_PATTERN, + validate: PostRequestDiagnosticsActionRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + requestDiagnosticsHandler + ); + // Get agent status for policy router.get( { diff --git a/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts b/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts new file mode 100644 index 0000000000000..46f5d354efc17 --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts @@ -0,0 +1,29 @@ +/* + * 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 type { RequestHandler } from '@kbn/core/server'; +import type { TypeOf } from '@kbn/config-schema'; + +import * as AgentService from '../../services/agents'; +import type { PostRequestDiagnosticsActionRequestSchema } from '../../types'; +import { defaultFleetErrorHandler } from '../../errors'; + +export const requestDiagnosticsHandler: RequestHandler< + TypeOf, + undefined, + undefined +> = async (context, request, response) => { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { + await AgentService.requestDiagnostics(esClient, request.params.agentId); + + return response.ok({ body: {} }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/services/agents/index.ts b/x-pack/plugins/fleet/server/services/agents/index.ts index 302790cf6ae6d..d2c7b936a554a 100644 --- a/x-pack/plugins/fleet/server/services/agents/index.ts +++ b/x-pack/plugins/fleet/server/services/agents/index.ts @@ -15,6 +15,7 @@ export * from './reassign'; export * from './setup'; export * from './update_agent_tags'; export * from './action_status'; +export * from './request_diagnostics'; export { AgentServiceImpl } from './agent_service'; export type { AgentClient, AgentService } from './agent_service'; export { BulkActionsResolver } from './bulk_actions_resolver'; diff --git a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts new file mode 100644 index 0000000000000..196475c668a67 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts @@ -0,0 +1,18 @@ +/* + * 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 type { ElasticsearchClient } from '@kbn/core/server'; + +import { createAgentAction } from './actions'; + +export async function requestDiagnostics(esClient: ElasticsearchClient, agentId: string) { + await createAgentAction(esClient, { + agents: [agentId], + created_at: new Date().toISOString(), + type: 'REQUEST_DIAGNOSTICS', + }); +} diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index d87031cdad9d6..1caf813e5cf2a 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -113,6 +113,12 @@ export const PutAgentReassignRequestSchema = { }), }; +export const PostRequestDiagnosticsActionRequestSchema = { + params: schema.object({ + agentId: schema.string(), + }), +}; + export const PostBulkAgentReassignRequestSchema = { body: schema.object({ policy_id: schema.string(), From af2e1d6340c36f2a61c9d3e9a69b83929fa5df37 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 30 Sep 2022 15:28:42 +0200 Subject: [PATCH 02/24] agent diagnostics action UI --- .../components/actions_menu.tsx | 13 ++++++++++++- .../components/agent_diagnostics/index.tsx | 16 ++++++++++++++++ .../agent_details_page/components/index.ts | 1 + .../sections/agents/agent_details_page/index.tsx | 15 +++++++++++++++ .../components/table_row_actions.tsx | 10 ++++++++++ .../plugins/fleet/public/constants/page_paths.ts | 3 +++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index c57347894003d..5c66f50ad750f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -10,7 +10,7 @@ import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { Agent, AgentPolicy } from '../../../../types'; -import { useAuthz, useKibanaVersion } from '../../../../hooks'; +import { useAuthz, useKibanaVersion, useLink } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollAgentModal, @@ -33,6 +33,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; + const { getHref } = useLink(); const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy); @@ -132,6 +133,16 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ defaultMessage="Upgrade agent" /> , + + + , ]} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx new file mode 100644 index 0000000000000..fc1a459042ae2 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -0,0 +1,16 @@ +/* + * 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 React from 'react'; + +export interface AgentDiagnosticsProps { + agentId: string; +} + +export const AgentDiagnostics: React.FunctionComponent = ({ agentId }) => { + return
diagnostics
; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts index 5a806d52d3af9..2beb21b31af5b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts @@ -9,3 +9,4 @@ export { AgentLogs } from './agent_logs'; export { AgentDetailsActionMenu } from './actions_menu'; export { AgentDetailsContent } from './agent_details'; export { AgentDashboardLink } from './agent_dashboard_link'; +export { AgentDiagnostics } from './agent_diagnostics'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index 5654f4a18d1d3..cdaafbca64000 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -31,6 +31,7 @@ import { AgentDetailsActionMenu, AgentDetailsContent, AgentDashboardLink, + AgentDiagnostics, } from './components'; export const AgentDetailsPage: React.FunctionComponent = () => { @@ -151,6 +152,14 @@ export const AgentDetailsPage: React.FunctionComponent = () => { href: getHref('agent_details_logs', { agentId, tabId: 'logs' }), isSelected: tabId === 'logs', }, + { + id: 'diagnostics', + name: i18n.translate('xpack.fleet.agentDetails.subTabs.diagnosticsTab', { + defaultMessage: 'Diagnostics', + }), + href: getHref('agent_details_diagnostics', { agentId, tabId: 'diagnostics' }), + isSelected: tabId === 'diagnostics', + }, ]; }, [getHref, agentId, tabId]); @@ -222,6 +231,12 @@ const AgentDetailsPageContent: React.FunctionComponent<{ return ; }} /> + { + return ; + }} + /> { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index 901d03ab7e371..7f13dd8ff2550 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -103,6 +103,16 @@ export const TableRowActions: React.FunctionComponent<{ id="xpack.fleet.agentList.upgradeOneButton" defaultMessage="Upgrade agent" /> + , + + ); } diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index fa9d6c1cec21c..776a46184f246 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -41,6 +41,7 @@ export type DynamicPage = | 'agent_list' | 'agent_details' | 'agent_details_logs' + | 'agent_details_diagnostics' | 'settings_edit_outputs' | 'settings_edit_download_sources'; @@ -60,6 +61,7 @@ export const FLEET_ROUTING_PATHS = { agents: '/agents', agent_details: '/agents/:agentId/:tabId?', agent_details_logs: '/agents/:agentId/logs', + agent_details_diagnostics: '/agents/:agentId/diagnostics', policies: '/policies', policies_list: '/policies', policy_details: '/policies/:policyId/:tabId?', @@ -197,6 +199,7 @@ export const pagePathGetters: { `/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`, ], agent_details_logs: ({ agentId }) => [FLEET_BASE_PATH, `/agents/${agentId}/logs`], + agent_details_diagnostics: ({ agentId }) => [FLEET_BASE_PATH, `/agents/${agentId}/diagnostics`], enrollment_tokens: () => [FLEET_BASE_PATH, '/enrollment-tokens'], data_streams: () => [FLEET_BASE_PATH, '/data-streams'], settings: () => [FLEET_BASE_PATH, FLEET_ROUTING_PATHS.settings], From 7a47627c06248bca131e2a63a26c247653d574aa Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Mon, 3 Oct 2022 13:43:29 +0200 Subject: [PATCH 03/24] call diagnostics api when clicking button, added modal --- .../plugins/fleet/common/services/routes.ts | 2 + .../fleet/common/types/rest_spec/agent.ts | 3 + .../components/actions_menu.tsx | 20 ++- .../components/table_row_actions.tsx | 6 +- .../sections/agents/agent_list_page/index.tsx | 17 +++ .../agent_request_diagnostics_modal/index.tsx | 117 ++++++++++++++++++ .../fleet/public/hooks/use_request/agents.ts | 9 ++ 7 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_request_diagnostics_modal/index.tsx diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index c2f76758c3d7b..380254dbcc2ec 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -205,6 +205,8 @@ export const agentRouteService = { AGENT_API_ROUTES.ACTIONS_PATTERN.replace('{agentId}', agentId), getListTagsPath: () => AGENT_API_ROUTES.LIST_TAGS_PATTERN, getAvailableVersionsPath: () => AGENT_API_ROUTES.AVAILABLE_VERSIONS_PATTERN, + getRequestDiagnosticsPath: (agentId: string) => + AGENT_API_ROUTES.REQUEST_DIAGNOSTICS_PATTERN.replace('{agentId}', agentId), }; export const outputRoutesService = { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 9a18613d95834..75879dab9d3c6 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -121,6 +121,9 @@ export interface PostBulkAgentReassignRequest { }; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PutRequestDiagnosticsResponse {} + export type PostBulkAgentReassignResponse = BulkAgentAction; export type PostBulkUpdateAgentTagsResponse = BulkAgentAction; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index 5c66f50ad750f..12f8c59df057d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -10,7 +10,7 @@ import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { Agent, AgentPolicy } from '../../../../types'; -import { useAuthz, useKibanaVersion, useLink } from '../../../../hooks'; +import { useAuthz, useKibanaVersion } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollAgentModal, @@ -19,6 +19,7 @@ import { } from '../../components'; import { useAgentRefresh } from '../hooks'; import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services'; +import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; @@ -32,8 +33,8 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault); const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); + const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; - const { getHref } = useLink(); const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy); @@ -78,6 +79,17 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ /> )} + {isRequestDiagnosticsModalOpen && ( + + { + setIsRequestDiagnosticsModalOpen(false); + }} + /> + + )} { + setIsRequestDiagnosticsModalOpen(true); + }} > void; onUpgradeClick: () => void; onAddRemoveTagsClick: (button: HTMLElement) => void; + onRequestDiagnosticsClick: () => void; }> = ({ agent, agentPolicy, @@ -28,6 +29,7 @@ export const TableRowActions: React.FunctionComponent<{ onUnenrollClick, onUpgradeClick, onAddRemoveTagsClick, + onRequestDiagnosticsClick, }) => { const { getHref } = useLink(); const hasFleetAllPrivileges = useAuthz().fleet.all; @@ -107,7 +109,9 @@ export const TableRowActions: React.FunctionComponent<{ { + onRequestDiagnosticsClick(); + }} > = () => { const [agentToAddRemoveTags, setAgentToAddRemoveTags] = useState(undefined); const [tagsPopoverButton, setTagsPopoverButton] = useState(); const [showTagsAddRemove, setShowTagsAddRemove] = useState(false); + const [agentToRequestDiagnostics, setAgentToRequestDiagnostics] = useState( + undefined + ); // Kuery const kuery = useMemo(() => { @@ -538,6 +543,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { setAgentToAddRemoveTags(agent); setShowTagsAddRemove(!showTagsAddRemove); }} + onRequestDiagnosticsClick={() => setAgentToRequestDiagnostics(agent)} /> ); }, @@ -612,6 +618,17 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> )} + {agentToRequestDiagnostics && ( + + { + setAgentToRequestDiagnostics(undefined); + }} + /> + + )} {showTagsAddRemove && ( void; + agents: Agent[] | string; + agentCount: number; +} + +export const AgentRequestDiagnosticsModal: React.FunctionComponent = ({ + onClose, + agents, + agentCount, +}) => { + const { notifications } = useStartServices(); + const [isSubmitting, setIsSubmitting] = useState(false); + const isSingleAgent = Array.isArray(agents) && agents.length === 1; + const { getPath } = useLink(); + const history = useHistory(); + + async function onSubmit() { + try { + setIsSubmitting(true); + const { error } = await sendPostRequestDiagnostics((agents[0] as Agent).id); + if (error) { + throw error; + } + setIsSubmitting(false); + const successMessage = i18n.translate( + 'xpack.fleet.requestDiagnostics.successSingleNotificationTitle', + { + defaultMessage: 'Request diagnostics submitted', + } + ); + notifications.toasts.addSuccess(successMessage); + + if (isSingleAgent) { + const path = getPath('agent_details_diagnostics', { agentId: (agents[0] as Agent).id }); + history.push(path); + } + onClose(); + } catch (error) { + setIsSubmitting(false); + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.requestDiagnostics.fatalErrorNotificationTitle', { + defaultMessage: + 'Error requesting diagnostics {count, plural, one {agent} other {agents}}', + values: { count: agentCount }, + }), + }); + } + } + + return ( + + ) : ( + + ) + } + onCancel={onClose} + onConfirm={onSubmit} + cancelButtonText={ + + } + confirmButtonDisabled={isSubmitting} + confirmButtonText={ + isSingleAgent ? ( + + ) : ( + + ) + } + buttonColor="primary" + > +

+ +

+
+ ); +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts index 13f687e321e54..86fc9e4a523c0 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts @@ -9,6 +9,7 @@ import type { GetActionStatusResponse, GetAgentTagsResponse, PostBulkUpdateAgentTagsRequest, + PutRequestDiagnosticsResponse, UpdateAgentRequest, } from '../../../common/types'; @@ -171,6 +172,14 @@ export function sendPostAgentUpgrade( }); } +export function sendPostRequestDiagnostics(agentId: string, options?: RequestOptions) { + return sendRequest({ + path: agentRouteService.getRequestDiagnosticsPath(agentId), + method: 'post', + ...options, + }); +} + export function sendPostAgentAction( agentId: string, body: PostNewAgentActionRequest['body'], From 03e6ab6c8e252385e86c9cac50d90bc529f22e27 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Tue, 4 Oct 2022 14:14:20 +0200 Subject: [PATCH 04/24] showing diagnostics uploads list with mock data --- .../plugins/fleet/common/constants/routes.ts | 1 + .../plugins/fleet/common/services/routes.ts | 2 + .../fleet/common/types/models/agent.ts | 8 + .../fleet/common/types/rest_spec/agent.ts | 13 +- .../components/agent_diagnostics/index.tsx | 182 +++++++++++++++++- .../agent_details_page/components/index.ts | 2 +- .../agents/agent_details_page/index.tsx | 4 +- .../fleet/public/hooks/use_request/agents.ts | 17 ++ .../fleet/server/routes/agent/handlers.ts | 15 ++ .../fleet/server/routes/agent/index.ts | 13 ++ .../fleet/server/services/agents/index.ts | 1 + .../fleet/server/services/agents/uploads.ts | 38 ++++ .../fleet/server/types/rest_spec/agent.ts | 6 + 13 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/agents/uploads.ts diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index fd7d350f0eacb..9ac8d8c2e8091 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -129,6 +129,7 @@ export const AGENT_API_ROUTES = { CURRENT_UPGRADES_PATTERN: `${API_ROOT}/agents/current_upgrades`, ACTION_STATUS_PATTERN: `${API_ROOT}/agents/action_status`, LIST_TAGS_PATTERN: `${API_ROOT}/agents/tags`, + LIST_UPLOADS_PATTERN: `${API_ROOT}/agents/{agentId}/uploads`, }; export const ENROLLMENT_API_KEY_ROUTES = { diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 380254dbcc2ec..161aeb04c0a45 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -207,6 +207,8 @@ export const agentRouteService = { getAvailableVersionsPath: () => AGENT_API_ROUTES.AVAILABLE_VERSIONS_PATTERN, getRequestDiagnosticsPath: (agentId: string) => AGENT_API_ROUTES.REQUEST_DIAGNOSTICS_PATTERN.replace('{agentId}', agentId), + getListAgentUploads: (agentId: string) => + AGENT_API_ROUTES.LIST_UPLOADS_PATTERN.replace('{agentId}', agentId), }; export const outputRoutesService = { diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 55c3da175b481..4fe3b1e2601e3 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -135,6 +135,14 @@ export interface ActionStatus { creationTime: string; } +export interface AgentDiagnostics { + id: string; + name: string; + createTime: string; + filePath: string; + status: 'READY' | 'AWAITING_UPLOAD' | 'DELETED'; +} + // Generated from FleetServer schema.json export interface FleetServerAgentComponentUnit { id: string; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 75879dab9d3c6..77ee99a59fac9 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -7,7 +7,14 @@ import type { SearchHit } from '@kbn/es-types'; -import type { Agent, AgentAction, ActionStatus, CurrentUpgrade, NewAgentAction } from '../models'; +import type { + Agent, + AgentAction, + ActionStatus, + CurrentUpgrade, + NewAgentAction, + AgentDiagnostics, +} from '../models'; import type { ListResult, ListWithKuery } from './common'; @@ -38,6 +45,10 @@ export interface GetOneAgentResponse { item: Agent; } +export interface GetAgentUploadsResponse { + items: AgentDiagnostics[]; +} + export interface PostNewAgentActionRequest { body: { action: Omit; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index fc1a459042ae2..f75bf23f390c9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -5,12 +5,186 @@ * 2.0. */ -import React from 'react'; +import type { EuiTableFieldDataColumnType } from '@elastic/eui'; +import { + EuiBasicTable, + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiLoadingContent, + EuiLoadingSpinner, + EuiText, + formatDate, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { i18n } from '@kbn/i18n'; + +import { + sendGetAgentUploads, + sendPostRequestDiagnostics, + useStartServices, +} from '../../../../../hooks'; +import type { AgentDiagnostics, Agent } from '../../../../../../../../common/types/models'; + +const FlexStartEuiFlexItem = styled(EuiFlexItem)` + align-self: flex-start; +`; export interface AgentDiagnosticsProps { - agentId: string; + agent: Agent; } -export const AgentDiagnostics: React.FunctionComponent = ({ agentId }) => { - return
diagnostics
; +export const AgentDiagnosticsTab: React.FunctionComponent = ({ agent }) => { + const [currentDiagnostics, setCurrentDiagnostics] = useState([]); + const { notifications } = useStartServices(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + const loadData = useCallback(async () => { + try { + const { data, error } = await sendGetAgentUploads(agent.id); + if (error) { + throw error; + } + if (!data) { + throw new Error('No data'); + } + setCurrentDiagnostics(data.items); + setIsLoading(false); + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate( + 'xpack.fleet.requestDiagnostics.errorLoadingUploadsNotificationTitle', + { + defaultMessage: 'Error loading diagnostics uploads', + } + ), + }); + } + }, [agent.id, notifications.toasts]); + + useEffect(() => { + loadData(); + const interval: ReturnType | null = setInterval(async () => { + loadData(); + }, 10000); + + const cleanup = () => { + if (interval) { + clearInterval(interval); + } + }; + + return cleanup; + }, [loadData]); + + const columns: Array> = [ + { + field: 'id', + name: 'File', + render: (id: string) => { + const currentItem = currentDiagnostics.find((item) => item.id === id); + return currentItem?.status === 'READY' ? ( + +   {currentItem?.name} + + ) : ( + +  {' '} + + + ); + }, + }, + { + field: 'id', + name: 'Date', + dataType: 'date', + render: (id: string) => { + const currentItem = currentDiagnostics.find((item) => item.id === id); + return ( + + {formatDate(currentItem?.createTime, 'll')} + + ); + }, + }, + ]; + + async function onSubmit() { + try { + setIsSubmitting(true); + const { error } = await sendPostRequestDiagnostics(agent.id); + if (error) { + throw error; + } + setIsSubmitting(false); + const successMessage = i18n.translate( + 'xpack.fleet.requestDiagnostics.successSingleNotificationTitle', + { + defaultMessage: 'Request diagnostics submitted', + } + ); + notifications.toasts.addSuccess(successMessage); + } catch (error) { + setIsSubmitting(false); + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.requestDiagnostics.fatalErrorNotificationTitle', { + defaultMessage: + 'Error requesting diagnostics {count, plural, one {agent} other {agents}}', + values: { count: 1 }, + }), + }); + } + } + + return ( + + + + } + > + + + + + + + + + + {isLoading ? ( + + ) : ( + + items={currentDiagnostics} + rowHeader="firstName" + columns={columns} + /> + )} + + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts index 2beb21b31af5b..858bb31033716 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts @@ -9,4 +9,4 @@ export { AgentLogs } from './agent_logs'; export { AgentDetailsActionMenu } from './actions_menu'; export { AgentDetailsContent } from './agent_details'; export { AgentDashboardLink } from './agent_dashboard_link'; -export { AgentDiagnostics } from './agent_diagnostics'; +export { AgentDiagnosticsTab } from './agent_diagnostics'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index cdaafbca64000..f91e2a70dd4d1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -31,7 +31,7 @@ import { AgentDetailsActionMenu, AgentDetailsContent, AgentDashboardLink, - AgentDiagnostics, + AgentDiagnosticsTab, } from './components'; export const AgentDetailsPage: React.FunctionComponent = () => { @@ -234,7 +234,7 @@ const AgentDetailsPageContent: React.FunctionComponent<{ { - return ; + return ; }} /> ({ + path: agentRouteService.getListAgentUploads(agentId), + method: 'get', + ...options, + }); +} + +export const useGetAgentUploads = (agentId: string, options?: RequestOptions) => { + return useRequest({ + path: agentRouteService.getListAgentUploads(agentId), + method: 'get', + ...options, + }); +}; + export function sendPostAgentAction( agentId: string, body: PostNewAgentActionRequest['body'], diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 86f202381119f..145d52ef0c0f8 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -28,6 +28,7 @@ import type { GetAgentTagsResponse, GetAvailableVersionsResponse, GetActionStatusResponse, + GetAgentUploadsResponse, } from '../../../common/types'; import type { GetAgentsRequestSchema, @@ -362,3 +363,17 @@ export const getActionStatusHandler: RequestHandler< return defaultFleetErrorHandler({ error, response }); } }; + +export const getAgentUploadsHandler: RequestHandler< + TypeOf +> = async (context, request, response) => { + try { + const body: GetAgentUploadsResponse = { + items: await AgentService.getAgentUploads(request.params.agentId), + }; + + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index c6d52dcf65a2c..e9f3ef513b8d9 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -24,6 +24,7 @@ import { PostCancelActionRequestSchema, GetActionStatusRequestSchema, PostRequestDiagnosticsActionRequestSchema, + ListAgentUploadsRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; import type { FleetConfigType } from '../..'; @@ -44,6 +45,7 @@ import { bulkUpdateAgentTagsHandler, getAvailableVersionsHandler, getActionStatusHandler, + getAgentUploadsHandler, } from './handlers'; import { postNewAgentActionHandlerBuilder, @@ -191,6 +193,17 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT requestDiagnosticsHandler ); + router.get( + { + path: AGENT_API_ROUTES.LIST_UPLOADS_PATTERN, + validate: ListAgentUploadsRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + getAgentUploadsHandler + ); + // Get agent status for policy router.get( { diff --git a/x-pack/plugins/fleet/server/services/agents/index.ts b/x-pack/plugins/fleet/server/services/agents/index.ts index d2c7b936a554a..6fdb0e6f05795 100644 --- a/x-pack/plugins/fleet/server/services/agents/index.ts +++ b/x-pack/plugins/fleet/server/services/agents/index.ts @@ -16,6 +16,7 @@ export * from './setup'; export * from './update_agent_tags'; export * from './action_status'; export * from './request_diagnostics'; +export { getAgentUploads } from './uploads'; export { AgentServiceImpl } from './agent_service'; export type { AgentClient, AgentService } from './agent_service'; export { BulkActionsResolver } from './bulk_actions_resolver'; diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts new file mode 100644 index 0000000000000..7e3f80136ab76 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -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 type { AgentDiagnostics } from '../../../common/types/models'; + +export async function getAgentUploads(agentId: string): Promise { + // const fileClient = createEsFileClient({ + // blobStorageIndex: '.elastic-agent-blob', + // metadataIndex: '.elastic-agent-metadata', + // elasticsearchClient: esClient, + // logger: appContextService.getLogger(), + // }); + + // const results = await fileClient.find({ + // status: ['READY'], + // meta: { agentId }, + // }); + return Promise.resolve([ + { + id: agentId + '-diagnostics-1', + name: '2022-10-04 10:00:00.zip', + createTime: '2022-10-04T10:00:00.000Z', + filePath: '/api/files/files/agent-diagnostics-1/blob/2022-10-04 10:00:00.zip', + status: 'AWAITING_UPLOAD', + }, + { + id: agentId + '-diagnostics-2', + name: '2022-10-04 11:00:00.zip', + createTime: '2022-10-04T11:00:00.000Z', + filePath: '/api/files/files/agent-diagnostics-2/blob/2022-10-04 11:00:00.zip', + status: 'READY', + }, + ]); +} diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 1caf813e5cf2a..ed8a6c6f90067 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -119,6 +119,12 @@ export const PostRequestDiagnosticsActionRequestSchema = { }), }; +export const ListAgentUploadsRequestSchema = { + params: schema.object({ + agentId: schema.string(), + }), +}; + export const PostBulkAgentReassignRequestSchema = { body: schema.object({ policy_id: schema.string(), From 478c3d5ecd13c6a8a3ad7ee4e3722fc72c322be3 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 5 Oct 2022 11:18:08 +0200 Subject: [PATCH 05/24] bulk request diagnostics --- .../plugins/fleet/common/constants/routes.ts | 1 + .../plugins/fleet/common/services/routes.ts | 1 + .../fleet/common/types/rest_spec/agent.ts | 11 +++- .../components/agent_activity_flyout.tsx | 5 ++ .../components/bulk_actions.tsx | 33 +++++++++++ .../agent_request_diagnostics_modal/index.tsx | 14 ++++- .../fleet/public/hooks/use_request/agents.ts | 18 +++++- .../fleet/server/routes/agent/index.ts | 17 +++++- .../agent/request_diagnostics_handler.ts | 32 ++++++++++- .../services/agents/bulk_actions_resolver.ts | 3 + .../services/agents/request_diagnostics.ts | 55 +++++++++++++++++- .../request_diagnostics_action_runner.ts | 57 +++++++++++++++++++ .../fleet/server/types/rest_spec/agent.ts | 7 +++ 13 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/agents/request_diagnostics_action_runner.ts diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 9ac8d8c2e8091..fa62bb0117ee7 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -119,6 +119,7 @@ export const AGENT_API_ROUTES = { REASSIGN_PATTERN: `${API_ROOT}/agents/{agentId}/reassign`, BULK_REASSIGN_PATTERN: `${API_ROOT}/agents/bulk_reassign`, REQUEST_DIAGNOSTICS_PATTERN: `${API_ROOT}/agents/{agentId}/request_diagnostics`, + BULK_REQUEST_DIAGNOSTICS_PATTERN: `${API_ROOT}/agents/bulk_request_diagnostics`, AVAILABLE_VERSIONS_PATTERN: `${API_ROOT}/agents/available_versions`, STATUS_PATTERN: `${API_ROOT}/agent_status`, DATA_PATTERN: `${API_ROOT}/agent_status/data`, diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 161aeb04c0a45..a5ae109240600 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -207,6 +207,7 @@ export const agentRouteService = { getAvailableVersionsPath: () => AGENT_API_ROUTES.AVAILABLE_VERSIONS_PATTERN, getRequestDiagnosticsPath: (agentId: string) => AGENT_API_ROUTES.REQUEST_DIAGNOSTICS_PATTERN.replace('{agentId}', agentId), + getBulkRequestDiagnosticsPath: () => AGENT_API_ROUTES.BULK_REQUEST_DIAGNOSTICS_PATTERN, getListAgentUploads: (agentId: string) => AGENT_API_ROUTES.LIST_UPLOADS_PATTERN.replace('{agentId}', agentId), }; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 77ee99a59fac9..8af5ee9abe1e3 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -132,8 +132,15 @@ export interface PostBulkAgentReassignRequest { }; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface PutRequestDiagnosticsResponse {} +export type PostRequestDiagnosticsResponse = BulkAgentAction; +export type PostBulkRequestDiagnosticsResponse = BulkAgentAction; + +export interface PostRequestBulkDiagnosticsRequest { + body: { + agents: string[] | string; + batchSize?: number; + }; +} export type PostBulkAgentReassignResponse = BulkAgentAction; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index dfa6623f4a90a..52aa84fe8f521 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -254,6 +254,11 @@ const actionNames: { cancelledText: 'update tags', }, CANCEL: { inProgressText: 'Cancelling', completedText: 'cancelled', cancelledText: '' }, + REQUEST_DIAGNOSTICS: { + inProgressText: 'Requesting diagnostics for', + completedText: 'requested diagnostics', + cancelledText: 'request diagnostics', + }, ACTION: { inProgressText: 'Actioning', completedText: 'actioned', cancelledText: 'action' }, }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index 356753a0d0045..e55451bdb9791 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -28,6 +28,8 @@ import { LICENSE_FOR_SCHEDULE_UPGRADE } from '../../../../../../../common/consta import { getCommonTags } from '../utils'; +import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal'; + import type { SelectionMode } from './types'; import { TagsAddRemove } from './tags_add_remove'; @@ -67,6 +69,8 @@ export const AgentBulkActions: React.FunctionComponent = ({ const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const [updateModalState, setUpgradeModalState] = useState({ isOpen: false, isScheduled: false }); const [isTagAddVisible, setIsTagAddVisible] = useState(false); + const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = + useState(false); // Check if user is working with only inactive agents const atLeastOneActiveAgentSelected = @@ -166,6 +170,24 @@ export const AgentBulkActions: React.FunctionComponent = ({ setUpgradeModalState({ isOpen: true, isScheduled: true }); }, }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsRequestDiagnosticsModalOpen(true); + }, + }, ], }, ]; @@ -228,6 +250,17 @@ export const AgentBulkActions: React.FunctionComponent = ({ }} /> )} + {isRequestDiagnosticsModalOpen && ( + + { + setIsRequestDiagnosticsModalOpen(false); + }} + /> + + )} void; @@ -34,7 +39,12 @@ export const AgentRequestDiagnosticsModal: React.FunctionComponent = ({ async function onSubmit() { try { setIsSubmitting(true); - const { error } = await sendPostRequestDiagnostics((agents[0] as Agent).id); + + const { error } = isSingleAgent + ? await sendPostRequestDiagnostics((agents[0] as Agent).id) + : await sendPostBulkRequestDiagnostics({ + agents: typeof agents === 'string' ? agents : agents.map((agent) => agent.id), + }); if (error) { throw error; } diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts index 461c8ea2d741e..0ebb99508bd84 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts @@ -9,8 +9,10 @@ import type { GetActionStatusResponse, GetAgentTagsResponse, GetAgentUploadsResponse, + PostBulkRequestDiagnosticsResponse, PostBulkUpdateAgentTagsRequest, - PutRequestDiagnosticsResponse, + PostRequestBulkDiagnosticsRequest, + PostRequestDiagnosticsResponse, UpdateAgentRequest, } from '../../../common/types'; @@ -174,13 +176,25 @@ export function sendPostAgentUpgrade( } export function sendPostRequestDiagnostics(agentId: string, options?: RequestOptions) { - return sendRequest({ + return sendRequest({ path: agentRouteService.getRequestDiagnosticsPath(agentId), method: 'post', ...options, }); } +export function sendPostBulkRequestDiagnostics( + body: PostRequestBulkDiagnosticsRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + path: agentRouteService.getBulkRequestDiagnosticsPath(), + method: 'post', + body, + ...options, + }); +} + export function sendGetAgentUploads(agentId: string, options?: RequestOptions) { return sendRequest({ path: agentRouteService.getListAgentUploads(agentId), diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index e9f3ef513b8d9..224c5f7ee8f06 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -24,6 +24,7 @@ import { PostCancelActionRequestSchema, GetActionStatusRequestSchema, PostRequestDiagnosticsActionRequestSchema, + PostBulkRequestDiagnosticsActionRequestSchema, ListAgentUploadsRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; @@ -57,7 +58,10 @@ import { postAgentUpgradeHandler, postBulkAgentsUpgradeHandler, } from './upgrade_handler'; -import { requestDiagnosticsHandler } from './request_diagnostics_handler'; +import { + bulkRequestDiagnosticsHandler, + requestDiagnosticsHandler, +} from './request_diagnostics_handler'; export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => { // Get one @@ -193,6 +197,17 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT requestDiagnosticsHandler ); + router.post( + { + path: AGENT_API_ROUTES.BULK_REQUEST_DIAGNOSTICS_PATTERN, + validate: PostBulkRequestDiagnosticsActionRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + bulkRequestDiagnosticsHandler + ); + router.get( { path: AGENT_API_ROUTES.LIST_UPLOADS_PATTERN, diff --git a/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts b/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts index 46f5d354efc17..74452b0e055c7 100644 --- a/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/request_diagnostics_handler.ts @@ -9,7 +9,10 @@ import type { RequestHandler } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; import * as AgentService from '../../services/agents'; -import type { PostRequestDiagnosticsActionRequestSchema } from '../../types'; +import type { + PostBulkRequestDiagnosticsActionRequestSchema, + PostRequestDiagnosticsActionRequestSchema, +} from '../../types'; import { defaultFleetErrorHandler } from '../../errors'; export const requestDiagnosticsHandler: RequestHandler< @@ -20,9 +23,32 @@ export const requestDiagnosticsHandler: RequestHandler< const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; try { - await AgentService.requestDiagnostics(esClient, request.params.agentId); + const result = await AgentService.requestDiagnostics(esClient, request.params.agentId); - return response.ok({ body: {} }); + return response.ok({ body: { actionId: result.actionId } }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; + +export const bulkRequestDiagnosticsHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + const soClient = coreContext.savedObjects.client; + const agentOptions = Array.isArray(request.body.agents) + ? { agentIds: request.body.agents } + : { kuery: request.body.agents }; + try { + const result = await AgentService.bulkRequestDiagnostics(esClient, soClient, { + ...agentOptions, + batchSize: request.body.batchSize, + }); + + return response.ok({ body: { actionId: result.actionId } }); } catch (error) { return defaultFleetErrorHandler({ error, response }); } diff --git a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts index 3930f96a9fa44..414bb4eb3a13e 100644 --- a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts +++ b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts @@ -20,12 +20,14 @@ import { UpgradeActionRunner } from './upgrade_action_runner'; import { UpdateAgentTagsActionRunner } from './update_agent_tags_action_runner'; import { UnenrollActionRunner } from './unenroll_action_runner'; import type { ActionParams, RetryParams } from './action_runner'; +import { RequestDiagnosticsActionRunner } from './request_diagnostics_action_runner'; export enum BulkActionTaskType { REASSIGN_RETRY = 'fleet:reassign_action:retry', UNENROLL_RETRY = 'fleet:unenroll_action:retry', UPGRADE_RETRY = 'fleet:upgrade_action:retry', UPDATE_AGENT_TAGS_RETRY = 'fleet:update_agent_tags:retry', + REQUEST_DIAGNOSTICS_RETRY = 'fleet:request_diagnostics:retry', } /** @@ -49,6 +51,7 @@ export class BulkActionsResolver { [BulkActionTaskType.REASSIGN_RETRY]: ReassignActionRunner, [BulkActionTaskType.UPDATE_AGENT_TAGS_RETRY]: UpdateAgentTagsActionRunner, [BulkActionTaskType.UPGRADE_RETRY]: UpgradeActionRunner, + [BulkActionTaskType.REQUEST_DIAGNOSTICS_RETRY]: RequestDiagnosticsActionRunner, }; return createRetryTask( diff --git a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts index 196475c668a67..efb9812180580 100644 --- a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts +++ b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.ts @@ -5,14 +5,63 @@ * 2.0. */ -import type { ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import { SO_SEARCH_LIMIT } from '../../constants'; + +import type { GetAgentsOptions } from '.'; +import { getAgents, getAgentsByKuery } from './crud'; import { createAgentAction } from './actions'; +import { openPointInTime } from './crud'; +import { + RequestDiagnosticsActionRunner, + requestDiagnosticsBatch, +} from './request_diagnostics_action_runner'; -export async function requestDiagnostics(esClient: ElasticsearchClient, agentId: string) { - await createAgentAction(esClient, { +export async function requestDiagnostics( + esClient: ElasticsearchClient, + agentId: string +): Promise<{ actionId: string }> { + const response = await createAgentAction(esClient, { agents: [agentId], created_at: new Date().toISOString(), type: 'REQUEST_DIAGNOSTICS', }); + return { actionId: response.id }; +} + +export async function bulkRequestDiagnostics( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, + options: GetAgentsOptions & { + batchSize?: number; + } +): Promise<{ actionId: string }> { + if ('agentIds' in options) { + const givenAgents = await getAgents(esClient, options); + return await requestDiagnosticsBatch(esClient, givenAgents, {}); + } + + const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; + const res = await getAgentsByKuery(esClient, { + kuery: options.kuery, + showInactive: false, + page: 1, + perPage: batchSize, + }); + if (res.total <= batchSize) { + const givenAgents = await getAgents(esClient, options); + return await requestDiagnosticsBatch(esClient, givenAgents, {}); + } else { + return await new RequestDiagnosticsActionRunner( + esClient, + soClient, + { + ...options, + batchSize, + total: res.total, + }, + { pitId: await openPointInTime(esClient) } + ).runActionAsyncWithRetry(); + } } diff --git a/x-pack/plugins/fleet/server/services/agents/request_diagnostics_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/request_diagnostics_action_runner.ts new file mode 100644 index 0000000000000..4b3cca06d061c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/request_diagnostics_action_runner.ts @@ -0,0 +1,57 @@ +/* + * 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 uuid from 'uuid'; +import type { ElasticsearchClient } from '@kbn/core/server'; + +import type { Agent } from '../../types'; + +import { ActionRunner } from './action_runner'; +import { createAgentAction } from './actions'; +import { BulkActionTaskType } from './bulk_actions_resolver'; + +export class RequestDiagnosticsActionRunner extends ActionRunner { + protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { + return await requestDiagnosticsBatch(this.esClient, agents, this.actionParams!); + } + + protected getTaskType() { + return BulkActionTaskType.REQUEST_DIAGNOSTICS_RETRY; + } + + protected getActionType() { + return 'REQUEST_DIAGNOSTICS'; + } +} + +export async function requestDiagnosticsBatch( + esClient: ElasticsearchClient, + givenAgents: Agent[], + options: { + actionId?: string; + total?: number; + } +): Promise<{ actionId: string }> { + const now = new Date().toISOString(); + + const actionId = options.actionId ?? uuid(); + const total = options.total ?? givenAgents.length; + + const agentIds = givenAgents.map((agent) => agent.id); + + await createAgentAction(esClient, { + id: actionId, + agents: agentIds, + created_at: now, + type: 'REQUEST_DIAGNOSTICS', + total, + }); + + return { + actionId, + }; +} diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index ed8a6c6f90067..301573ec83794 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -119,6 +119,13 @@ export const PostRequestDiagnosticsActionRequestSchema = { }), }; +export const PostBulkRequestDiagnosticsActionRequestSchema = { + body: schema.object({ + agents: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + batchSize: schema.maybe(schema.number()), + }), +}; + export const ListAgentUploadsRequestSchema = { params: schema.object({ agentId: schema.string(), From c55965532b9e444e936e7e3a5f79fa5ddbd64c4d Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 5 Oct 2022 14:13:03 +0200 Subject: [PATCH 06/24] query action status to show diagnostics status --- .../fleet/common/types/models/agent.ts | 1 + .../components/agent_diagnostics/index.tsx | 75 +++++++++++++++---- .../fleet/server/services/agents/uploads.ts | 6 +- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 09fe71444e36c..1569f5f6280f5 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -146,6 +146,7 @@ export interface AgentDiagnostics { createTime: string; filePath: string; status: 'READY' | 'AWAITING_UPLOAD' | 'DELETED'; + actionId: string; } // Generated from FleetServer schema.json diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index f75bf23f390c9..a19911fcb6559 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -22,15 +22,17 @@ import { import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; - +import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { sendGetAgentUploads, sendPostRequestDiagnostics, useStartServices, + sendGetActionStatus, } from '../../../../../hooks'; import type { AgentDiagnostics, Agent } from '../../../../../../../../common/types/models'; +import type { ActionStatus } from '../../../../../types'; const FlexStartEuiFlexItem = styled(EuiFlexItem)` align-self: flex-start; @@ -40,22 +42,59 @@ export interface AgentDiagnosticsProps { agent: Agent; } +export interface DiagnosticsEntry { + id: string; + name: string; + filePath?: string; + status: string; + createTime: string; +} + export const AgentDiagnosticsTab: React.FunctionComponent = ({ agent }) => { - const [currentDiagnostics, setCurrentDiagnostics] = useState([]); const { notifications } = useStartServices(); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(true); + const [diagnosticsEntries, setDiagnosticEntries] = useState([]); + + const createDiagnosticEntries = ( + currentDiagnostics: AgentDiagnostics[], + currentActions: ActionStatus[] + ) => { + const requestDiagnosticsActions = currentActions.filter( + (action) => action.type === 'REQUEST_DIAGNOSTICS' + ); + + return requestDiagnosticsActions.map((action) => { + const upload = currentDiagnostics.find((diag) => diag.actionId === action.actionId); + const fileName = + upload?.name ?? `${moment(action.creationTime).format('YYYY-MM-DD HH:mm:ss')}.zip`; + const filePath = upload?.filePath ?? `/api/files/files/${action.actionId}/blob/${fileName}`; // TODO mock value + return { + id: action.actionId, + status: action.status, + createTime: action.creationTime, + filePath, + name: fileName, + }; + }); + }; const loadData = useCallback(async () => { try { - const { data, error } = await sendGetAgentUploads(agent.id); + const [uploadsResponse, actionStatusResponse] = await Promise.all([ + sendGetAgentUploads(agent.id), + sendGetActionStatus(), + ]); + const error = uploadsResponse.error || actionStatusResponse.error; if (error) { throw error; } - if (!data) { + if (!uploadsResponse.data || !actionStatusResponse.data) { throw new Error('No data'); } - setCurrentDiagnostics(data.items); + setDiagnosticEntries( + createDiagnosticEntries(uploadsResponse.data.items, actionStatusResponse.data.items) + ); setIsLoading(false); } catch (err) { notifications.toasts.addError(err, { @@ -84,17 +123,17 @@ export const AgentDiagnosticsTab: React.FunctionComponent return cleanup; }, [loadData]); - const columns: Array> = [ + const columns: Array> = [ { field: 'id', name: 'File', render: (id: string) => { - const currentItem = currentDiagnostics.find((item) => item.id === id); - return currentItem?.status === 'READY' ? ( + const currentItem = diagnosticsEntries.find((item) => item.id === id); + return currentItem?.status === 'COMPLETE' ? (   {currentItem?.name} - ) : ( + ) : currentItem?.status === 'IN_PROGRESS' ? (  {' '} defaultMessage="Generating diagnostics file..." /> + ) : ( + +  {' '} + + ); }, }, @@ -110,9 +157,9 @@ export const AgentDiagnosticsTab: React.FunctionComponent name: 'Date', dataType: 'date', render: (id: string) => { - const currentItem = currentDiagnostics.find((item) => item.id === id); + const currentItem = diagnosticsEntries.find((item) => item.id === id); return ( - + {formatDate(currentItem?.createTime, 'll')} ); @@ -178,11 +225,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent {isLoading ? ( ) : ( - - items={currentDiagnostics} - rowHeader="firstName" - columns={columns} - /> + items={diagnosticsEntries} columns={columns} /> )} diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index 7e3f80136ab76..1cf2660ab7ce5 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -9,8 +9,8 @@ import type { AgentDiagnostics } from '../../../common/types/models'; export async function getAgentUploads(agentId: string): Promise { // const fileClient = createEsFileClient({ - // blobStorageIndex: '.elastic-agent-blob', - // metadataIndex: '.elastic-agent-metadata', + // blobStorageIndex: '.fleet-file_data', + // metadataIndex: '.fleet-files', // elasticsearchClient: esClient, // logger: appContextService.getLogger(), // }); @@ -26,6 +26,7 @@ export async function getAgentUploads(agentId: string): Promise Date: Wed, 5 Oct 2022 14:19:12 +0200 Subject: [PATCH 07/24] fix for failed status display --- .../components/agent_diagnostics/index.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index a19911fcb6559..7bd0fdfb9a47e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -135,7 +135,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent ) : currentItem?.status === 'IN_PROGRESS' ? ( -  {' '} +   ) : ( -  {' '} - +   + {currentItem?.name} ); }, From c0dcc51e1b0ca081afd71e0811a5618b3754ac25 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 5 Oct 2022 17:40:25 +0200 Subject: [PATCH 08/24] changed implementation to query files index in /uploads API --- .../fleet/common/types/models/agent.ts | 2 +- x-pack/plugins/fleet/kibana.json | 2 +- .../components/agent_diagnostics/index.tsx | 59 ++------ .../fleet/server/routes/agent/handlers.ts | 4 +- .../fleet/server/services/agents/uploads.ts | 131 ++++++++++++++---- x-pack/plugins/fleet/tsconfig.json | 3 + 6 files changed, 120 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 1569f5f6280f5..e6430caf4e360 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -145,7 +145,7 @@ export interface AgentDiagnostics { name: string; createTime: string; filePath: string; - status: 'READY' | 'AWAITING_UPLOAD' | 'DELETED'; + status: 'READY' | 'AWAITING_UPLOAD' | 'DELETED' | 'IN_PROGRESS'; actionId: string; } diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 79d8bbd40644e..66bdfe98220c3 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager", "files"], "optionalPlugins": ["features", "cloud", "usageCollection", "home", "globalSearch", "telemetry", "discover", "ingestPipelines"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "cloudChat", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"] diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index 7bd0fdfb9a47e..b7de38bc8de54 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -22,17 +22,14 @@ import { import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; -import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { sendGetAgentUploads, sendPostRequestDiagnostics, useStartServices, - sendGetActionStatus, } from '../../../../../hooks'; import type { AgentDiagnostics, Agent } from '../../../../../../../../common/types/models'; -import type { ActionStatus } from '../../../../../types'; const FlexStartEuiFlexItem = styled(EuiFlexItem)` align-self: flex-start; @@ -42,59 +39,23 @@ export interface AgentDiagnosticsProps { agent: Agent; } -export interface DiagnosticsEntry { - id: string; - name: string; - filePath?: string; - status: string; - createTime: string; -} - export const AgentDiagnosticsTab: React.FunctionComponent = ({ agent }) => { const { notifications } = useStartServices(); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(true); - const [diagnosticsEntries, setDiagnosticEntries] = useState([]); - - const createDiagnosticEntries = ( - currentDiagnostics: AgentDiagnostics[], - currentActions: ActionStatus[] - ) => { - const requestDiagnosticsActions = currentActions.filter( - (action) => action.type === 'REQUEST_DIAGNOSTICS' - ); - - return requestDiagnosticsActions.map((action) => { - const upload = currentDiagnostics.find((diag) => diag.actionId === action.actionId); - const fileName = - upload?.name ?? `${moment(action.creationTime).format('YYYY-MM-DD HH:mm:ss')}.zip`; - const filePath = upload?.filePath ?? `/api/files/files/${action.actionId}/blob/${fileName}`; // TODO mock value - return { - id: action.actionId, - status: action.status, - createTime: action.creationTime, - filePath, - name: fileName, - }; - }); - }; + const [diagnosticsEntries, setDiagnosticEntries] = useState([]); const loadData = useCallback(async () => { try { - const [uploadsResponse, actionStatusResponse] = await Promise.all([ - sendGetAgentUploads(agent.id), - sendGetActionStatus(), - ]); - const error = uploadsResponse.error || actionStatusResponse.error; + const uploadsResponse = await sendGetAgentUploads(agent.id); + const error = uploadsResponse.error; if (error) { throw error; } - if (!uploadsResponse.data || !actionStatusResponse.data) { + if (!uploadsResponse.data) { throw new Error('No data'); } - setDiagnosticEntries( - createDiagnosticEntries(uploadsResponse.data.items, actionStatusResponse.data.items) - ); + setDiagnosticEntries(uploadsResponse.data.items); setIsLoading(false); } catch (err) { notifications.toasts.addError(err, { @@ -123,17 +84,17 @@ export const AgentDiagnosticsTab: React.FunctionComponent return cleanup; }, [loadData]); - const columns: Array> = [ + const columns: Array> = [ { field: 'id', name: 'File', render: (id: string) => { const currentItem = diagnosticsEntries.find((item) => item.id === id); - return currentItem?.status === 'COMPLETE' ? ( + return currentItem?.status === 'READY' ? (   {currentItem?.name} - ) : currentItem?.status === 'IN_PROGRESS' ? ( + ) : currentItem?.status === 'IN_PROGRESS' || currentItem?.status === 'AWAITING_UPLOAD' ? (   render: (id: string) => { const currentItem = diagnosticsEntries.find((item) => item.id === id); return ( - + {formatDate(currentItem?.createTime, 'll')} ); @@ -222,7 +183,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent {isLoading ? ( ) : ( - items={diagnosticsEntries} columns={columns} /> + items={diagnosticsEntries} columns={columns} /> )} diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 145d52ef0c0f8..e7df4dce6c46f 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -367,9 +367,11 @@ export const getActionStatusHandler: RequestHandler< export const getAgentUploadsHandler: RequestHandler< TypeOf > = async (context, request, response) => { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; try { const body: GetAgentUploadsResponse = { - items: await AgentService.getAgentUploads(request.params.agentId), + items: await AgentService.getAgentUploads(esClient, request.params.agentId), }; return response.ok({ body }); diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index 1cf2660ab7ce5..c9162bce1e527 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -4,37 +4,110 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import moment from 'moment'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import { createEsFileClient } from '@kbn/files-plugin/server'; import type { AgentDiagnostics } from '../../../common/types/models'; +import { appContextService } from '../app_context'; +import { AGENT_ACTIONS_INDEX } from '../../../common'; -export async function getAgentUploads(agentId: string): Promise { - // const fileClient = createEsFileClient({ - // blobStorageIndex: '.fleet-file_data', - // metadataIndex: '.fleet-files', - // elasticsearchClient: esClient, - // logger: appContextService.getLogger(), - // }); - - // const results = await fileClient.find({ - // status: ['READY'], - // meta: { agentId }, - // }); - return Promise.resolve([ - { - id: agentId + '-diagnostics-1', - name: '2022-10-04 10:00:00.zip', - createTime: '2022-10-04T10:00:00.000Z', - filePath: '/api/files/files/agent-diagnostics-1/blob/2022-10-04 10:00:00.zip', - status: 'AWAITING_UPLOAD', - actionId: '15be4c47-c262-4d41-b86d-802affd8b56d', - }, - { - id: agentId + '-diagnostics-2', - name: '2022-10-04 11:00:00.zip', - createTime: '2022-10-04T11:00:00.000Z', - filePath: '/api/files/files/agent-diagnostics-2/blob/2022-10-04 11:00:00.zip', - status: 'READY', - actionId: 'c2cb9de7-4dd1-4909-8003-91e712828804', +import { SO_SEARCH_LIMIT } from '../../constants'; + +export async function getAgentUploads( + esClient: ElasticsearchClient, + agentId: string +): Promise { + // TODO filter agentId + const filesMetadata = await esClient.search({ + index: '.fleet-agent-files', + size: 20, + }); + const files = filesMetadata.hits.hits.map((hit) => ({ + ...(hit._source as any).file, + id: hit._id, + })); + + const actions = await _getRequestDiagnosticsActions(esClient, agentId); + + const result = actions.map((action, index) => { + let file = files.find((item) => item.action_id === action.actionId); + // TODO mock, remove when files contain actionId + if (index === actions.length - 1) { + file = files[0]; + } + const fileName = file?.name ?? `${moment(action.timestamp!).format('YYYY-MM-DD HH:mm:ss')}.zip`; + const filePath = `/api/files/files/${action.actionId}/blob/${fileName}`; // TODO mock value + return { + actionId: action.actionId, + id: file?.id ?? action.actionId, + status: file?.Status ?? 'IN_PROGRESS', + name: fileName, + createTime: action.timestamp!, + filePath, + }; + }); + + // TODO mock failed value + if (result.length > 0) { + result.push({ + ...result[0], + id: 'failed1', + status: 'FAILED', + }); + } + + return result; +} + +async function _getRequestDiagnosticsActions( + esClient: ElasticsearchClient, + agentId: string +): Promise> { + const res = await esClient.search({ + index: AGENT_ACTIONS_INDEX, + ignore_unavailable: true, + size: SO_SEARCH_LIMIT, + query: { + bool: { + must: [ + { + term: { + type: 'REQUEST_DIAGNOSTICS', + }, + }, + { + term: { + agents: agentId, + }, + }, + ], + }, }, - ]); + }); + + return res.hits.hits.map((hit) => ({ + actionId: hit._source?.action_id as string, + timestamp: hit._source?.['@timestamp'], + })); +} + +export async function getDiagnosticFile(esClient: ElasticsearchClient, id: string): Promise { + try { + const fileClient = createEsFileClient({ + blobStorageIndex: '.fleet-agent-file-data', + metadataIndex: '.fleet-agent-files', + elasticsearchClient: esClient, + logger: appContextService.getLogger(), + }); + + const results = await fileClient.get({ + id, + }); + return results; + } catch (error) { + appContextService.getLogger().error(error); + throw error; + } } diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 320843546a305..755129fffc040 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -27,6 +27,9 @@ { "path": "../licensing/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../encrypted_saved_objects/tsconfig.json" }, + { + "path": "../files/tsconfig.json" + }, // optionalPlugins from ./kibana.json { "path": "../security/tsconfig.json" }, From e795a39b216feaada03711a9a78ff7215325bef4 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 6 Oct 2022 11:10:31 +0200 Subject: [PATCH 09/24] implemented file download --- .../plugins/fleet/common/constants/routes.ts | 1 + .../plugins/fleet/common/services/routes.ts | 5 +++++ .../components/agent_diagnostics/index.tsx | 4 +++- .../fleet/server/routes/agent/handlers.ts | 20 +++++++++++++++++++ .../fleet/server/routes/agent/index.ts | 13 ++++++++++++ .../fleet/server/services/agents/index.ts | 2 +- .../fleet/server/services/agents/uploads.ts | 16 ++++++++++----- .../fleet/server/types/rest_spec/agent.ts | 7 +++++++ 8 files changed, 61 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index fa62bb0117ee7..e4e104f0324f4 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -131,6 +131,7 @@ export const AGENT_API_ROUTES = { ACTION_STATUS_PATTERN: `${API_ROOT}/agents/action_status`, LIST_TAGS_PATTERN: `${API_ROOT}/agents/tags`, LIST_UPLOADS_PATTERN: `${API_ROOT}/agents/{agentId}/uploads`, + GET_UPLOAD_FILE_PATTERN: `${API_ROOT}/agents/files/{fileId}/{fileName}`, }; export const ENROLLMENT_API_KEY_ROUTES = { diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index a5ae109240600..697f03dff9d98 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -210,6 +210,11 @@ export const agentRouteService = { getBulkRequestDiagnosticsPath: () => AGENT_API_ROUTES.BULK_REQUEST_DIAGNOSTICS_PATTERN, getListAgentUploads: (agentId: string) => AGENT_API_ROUTES.LIST_UPLOADS_PATTERN.replace('{agentId}', agentId), + getAgentFileDownloadLink: (fileId: string, fileName: string) => + AGENT_API_ROUTES.GET_UPLOAD_FILE_PATTERN.replace('{fileId}', fileId).replace( + '{fileName}', + fileName + ), }; export const outputRoutesService = { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index b7de38bc8de54..cc631bae285af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -27,6 +27,7 @@ import { i18n } from '@kbn/i18n'; import { sendGetAgentUploads, sendPostRequestDiagnostics, + useLink, useStartServices, } from '../../../../../hooks'; import type { AgentDiagnostics, Agent } from '../../../../../../../../common/types/models'; @@ -41,6 +42,7 @@ export interface AgentDiagnosticsProps { export const AgentDiagnosticsTab: React.FunctionComponent = ({ agent }) => { const { notifications } = useStartServices(); + const { getAbsolutePath } = useLink(); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(true); const [diagnosticsEntries, setDiagnosticEntries] = useState([]); @@ -91,7 +93,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent render: (id: string) => { const currentItem = diagnosticsEntries.find((item) => item.id === id); return currentItem?.status === 'READY' ? ( - +   {currentItem?.name} ) : currentItem?.status === 'IN_PROGRESS' || currentItem?.status === 'AWAITING_UPLOAD' ? ( diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index e7df4dce6c46f..8eac1c1e33c7c 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -8,6 +8,8 @@ import { readFile } from 'fs/promises'; import Path from 'path'; +import { Stream } from 'stream'; + import { REPO_ROOT } from '@kbn/utils'; import { uniq } from 'lodash'; import semverGte from 'semver/functions/gte'; @@ -42,6 +44,7 @@ import type { PostBulkAgentReassignRequestSchema, PostBulkUpdateAgentTagsRequestSchema, GetActionStatusRequestSchema, + GetAgentUploadFileRequestSchema, } from '../../types'; import { defaultFleetErrorHandler } from '../../errors'; import * as AgentService from '../../services/agents'; @@ -379,3 +382,20 @@ export const getAgentUploadsHandler: RequestHandler< return defaultFleetErrorHandler({ error, response }); } }; + +export const getAgentUploadFileHandler: RequestHandler< + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { + const readable = await AgentService.getAgentUploadFile(esClient, request.params.fileId); + + const stream = new Stream.PassThrough(); + readable.pipe(stream); + + return response.ok({ body: stream }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index 224c5f7ee8f06..e8ac9a2de6749 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -26,6 +26,7 @@ import { PostRequestDiagnosticsActionRequestSchema, PostBulkRequestDiagnosticsActionRequestSchema, ListAgentUploadsRequestSchema, + GetAgentUploadFileRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; import type { FleetConfigType } from '../..'; @@ -47,6 +48,7 @@ import { getAvailableVersionsHandler, getActionStatusHandler, getAgentUploadsHandler, + getAgentUploadFileHandler, } from './handlers'; import { postNewAgentActionHandlerBuilder, @@ -219,6 +221,17 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT getAgentUploadsHandler ); + router.get( + { + path: AGENT_API_ROUTES.GET_UPLOAD_FILE_PATTERN, + validate: GetAgentUploadFileRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + getAgentUploadFileHandler + ); + // Get agent status for policy router.get( { diff --git a/x-pack/plugins/fleet/server/services/agents/index.ts b/x-pack/plugins/fleet/server/services/agents/index.ts index 6fdb0e6f05795..c5e1c199e6b82 100644 --- a/x-pack/plugins/fleet/server/services/agents/index.ts +++ b/x-pack/plugins/fleet/server/services/agents/index.ts @@ -16,7 +16,7 @@ export * from './setup'; export * from './update_agent_tags'; export * from './action_status'; export * from './request_diagnostics'; -export { getAgentUploads } from './uploads'; +export { getAgentUploads, getAgentUploadFile } from './uploads'; export { AgentServiceImpl } from './agent_service'; export type { AgentClient, AgentService } from './agent_service'; export { BulkActionsResolver } from './bulk_actions_resolver'; diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index c9162bce1e527..14579b24713d9 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { Readable } from 'stream'; + import moment from 'moment'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; @@ -11,7 +13,7 @@ import { createEsFileClient } from '@kbn/files-plugin/server'; import type { AgentDiagnostics } from '../../../common/types/models'; import { appContextService } from '../app_context'; -import { AGENT_ACTIONS_INDEX } from '../../../common'; +import { AGENT_ACTIONS_INDEX, agentRouteService } from '../../../common'; import { SO_SEARCH_LIMIT } from '../../constants'; @@ -38,7 +40,7 @@ export async function getAgentUploads( file = files[0]; } const fileName = file?.name ?? `${moment(action.timestamp!).format('YYYY-MM-DD HH:mm:ss')}.zip`; - const filePath = `/api/files/files/${action.actionId}/blob/${fileName}`; // TODO mock value + const filePath = file ? agentRouteService.getAgentFileDownloadLink(file.id, file.name) : ''; return { actionId: action.actionId, id: file?.id ?? action.actionId, @@ -93,7 +95,10 @@ async function _getRequestDiagnosticsActions( })); } -export async function getDiagnosticFile(esClient: ElasticsearchClient, id: string): Promise { +export async function getAgentUploadFile( + esClient: ElasticsearchClient, + id: string +): Promise { try { const fileClient = createEsFileClient({ blobStorageIndex: '.fleet-agent-file-data', @@ -102,10 +107,11 @@ export async function getDiagnosticFile(esClient: ElasticsearchClient, id: strin logger: appContextService.getLogger(), }); - const results = await fileClient.get({ + const file = await fileClient.get({ id, }); - return results; + + return await file.downloadContent(); } catch (error) { appContextService.getLogger().error(error); throw error; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 301573ec83794..709e8e8d27159 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -132,6 +132,13 @@ export const ListAgentUploadsRequestSchema = { }), }; +export const GetAgentUploadFileRequestSchema = { + params: schema.object({ + fileId: schema.string(), + fileName: schema.string(), + }), +}; + export const PostBulkAgentReassignRequestSchema = { body: schema.object({ policy_id: schema.string(), From ed1a5d1e87b02673e62ab11b3b4d3b66b621fdb1 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 6 Oct 2022 13:12:08 +0200 Subject: [PATCH 10/24] changed implementation to add downlaod headers for file --- .../fleet/server/routes/agent/handlers.ts | 13 ++++----- .../fleet/server/services/agents/uploads.ts | 28 +++++++++++++++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 8eac1c1e33c7c..85bcce7df69ba 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -8,8 +8,6 @@ import { readFile } from 'fs/promises'; import Path from 'path'; -import { Stream } from 'stream'; - import { REPO_ROOT } from '@kbn/utils'; import { uniq } from 'lodash'; import semverGte from 'semver/functions/gte'; @@ -389,12 +387,13 @@ export const getAgentUploadFileHandler: RequestHandler< const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; try { - const readable = await AgentService.getAgentUploadFile(esClient, request.params.fileId); - - const stream = new Stream.PassThrough(); - readable.pipe(stream); + const resp = await AgentService.getAgentUploadFile( + esClient, + request.params.fileId, + request.params.fileName + ); - return response.ok({ body: stream }); + return response.ok(resp); } catch (error) { return defaultFleetErrorHandler({ error, response }); } diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index 14579b24713d9..22f0a9cc7ebda 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -6,10 +6,15 @@ */ import type { Readable } from 'stream'; +import mime from 'mime'; + import moment from 'moment'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { createEsFileClient } from '@kbn/files-plugin/server'; +import type { File } from '@kbn/files-plugin/common'; + +import type { ResponseHeaders } from '@kbn/core-http-server'; import type { AgentDiagnostics } from '../../../common/types/models'; import { appContextService } from '../app_context'; @@ -97,8 +102,9 @@ async function _getRequestDiagnosticsActions( export async function getAgentUploadFile( esClient: ElasticsearchClient, - id: string -): Promise { + id: string, + fileName: string +): Promise<{ body: Readable; headers: ResponseHeaders }> { try { const fileClient = createEsFileClient({ blobStorageIndex: '.fleet-agent-file-data', @@ -111,9 +117,25 @@ export async function getAgentUploadFile( id, }); - return await file.downloadContent(); + return { + body: await file.downloadContent(), + headers: getDownloadHeadersForFile(file, fileName), + }; } catch (error) { appContextService.getLogger().error(error); throw error; } } + +// copied from https://github.com/elastic/kibana/blob/main/x-pack/plugins/files/server/routes/common.ts +export function getDownloadHeadersForFile(file: File, fileName: string): ResponseHeaders { + return { + 'content-type': + (fileName && mime.getType(fileName)) ?? file.data.mimeType ?? 'application/octet-stream', + // Note, this name can be overridden by the client if set via a "download" attribute on the HTML tag. + 'content-disposition': `attachment; filename="${fileName}"`, + 'cache-control': 'max-age=31536000, immutable', + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + 'x-content-type-options': 'nosniff', + }; +} From d53796134938ec772579bf75e022d9639aff7cd4 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Thu, 6 Oct 2022 14:50:37 +0200 Subject: [PATCH 11/24] added toast when a diagnostics became ready --- .../components/agent_diagnostics/index.tsx | 25 +++++++++++++++++++ .../fleet/server/services/agents/uploads.ts | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index cc631bae285af..7f9bc76799ed3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -46,6 +46,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(true); const [diagnosticsEntries, setDiagnosticEntries] = useState([]); + const [prevDiagnosticsEntries, setPrevDiagnosticEntries] = useState([]); const loadData = useCallback(async () => { try { @@ -86,6 +87,30 @@ export const AgentDiagnosticsTab: React.FunctionComponent return cleanup; }, [loadData]); + useEffect(() => { + setPrevDiagnosticEntries(diagnosticsEntries); + if (prevDiagnosticsEntries.length > 0) { + diagnosticsEntries + .filter((newEntry) => { + const oldEntry = prevDiagnosticsEntries.find((entry) => entry.id === newEntry.id); + return newEntry.status === 'READY' && (!oldEntry || oldEntry?.status !== 'READY'); + }) + .forEach((entry) => { + notifications.toasts.addSuccess( + { + title: i18n.translate('xpack.fleet.requestDiagnostics.readyNotificationTitle', { + defaultMessage: 'Agent diagnostics {name} ready', + values: { + name: entry.name, + }, + }), + }, + { toastLifeTimeMs: 5000 } + ); + }); + } + }, [prevDiagnosticsEntries, diagnosticsEntries, notifications.toasts]); + const columns: Array> = [ { field: 'id', diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index 22f0a9cc7ebda..9bd6d0e3e123b 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -63,6 +63,12 @@ export async function getAgentUploads( id: 'failed1', status: 'FAILED', }); + + // TODO mock simulating last in progress action turning ready if taken in last 10s + const diag = result.find((item) => item.status === 'IN_PROGRESS'); + if (diag && new Date(diag.createTime).getTime() > Date.now() - 10000) { + diag.status = 'READY'; + } } return result; From f165ee7e9aeeb19d058464f0682e4cf203a35f3f Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 7 Oct 2022 15:25:56 +0200 Subject: [PATCH 12/24] added tests on request diagnostics and uploads --- .../agents/request_diagnostics.test.ts | 54 +++++++++ .../apis/agents/index.js | 2 + .../apis/agents/request_diagnostics.ts | 109 ++++++++++++++++++ .../apis/agents/uploads.ts | 109 ++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts create mode 100644 x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts create mode 100644 x-pack/test/fleet_api_integration/apis/agents/uploads.ts diff --git a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts new file mode 100644 index 0000000000000..29055cd4b04f3 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { appContextService } from '../app_context'; +import { createAppContextStartContractMock } from '../../mocks'; + +import { createClientMock } from './action.mock'; +import { bulkRequestDiagnostics, requestDiagnostics } from './request_diagnostics'; + +describe('requestDiagnostics (singular)', () => { + it('can request diagnostics for single agent', async () => { + const { soClient, esClient, agentInRegularDoc } = createClientMock(); + await requestDiagnostics(esClient, agentInRegularDoc._id); + + expect(esClient.create).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + agents: ['agent-in-regular-policy'], + type: 'REQUEST_DIAGNOSTICS', + }), + index: '.fleet-actions', + }) + ); + }); +}); + +describe('requestDiagnostics (plural)', () => { + beforeEach(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(() => { + appContextService.stop(); + }); + it('can request diagnostics for multiple agents', async () => { + const { soClient, esClient, agentInRegularDoc, agentInRegularDoc2 } = createClientMock(); + const idsToUnenroll = [agentInRegularDoc._id, agentInRegularDoc2._id]; + await bulkRequestDiagnostics(esClient, soClient, { agentIds: idsToUnenroll }); + + expect(esClient.create).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + agents: ['agent-in-regular-policy', 'agent-in-regular-policy2'], + type: 'REQUEST_DIAGNOSTICS', + }), + index: '.fleet-actions', + }) + ); + }); +}); diff --git a/x-pack/test/fleet_api_integration/apis/agents/index.js b/x-pack/test/fleet_api_integration/apis/agents/index.js index 0ce4413f7c2cf..783775d53f265 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/index.js +++ b/x-pack/test/fleet_api_integration/apis/agents/index.js @@ -18,5 +18,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./update_agent_tags')); loadTestFile(require.resolve('./available_versions')); + loadTestFile(require.resolve('./request_diagnostics')); + loadTestFile(require.resolve('./uploads')); }); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts b/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts new file mode 100644 index 0000000000000..bd5db9a5df92f --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts @@ -0,0 +1,109 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { setupFleetAndAgents } from './services'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const esClient = getService('es'); + + describe('fleet_request_diagnostics', () => { + skipIfNoDockerRegistry(providerContext); + setupFleetAndAgents(providerContext); + beforeEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + await esArchiver.load('x-pack/test/functional/es_archives/fleet/agents'); + await getService('supertest').post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); + }); + afterEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); + await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + }); + + async function verifyActionResult(agentCount: number) { + const { body } = await supertest + .get(`/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + const actionStatus = body.items[0]; + + expect(actionStatus.nbAgentsActionCreated).to.eql(agentCount); + } + + it('/agents/{agent_id}/request_diagnostics should work', async () => { + const response = await supertest + .post(`/api/fleet/agents/agent1/request_diagnostics`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + verifyActionResult(1); + }); + + it('/agents/bulk_request_diagnostics should work for multiple agents by id', async () => { + await supertest + .post(`/api/fleet/agents/bulk_request_diagnostics`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent2', 'agent3'], + }); + + verifyActionResult(2); + }); + + it('/agents/bulk_request_diagnostics should work for multiple agents by kuery', async () => { + await supertest + .post(`/api/fleet/agents/bulk_request_diagnostics`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: '', + }) + .expect(200); + + verifyActionResult(4); + }); + + it('/agents/bulk_request_diagnostics should work for multiple agents by kuery in batches async', async () => { + const { body } = await supertest + .post(`/api/fleet/agents/bulk_request_diagnostics`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: '', + batchSize: 2, + }) + .expect(200); + + const actionId = body.actionId; + + await new Promise((resolve, reject) => { + let attempts = 0; + const intervalId = setInterval(async () => { + if (attempts > 2) { + clearInterval(intervalId); + reject('action timed out'); + } + ++attempts; + const { + body: { items: actionStatuses }, + } = await supertest.get(`/api/fleet/agents/action_status`).set('kbn-xsrf', 'xxx'); + + const action = actionStatuses?.find((a: any) => a.actionId === actionId); + if (action && action.nbAgentsActioned === action.nbAgentsActionCreated) { + clearInterval(intervalId); + resolve({}); + } + }, 1000); + }).catch((e) => { + throw e; + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts new file mode 100644 index 0000000000000..a83fd1719cd74 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts @@ -0,0 +1,109 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { setupFleetAndAgents } from './services'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const esClient = getService('es'); + + describe('fleet_uploads', () => { + skipIfNoDockerRegistry(providerContext); + setupFleetAndAgents(providerContext); + beforeEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + await getService('supertest').post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); + + await esClient.create({ + index: '.fleet-actions', + id: 'action1', + refresh: true, + body: { + type: 'REQUEST_DIAGNOSTICS', + action_id: 'action1', + agents: ['agent1'], + '@timestamp': '2022-10-07T12:00:00.000Z', + }, + }); + + await esClient.update({ + index: '.fleet-agent-files', + id: 'file1', + refresh: true, + body: { + doc_as_upsert: true, + doc: { + file: { + ChunkSize: 4194304, + extension: 'zip', + hash: {}, + mime_type: 'application/zip', + mode: '0644', + name: 'elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + path: '/agent/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + size: 24917, + Status: 'READY', + type: 'file', + }, + }, + }, + }); + }); + afterEach(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + }); + + it('should get agent uploads', async () => { + const { body } = await supertest + .get(`/api/fleet/agents/agent1/uploads`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(body.items[0]).to.eql({ + actionId: 'action1', + createTime: '2022-10-07T12:00:00.000Z', + filePath: + '/api/fleet/agents/files/file1/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + id: 'file1', + name: 'elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip', + status: 'READY', + }); + }); + + it('should get agent uploaded file', async () => { + await esClient.update({ + index: '.fleet-agent-file-data', + id: 'file1.0', + refresh: true, + body: { + doc_as_upsert: true, + doc: { + last: true, + bid: 'file1', + data: 'test', + }, + }, + }); + + const { headers } = await supertest + .get(`/api/fleet/agents/files/file1/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(headers['content-type']).to.eql('application/zip'); + expect(headers['content-disposition']).to.eql( + 'attachment; filename="elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip"' + ); + }); + }); +} From a15ba47e9731373d016a73f56e77cd0905d4e430 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 7 Oct 2022 16:00:10 +0200 Subject: [PATCH 13/24] fixed checks --- .../fleet/server/services/agents/request_diagnostics.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts index 29055cd4b04f3..05aa81be27612 100644 --- a/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/request_diagnostics.test.ts @@ -13,7 +13,7 @@ import { bulkRequestDiagnostics, requestDiagnostics } from './request_diagnostic describe('requestDiagnostics (singular)', () => { it('can request diagnostics for single agent', async () => { - const { soClient, esClient, agentInRegularDoc } = createClientMock(); + const { esClient, agentInRegularDoc } = createClientMock(); await requestDiagnostics(esClient, agentInRegularDoc._id); expect(esClient.create).toHaveBeenCalledWith( From 4b7c5c5edcb24a4978864c6dcc7cba30a2ac5479 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 7 Oct 2022 17:24:29 +0200 Subject: [PATCH 14/24] fix checks --- .../apis/agents/request_diagnostics.ts | 3 +-- x-pack/test/fleet_api_integration/apis/agents/uploads.ts | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts b/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts index bd5db9a5df92f..7f5ad510723d3 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/request_diagnostics.ts @@ -15,7 +15,6 @@ export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - const esClient = getService('es'); describe('fleet_request_diagnostics', () => { skipIfNoDockerRegistry(providerContext); @@ -40,7 +39,7 @@ export default function (providerContext: FtrProviderContext) { } it('/agents/{agent_id}/request_diagnostics should work', async () => { - const response = await supertest + await supertest .post(`/api/fleet/agents/agent1/request_diagnostics`) .set('kbn-xsrf', 'xxx') .expect(200); diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts index a83fd1719cd74..5ce56a183aace 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts @@ -95,13 +95,13 @@ export default function (providerContext: FtrProviderContext) { }, }); - const { headers } = await supertest + const { header } = await supertest .get(`/api/fleet/agents/files/file1/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip`) .set('kbn-xsrf', 'xxx') .expect(200); - expect(headers['content-type']).to.eql('application/zip'); - expect(headers['content-disposition']).to.eql( + expect(header['content-type']).to.eql('application/zip'); + expect(header['content-disposition']).to.eql( 'attachment; filename="elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip"' ); }); From 6684a78cc517bc89d7de0945511a45dbb2b011f6 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Mon, 10 Oct 2022 13:19:32 +0200 Subject: [PATCH 15/24] returning always octet stream when downloading file --- .../plugins/fleet/server/services/agents/uploads.ts | 11 +++-------- .../test/fleet_api_integration/apis/agents/uploads.ts | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index 9bd6d0e3e123b..8a1ab399f4206 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -6,13 +6,10 @@ */ import type { Readable } from 'stream'; -import mime from 'mime'; - import moment from 'moment'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { createEsFileClient } from '@kbn/files-plugin/server'; -import type { File } from '@kbn/files-plugin/common'; import type { ResponseHeaders } from '@kbn/core-http-server'; @@ -125,7 +122,7 @@ export async function getAgentUploadFile( return { body: await file.downloadContent(), - headers: getDownloadHeadersForFile(file, fileName), + headers: getDownloadHeadersForFile(fileName), }; } catch (error) { appContextService.getLogger().error(error); @@ -133,11 +130,9 @@ export async function getAgentUploadFile( } } -// copied from https://github.com/elastic/kibana/blob/main/x-pack/plugins/files/server/routes/common.ts -export function getDownloadHeadersForFile(file: File, fileName: string): ResponseHeaders { +export function getDownloadHeadersForFile(fileName: string): ResponseHeaders { return { - 'content-type': - (fileName && mime.getType(fileName)) ?? file.data.mimeType ?? 'application/octet-stream', + 'content-type': 'application/octet-stream', // Note, this name can be overridden by the client if set via a "download" attribute on the HTML tag. 'content-disposition': `attachment; filename="${fileName}"`, 'cache-control': 'max-age=31536000, immutable', diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts index 5ce56a183aace..386facb7bccf6 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts @@ -100,7 +100,7 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .expect(200); - expect(header['content-type']).to.eql('application/zip'); + expect(header['content-type']).to.eql('application/octet-stream'); expect(header['content-disposition']).to.eql( 'attachment; filename="elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip"' ); From f758d960d9767131235c3324729705e2d2275b61 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:55:27 +0100 Subject: [PATCH 16/24] Update tsconfig.json Files plugin location changed --- x-pack/plugins/fleet/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 9e6e8f87df950..e507cd543ca54 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -28,7 +28,7 @@ { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../encrypted_saved_objects/tsconfig.json" }, { "path": "../../../src/plugins/guided_onboarding/tsconfig.json" }, - { "path": "../files/tsconfig.json" }, + { "path": "../../../src/plugins/files/tsconfig.json" }, // optionalPlugins from ./kibana.json { "path": "../security/tsconfig.json" }, From 22ad4512360eda720e6c7ed32044ce686b771e9a Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Mon, 7 Nov 2022 09:29:52 +0100 Subject: [PATCH 17/24] fixed test --- .../test_suites/task_manager/check_registered_task_types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 518d36bbdc9a5..1bdfda2f63863 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -114,6 +114,7 @@ export default function ({ getService }: FtrProviderContext) { 'endpoint:user-artifact-packager', 'fleet:check-deleted-files-task', 'fleet:reassign_action:retry', + 'fleet:request_diagnostics:retry', 'fleet:unenroll_action:retry', 'fleet:update_agent_tags:retry', 'fleet:upgrade_action:retry', From b164e02324b1605abad9e2f02bc874cc7369fff9 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 7 Nov 2022 14:06:32 +0000 Subject: [PATCH 18/24] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 90 +++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0b64a5bf200b8..86f74c0ec222f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -702,7 +702,7 @@ packages/analytics/shippers/elastic_v3/common @elastic/kibana-core packages/analytics/shippers/elastic_v3/server @elastic/kibana-core packages/analytics/shippers/fullstory @elastic/kibana-core packages/analytics/shippers/gainsight @elastic/kibana-core -packages/content-management/table_list @elastic/kibana-global-experience +packages/content-management/table_list @elastic/shared-ux packages/core/analytics/core-analytics-browser @elastic/kibana-core packages/core/analytics/core-analytics-browser-internal @elastic/kibana-core packages/core/analytics/core-analytics-browser-mocks @elastic/kibana-core @@ -874,9 +874,9 @@ packages/core/usage-data/core-usage-data-base-server-internal @elastic/kibana-co packages/core/usage-data/core-usage-data-server @elastic/kibana-core packages/core/usage-data/core-usage-data-server-internal @elastic/kibana-core packages/core/usage-data/core-usage-data-server-mocks @elastic/kibana-core -packages/home/sample_data_card @elastic/kibana-global-experience -packages/home/sample_data_tab @elastic/kibana-global-experience -packages/home/sample_data_types @elastic/kibana-global-experience +packages/home/sample_data_card @elastic/shared-ux +packages/home/sample_data_tab @elastic/shared-ux +packages/home/sample_data_types @elastic/shared-ux packages/kbn-ace @elastic/platform-deployment-management packages/kbn-alerts @elastic/security-solution packages/kbn-ambient-storybook-types @elastic/kibana-operations @@ -945,7 +945,7 @@ packages/kbn-logging-mocks @elastic/kibana-core packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations packages/kbn-mapbox-gl @elastic/kibana-gis -packages/kbn-monaco @elastic/kibana-global-experience +packages/kbn-monaco @elastic/kibana-app-services packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations packages/kbn-osquery-io-ts-types @elastic/security-asset-management @@ -976,7 +976,7 @@ packages/kbn-securitysolution-utils @elastic/security-solution-platform packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/apm-ui packages/kbn-shared-svg @elastic/apm-ui -packages/kbn-shared-ux-utility @elastic/kibana-global-experience +packages/kbn-shared-ux-utility @elastic/shared-ux packages/kbn-some-dev-log @elastic/kibana-operations packages/kbn-sort-package-json @elastic/kibana-operations packages/kbn-spec-to-console @elastic/platform-deployment-management @@ -1004,45 +1004,45 @@ packages/kbn-utility-types @elastic/kibana-core packages/kbn-utility-types-jest @elastic/kibana-operations packages/kbn-utils @elastic/kibana-operations packages/kbn-yarn-lock-validator @elastic/kibana-operations -packages/shared-ux/avatar/solution @elastic/kibana-global-experience -packages/shared-ux/avatar/user_profile/impl @elastic/kibana-global-experience -packages/shared-ux/button_toolbar @elastic/kibana-global-experience -packages/shared-ux/button/exit_full_screen/impl @elastic/kibana-global-experience -packages/shared-ux/button/exit_full_screen/mocks @elastic/kibana-global-experience -packages/shared-ux/button/exit_full_screen/types @elastic/kibana-global-experience -packages/shared-ux/card/no_data/impl @elastic/kibana-global-experience -packages/shared-ux/card/no_data/mocks @elastic/kibana-global-experience -packages/shared-ux/card/no_data/types @elastic/kibana-global-experience -packages/shared-ux/link/redirect_app/impl @elastic/kibana-global-experience -packages/shared-ux/link/redirect_app/mocks @elastic/kibana-global-experience -packages/shared-ux/link/redirect_app/types @elastic/kibana-global-experience -packages/shared-ux/markdown/impl @elastic/kibana-global-experience -packages/shared-ux/markdown/mocks @elastic/kibana-global-experience -packages/shared-ux/markdown/types @elastic/kibana-global-experience -packages/shared-ux/page/analytics_no_data/impl @elastic/kibana-global-experience -packages/shared-ux/page/analytics_no_data/mocks @elastic/kibana-global-experience -packages/shared-ux/page/analytics_no_data/types @elastic/kibana-global-experience -packages/shared-ux/page/kibana_no_data/impl @elastic/kibana-global-experience -packages/shared-ux/page/kibana_no_data/mocks @elastic/kibana-global-experience -packages/shared-ux/page/kibana_no_data/types @elastic/kibana-global-experience -packages/shared-ux/page/kibana_template/impl @elastic/kibana-global-experience -packages/shared-ux/page/kibana_template/mocks @elastic/kibana-global-experience -packages/shared-ux/page/kibana_template/types @elastic/kibana-global-experience -packages/shared-ux/page/no_data_config/impl @elastic/kibana-global-experience -packages/shared-ux/page/no_data_config/mocks @elastic/kibana-global-experience -packages/shared-ux/page/no_data_config/types @elastic/kibana-global-experience -packages/shared-ux/page/no_data/impl @elastic/kibana-global-experience -packages/shared-ux/page/no_data/mocks @elastic/kibana-global-experience -packages/shared-ux/page/no_data/types @elastic/kibana-global-experience -packages/shared-ux/page/solution_nav @elastic/kibana-global-experience -packages/shared-ux/prompt/no_data_views/impl @elastic/kibana-global-experience -packages/shared-ux/prompt/no_data_views/mocks @elastic/kibana-global-experience -packages/shared-ux/prompt/no_data_views/types @elastic/kibana-global-experience -packages/shared-ux/router/impl @elastic/kibana-global-experience -packages/shared-ux/router/mocks @elastic/kibana-global-experience -packages/shared-ux/router/types @elastic/kibana-global-experience -packages/shared-ux/storybook/config @elastic/kibana-global-experience -packages/shared-ux/storybook/mock @elastic/kibana-global-experience +packages/shared-ux/avatar/solution @elastic/shared-ux +packages/shared-ux/avatar/user_profile/impl @elastic/shared-ux +packages/shared-ux/button_toolbar @elastic/shared-ux +packages/shared-ux/button/exit_full_screen/impl @elastic/shared-ux +packages/shared-ux/button/exit_full_screen/mocks @elastic/shared-ux +packages/shared-ux/button/exit_full_screen/types @elastic/shared-ux +packages/shared-ux/card/no_data/impl @elastic/shared-ux +packages/shared-ux/card/no_data/mocks @elastic/shared-ux +packages/shared-ux/card/no_data/types @elastic/shared-ux +packages/shared-ux/link/redirect_app/impl @elastic/shared-ux +packages/shared-ux/link/redirect_app/mocks @elastic/shared-ux +packages/shared-ux/link/redirect_app/types @elastic/shared-ux +packages/shared-ux/markdown/impl @elastic/shared-ux +packages/shared-ux/markdown/mocks @elastic/shared-ux +packages/shared-ux/markdown/types @elastic/shared-ux +packages/shared-ux/page/analytics_no_data/impl @elastic/shared-ux +packages/shared-ux/page/analytics_no_data/mocks @elastic/shared-ux +packages/shared-ux/page/analytics_no_data/types @elastic/shared-ux +packages/shared-ux/page/kibana_no_data/impl @elastic/shared-ux +packages/shared-ux/page/kibana_no_data/mocks @elastic/shared-ux +packages/shared-ux/page/kibana_no_data/types @elastic/shared-ux +packages/shared-ux/page/kibana_template/impl @elastic/shared-ux +packages/shared-ux/page/kibana_template/mocks @elastic/shared-ux +packages/shared-ux/page/kibana_template/types @elastic/shared-ux +packages/shared-ux/page/no_data_config/impl @elastic/shared-ux +packages/shared-ux/page/no_data_config/mocks @elastic/shared-ux +packages/shared-ux/page/no_data_config/types @elastic/shared-ux +packages/shared-ux/page/no_data/impl @elastic/shared-ux +packages/shared-ux/page/no_data/mocks @elastic/shared-ux +packages/shared-ux/page/no_data/types @elastic/shared-ux +packages/shared-ux/page/solution_nav @elastic/shared-ux +packages/shared-ux/prompt/no_data_views/impl @elastic/shared-ux +packages/shared-ux/prompt/no_data_views/mocks @elastic/shared-ux +packages/shared-ux/prompt/no_data_views/types @elastic/shared-ux +packages/shared-ux/router/impl @elastic/shared-ux +packages/shared-ux/router/mocks @elastic/shared-ux +packages/shared-ux/router/types @elastic/shared-ux +packages/shared-ux/storybook/config @elastic/shared-ux +packages/shared-ux/storybook/mock @elastic/shared-ux x-pack/packages/ml/agg_utils @elastic/ml-ui x-pack/packages/ml/aiops_components @elastic/ml-ui x-pack/packages/ml/aiops_utils @elastic/ml-ui From a4861967aa8497e7c20ca89eae9b6d6893a3a99c Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Tue, 8 Nov 2022 10:48:53 +0100 Subject: [PATCH 19/24] added feature flag to hide request diagnostics action --- .../fleet/common/experimental_features.ts | 1 + .../components/actions_menu.tsx | 125 +++++----- .../components/bulk_actions.tsx | 215 +++++++++--------- .../components/table_row_actions.tsx | 31 ++- 4 files changed, 198 insertions(+), 174 deletions(-) diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 8926a1092fca2..2c55ce4a2a08e 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -15,6 +15,7 @@ export const allowedExperimentalValues = Object.freeze({ createPackagePolicyMultiPageLayout: true, packageVerification: true, showDevtoolsRequest: true, + showRequestDiagnostics: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index 12f8c59df057d..b52a0401b8650 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -20,6 +20,7 @@ import { import { useAgentRefresh } from '../hooks'; import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services'; import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal'; +import { ExperimentalFeaturesService } from '../../../../services'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; @@ -37,6 +38,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const isUnenrolling = agent.status === 'unenrolling'; const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy); + const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); const onClose = useMemo(() => { if (onCancelReassign) { @@ -46,6 +48,70 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ } }, [onCancelReassign, setIsReassignFlyoutOpen]); + const menuItems = [ + { + setIsReassignFlyoutOpen(true); + }} + disabled={!agent.active} + key="reassignPolicy" + > + + , + { + setIsUnenrollModalOpen(true); + }} + > + {isUnenrolling ? ( + + ) : ( + + )} + , + { + setIsUpgradeModalOpen(true); + }} + > + + , + ]; + + if (showRequestDiagnostics) { + menuItems.push( + { + setIsRequestDiagnosticsModalOpen(true); + }} + > + + + ); + } + return ( <> {isReassignFlyoutOpen && ( @@ -100,64 +166,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ /> ), }} - items={[ - { - setIsReassignFlyoutOpen(true); - }} - disabled={!agent.active} - key="reassignPolicy" - > - - , - { - setIsUnenrollModalOpen(true); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - , - { - setIsUpgradeModalOpen(true); - }} - > - - , - { - setIsRequestDiagnosticsModalOpen(true); - }} - > - - , - ]} + items={menuItems} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index e55451bdb9791..aa53c3d8f4040 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -25,6 +25,7 @@ import { } from '../../components'; import { useLicense } from '../../../../hooks'; import { LICENSE_FOR_SCHEDULE_UPGRADE } from '../../../../../../../common/constants'; +import { ExperimentalFeaturesService } from '../../../../services'; import { getCommonTags } from '../utils'; @@ -81,114 +82,120 @@ export const AgentBulkActions: React.FunctionComponent = ({ const agentCount = selectionMode === 'manual' ? selectedAgents.length : totalActiveAgents; const agents = selectionMode === 'manual' ? selectedAgents : currentQuery; const [tagsPopoverButton, setTagsPopoverButton] = useState(); + const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); + + const menuItems = [ + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: (event: any) => { + setTagsPopoverButton((event.target as Element).closest('button')!); + setIsTagAddVisible(!isTagAddVisible); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsReassignFlyoutOpen(true); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsUnenrollModalOpen(true); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setUpgradeModalState({ isOpen: true, isScheduled: false }); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected || !isLicenceAllowingScheduleUpgrade, + onClick: () => { + closeMenu(); + setUpgradeModalState({ isOpen: true, isScheduled: true }); + }, + }, + ]; + + if (showRequestDiagnostics) { + menuItems.push({ + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsRequestDiagnosticsModalOpen(true); + }, + }); + } const panels = [ { id: 0, - items: [ - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected, - onClick: (event: any) => { - setTagsPopoverButton((event.target as Element).closest('button')!); - setIsTagAddVisible(!isTagAddVisible); - }, - }, - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected, - onClick: () => { - closeMenu(); - setIsReassignFlyoutOpen(true); - }, - }, - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected, - onClick: () => { - closeMenu(); - setIsUnenrollModalOpen(true); - }, - }, - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected, - onClick: () => { - closeMenu(); - setUpgradeModalState({ isOpen: true, isScheduled: false }); - }, - }, - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected || !isLicenceAllowingScheduleUpgrade, - onClick: () => { - closeMenu(); - setUpgradeModalState({ isOpen: true, isScheduled: true }); - }, - }, - { - name: ( - - ), - icon: , - disabled: !atLeastOneActiveAgentSelected, - onClick: () => { - closeMenu(); - setIsRequestDiagnosticsModalOpen(true); - }, - }, - ], + items: menuItems, }, ]; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index d6b8cec3c03c0..ad9c9bdbf3f7b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -13,6 +13,7 @@ import type { Agent, AgentPolicy } from '../../../../types'; import { useAuthz, useLink, useKibanaVersion } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; import { isAgentUpgradeable } from '../../../../services'; +import { ExperimentalFeaturesService } from '../../../../services'; export const TableRowActions: React.FunctionComponent<{ agent: Agent; @@ -37,6 +38,7 @@ export const TableRowActions: React.FunctionComponent<{ const isUnenrolling = agent.status === 'unenrolling'; const kibanaVersion = useKibanaVersion(); const [isMenuOpen, setIsMenuOpen] = useState(false); + const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); const menuItems = [ - , - { - onRequestDiagnosticsClick(); - }} - > - ); + + if (showRequestDiagnostics) { + menuItems.push( + { + onRequestDiagnosticsClick(); + }} + > + + + ); + } } return ( Date: Tue, 8 Nov 2022 11:18:09 +0100 Subject: [PATCH 20/24] fixed checks --- x-pack/plugins/fleet/public/mock/create_test_renderer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index 51ff2cb0f8245..f4cd7f9403280 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -68,6 +68,7 @@ export const createFleetTestRendererMock = (): TestRenderer => { createPackagePolicyMultiPageLayout: true, packageVerification: true, showDevtoolsRequest: false, + showRequestDiagnostics: false, }); const HookWrapper = memo(({ children }) => { From b3123faf5e57fa0a5bf8c4344ec22961bcc8addf Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Tue, 8 Nov 2022 13:58:37 +0100 Subject: [PATCH 21/24] fixed test --- .../components/search_and_filter_bar.test.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx index 33fd16419a1bd..f3de9cf503cf1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.test.tsx @@ -20,6 +20,8 @@ import { FleetStatusProvider, ConfigContext, KibanaVersionContext } from '../../ import { getMockTheme } from '../../../../../../mocks'; +import { ExperimentalFeaturesService } from '../../../../services'; + import { SearchAndFilterBar } from './search_and_filter_bar'; const mockTheme = getMockTheme({ @@ -49,6 +51,14 @@ const TestComponent = (props: any) => ( ); describe('SearchAndFilterBar', () => { + beforeAll(() => { + ExperimentalFeaturesService.init({ + createPackagePolicyMultiPageLayout: true, + packageVerification: true, + showDevtoolsRequest: false, + showRequestDiagnostics: false, + }); + }); it('should show no Actions button when no agent is selected', async () => { const selectedAgents: Agent[] = []; const props: any = { From 820edeec709346ba0aca32e56ce68625538b816a Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Tue, 8 Nov 2022 16:50:52 +0100 Subject: [PATCH 22/24] removed mock data and changed the query to return diagnostic files --- .../agents/agent_details_page/index.tsx | 16 ++- .../fleet/server/services/agents/uploads.ts | 105 +++++++++++------- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index f91e2a70dd4d1..b2e9bd76194e4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -25,6 +25,8 @@ import { } from '../../../hooks'; import { WithHeaderLayout } from '../../../layouts'; +import { ExperimentalFeaturesService } from '../../../services'; + import { AgentRefreshContext } from './hooks'; import { AgentLogs, @@ -66,6 +68,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { navigateToApp(routeState.onDoneNavigateTo[0], routeState.onDoneNavigateTo[1]); } }, [routeState, navigateToApp]); + const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); const host = agentData?.item?.local_metadata?.host; @@ -135,7 +138,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ); const headerTabs = useMemo(() => { - return [ + const tabs = [ { id: 'details', name: i18n.translate('xpack.fleet.agentDetails.subTabs.detailsTab', { @@ -152,16 +155,19 @@ export const AgentDetailsPage: React.FunctionComponent = () => { href: getHref('agent_details_logs', { agentId, tabId: 'logs' }), isSelected: tabId === 'logs', }, - { + ]; + if (showRequestDiagnostics) { + tabs.push({ id: 'diagnostics', name: i18n.translate('xpack.fleet.agentDetails.subTabs.diagnosticsTab', { defaultMessage: 'Diagnostics', }), href: getHref('agent_details_diagnostics', { agentId, tabId: 'diagnostics' }), isSelected: tabId === 'diagnostics', - }, - ]; - }, [getHref, agentId, tabId]); + }); + } + return tabs; + }, [getHref, agentId, tabId, showRequestDiagnostics]); return ( { - // TODO filter agentId - const filesMetadata = await esClient.search({ - index: '.fleet-agent-files', - size: 20, - }); - const files = filesMetadata.hits.hits.map((hit) => ({ - ...(hit._source as any).file, - id: hit._id, - })); + const getFile = async (fileId: string) => { + if (!fileId) return; + const file = await esClient.get({ + index: '.fleet-agent-files', + id: fileId, + }); + return { + id: file._id, + ...(file._source as any)?.file, + }; + }; const actions = await _getRequestDiagnosticsActions(esClient, agentId); - const result = actions.map((action, index) => { - let file = files.find((item) => item.action_id === action.actionId); - // TODO mock, remove when files contain actionId - if (index === actions.length - 1) { - file = files[0]; - } + const results = []; + for (const action of actions) { + const file = await getFile(action.fileId); const fileName = file?.name ?? `${moment(action.timestamp!).format('YYYY-MM-DD HH:mm:ss')}.zip`; const filePath = file ? agentRouteService.getAgentFileDownloadLink(file.id, file.name) : ''; - return { + const result = { actionId: action.actionId, id: file?.id ?? action.actionId, status: file?.Status ?? 'IN_PROGRESS', @@ -51,31 +54,17 @@ export async function getAgentUploads( createTime: action.timestamp!, filePath, }; - }); - - // TODO mock failed value - if (result.length > 0) { - result.push({ - ...result[0], - id: 'failed1', - status: 'FAILED', - }); - - // TODO mock simulating last in progress action turning ready if taken in last 10s - const diag = result.find((item) => item.status === 'IN_PROGRESS'); - if (diag && new Date(diag.createTime).getTime() > Date.now() - 10000) { - diag.status = 'READY'; - } + results.push(result); } - return result; + return results; } async function _getRequestDiagnosticsActions( esClient: ElasticsearchClient, agentId: string -): Promise> { - const res = await esClient.search({ +): Promise> { + const agentActionRes = await esClient.search({ index: AGENT_ACTIONS_INDEX, ignore_unavailable: true, size: SO_SEARCH_LIMIT, @@ -97,10 +86,48 @@ async function _getRequestDiagnosticsActions( }, }); - return res.hits.hits.map((hit) => ({ - actionId: hit._source?.action_id as string, - timestamp: hit._source?.['@timestamp'], - })); + const agentActionIds = agentActionRes.hits.hits.map((hit) => hit._source?.action_id as string); + + if (agentActionIds.length === 0) { + return []; + } + + try { + const actionResults = await esClient.search({ + index: AGENT_ACTIONS_RESULTS_INDEX, + ignore_unavailable: true, + size: SO_SEARCH_LIMIT, + query: { + bool: { + must: [ + { + terms: { + action_id: agentActionIds, + }, + }, + { + term: { + agent_id: agentId, + }, + }, + ], + }, + }, + }); + return actionResults.hits.hits.map((hit) => ({ + actionId: hit._source?.action_id as string, + timestamp: hit._source?.['@timestamp'], + fileId: hit._source?.data?.file_id as string, + })); + } catch (err) { + if (err.statusCode === 404) { + // .fleet-actions-results does not yet exist + appContextService.getLogger().debug(err); + return []; + } else { + throw err; + } + } } export async function getAgentUploadFile( From d622ed3dfff41ff7f0ccff6b1c59826a32471555 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 9 Nov 2022 08:42:57 +0100 Subject: [PATCH 23/24] fixed integration test --- .../apis/agents/uploads.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts index 386facb7bccf6..fc56b4fc46c18 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; - +import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { setupFleetAndAgents } from './services'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -17,25 +17,45 @@ export default function (providerContext: FtrProviderContext) { const supertest = getService('supertest'); const esClient = getService('es'); + const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; + describe('fleet_uploads', () => { skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); - beforeEach(async () => { + + before(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); await getService('supertest').post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); await esClient.create({ - index: '.fleet-actions', - id: 'action1', + index: AGENT_ACTIONS_INDEX, + id: new Date().toISOString(), refresh: true, body: { type: 'REQUEST_DIAGNOSTICS', action_id: 'action1', agents: ['agent1'], - '@timestamp': '2022-10-07T12:00:00.000Z', + '@timestamp': '2022-10-07T11:00:00.000Z', }, }); + await esClient.create( + { + index: AGENT_ACTIONS_RESULTS_INDEX, + id: new Date().toISOString(), + refresh: true, + body: { + action_id: 'action1', + agent_id: 'agent1', + '@timestamp': '2022-10-07T12:00:00.000Z', + data: { + file_id: 'file1', + }, + }, + }, + ES_INDEX_OPTIONS + ); + await esClient.update({ index: '.fleet-agent-files', id: 'file1', @@ -59,7 +79,7 @@ export default function (providerContext: FtrProviderContext) { }, }); }); - afterEach(async () => { + after(async () => { await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); From 89888ae52a8c673780f9319e43e82a6f342a0f42 Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Wed, 9 Nov 2022 09:21:04 +0100 Subject: [PATCH 24/24] added error handling, extracted index names as constants --- .../fleet/server/services/agents/uploads.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts index b76b8fab9b117..2269a080e41b9 100644 --- a/x-pack/plugins/fleet/server/services/agents/uploads.ts +++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts @@ -23,20 +23,32 @@ import { import { SO_SEARCH_LIMIT } from '../../constants'; +const FILE_STORAGE_METADATA_AGENT_INDEX = '.fleet-agent-files'; +const FILE_STORAGE_DATA_AGENT_INDEX = '.fleet-agent-file-data'; + export async function getAgentUploads( esClient: ElasticsearchClient, agentId: string ): Promise { const getFile = async (fileId: string) => { if (!fileId) return; - const file = await esClient.get({ - index: '.fleet-agent-files', - id: fileId, - }); - return { - id: file._id, - ...(file._source as any)?.file, - }; + try { + const file = await esClient.get({ + index: FILE_STORAGE_METADATA_AGENT_INDEX, + id: fileId, + }); + return { + id: file._id, + ...(file._source as any)?.file, + }; + } catch (err) { + if (err.statusCode === 404) { + appContextService.getLogger().debug(err); + return; + } else { + throw err; + } + } }; const actions = await _getRequestDiagnosticsActions(esClient, agentId); @@ -137,8 +149,8 @@ export async function getAgentUploadFile( ): Promise<{ body: Readable; headers: ResponseHeaders }> { try { const fileClient = createEsFileClient({ - blobStorageIndex: '.fleet-agent-file-data', - metadataIndex: '.fleet-agent-files', + blobStorageIndex: FILE_STORAGE_DATA_AGENT_INDEX, + metadataIndex: FILE_STORAGE_METADATA_AGENT_INDEX, elasticsearchClient: esClient, logger: appContextService.getLogger(), });