From 3f646d8b9b7c84208bd1af3641aa0174402e2a58 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Tue, 4 May 2021 08:26:28 -0400 Subject: [PATCH] [APM] Inject agent config directly into APM Fleet policies (#95501) --- x-pack/plugins/apm/common/alert_types.ts | 10 ++ x-pack/plugins/apm/kibana.json | 3 +- .../Settings/AgentConfigurations/index.tsx | 72 ++++++++++++- ...ster_agent_config_fleet_sync_alert_type.ts | 99 +++++++++++++++++ .../server/lib/alerts/register_apm_alerts.ts | 4 + .../fleet/agent_config_fleet_sync_alert.ts | 61 +++++++++++ .../fleet/register_fleet_policy_callbacks.ts | 100 ++++++++++++++++++ ...c_agent_configs_to_apm_package_policies.ts | 66 ++++++++++++ x-pack/plugins/apm/server/plugin.ts | 37 ++++--- .../routes/settings/agent_configuration.ts | 79 +++++++++++++- x-pack/plugins/apm/server/types.ts | 9 ++ x-pack/plugins/apm/tsconfig.json | 3 +- 12 files changed, 521 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/alerts/register_agent_config_fleet_sync_alert_type.ts create mode 100644 x-pack/plugins/apm/server/lib/fleet/agent_config_fleet_sync_alert.ts create mode 100644 x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts create mode 100644 x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts index 12df93d54b296..bd92635642b83 100644 --- a/x-pack/plugins/apm/common/alert_types.ts +++ b/x-pack/plugins/apm/common/alert_types.ts @@ -15,6 +15,7 @@ export enum AlertType { TransactionErrorRate = 'apm.transaction_error_rate', TransactionDuration = 'apm.transaction_duration', TransactionDurationAnomaly = 'apm.transaction_duration_anomaly', + AgentConfigFleetSync = 'apm.agent_config_fleet_sync', } export const THRESHOLD_MET_GROUP_ID = 'threshold_met'; @@ -72,6 +73,15 @@ export const ALERT_TYPES_CONFIG: Record< minimumLicenseRequired: 'basic', producer: 'apm', }, + [AlertType.AgentConfigFleetSync]: { + name: i18n.translate('xpack.apm.agentConfigFleetSyncAlert.name', { + defaultMessage: 'Central agent configuration updates', + }), + actionGroups: [THRESHOLD_MET_GROUP], + defaultActionGroupId: THRESHOLD_MET_GROUP_ID, + minimumLicenseRequired: 'basic', + producer: 'apm', + }, }; export const ANOMALY_ALERT_SEVERITY_TYPES = [ diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 28e4a7b36e740..d324921c84702 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -22,7 +22,8 @@ "security", "ml", "home", - "maps" + "maps", + "fleet" ], "server": true, "ui": true, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index 3225951fd6c70..98aca156e6f94 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -17,16 +17,24 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import React from 'react'; +import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; +import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { useTrackPageview } from '../../../../../../observability/public'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks'; import { AgentConfigurationList } from './List'; const INITIAL_DATA = { configurations: [] }; +async function enableFleetSync() { + return await callApmApi({ + endpoint: 'POST /api/apm/settings/agent-configuration/fleet_sync', + signal: null, + }); +} + export function AgentConfigurations() { const { refetch, data = INITIAL_DATA, status } = useFetcher( (callApmApi) => @@ -38,8 +46,37 @@ export function AgentConfigurations() { useTrackPageview({ app: 'apm', path: 'agent_configuration' }); useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 }); + const { + data: packagePolicyInput, + status: packagePolicyInputStatus, + error: packagePolicyError, + refetch: refetchPackagePolicyInput, + } = useFetcher( + (callApmApi) => + callApmApi({ + endpoint: + 'GET /api/apm/settings/agent-configuration/fleet-sync/package-policy-input', + }), + [], + { preservePreviousData: false, showToastOnError: false } + ); + const hasConfigurations = !isEmpty(data.configurations); + const isFleetSyncLoading = + packagePolicyInputStatus !== FETCH_STATUS.FAILURE && + packagePolicyInputStatus !== FETCH_STATUS.SUCCESS; + const isFleetSyncUnavailable = packagePolicyError?.response?.status === 503; + const isFleetSyncEnabled = Boolean( + packagePolicyInputStatus === FETCH_STATUS.SUCCESS && + packagePolicyInput?.agent_config.value && + packagePolicyInput?.alert + ); + + const [isFleetSyncEnableLoading, setIsFleetSyncEnableLoading] = useState( + false + ); + return ( <> @@ -62,13 +99,38 @@ export function AgentConfigurations() {

{i18n.translate( - 'xpack.apm.agentConfig.configurationsPanelTitle', + 'xpack.apm.agentConfig.configurationsPanel.title', { defaultMessage: 'Configurations' } )}

- + {!isFleetSyncLoading && !isFleetSyncUnavailable && ( + +
+ { + setIsFleetSyncEnableLoading(true); + await enableFleetSync(); + refetchPackagePolicyInput(); + setIsFleetSyncEnableLoading(false); + }} + isLoading={isFleetSyncEnableLoading} + > + {isFleetSyncEnabled + ? i18n.translate( + 'xpack.apm.agentConfig.configurationsPanel.fleetSyncingEnabledLabel', + { defaultMessage: 'Syncing with fleet policy' } + ) + : i18n.translate( + 'xpack.apm.agentConfig.configurationsPanel.enableFleetSyncButtonLabel', + { defaultMessage: 'Sync with fleet policy' } + )} + +
+
+ )} {hasConfigurations ? : null} @@ -98,7 +160,7 @@ function CreateConfigurationButton() { content={ !canSave && i18n.translate( - 'xpack.apm.agentConfig.configurationsPanelTitle.noPermissionTooltipLabel', + 'xpack.apm.agentConfig.configurationsPanel.title.noPermissionTooltipLabel', { defaultMessage: "Your user role doesn't have permissions to create agent configurations", diff --git a/x-pack/plugins/apm/server/lib/alerts/register_agent_config_fleet_sync_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_agent_config_fleet_sync_alert_type.ts new file mode 100644 index 0000000000000..f26b3e0eb1afd --- /dev/null +++ b/x-pack/plugins/apm/server/lib/alerts/register_agent_config_fleet_sync_alert_type.ts @@ -0,0 +1,99 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { take } from 'rxjs/operators'; +import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; +import { alertingEsClient } from './alerting_es_client'; +import { RegisterRuleDependencies } from './register_apm_alerts'; +import { createAPMLifecycleRuleType } from './create_apm_lifecycle_rule_type'; +import { convertConfigSettingsToString } from '../settings/agent_configuration/convert_settings_to_string'; +import { AgentConfiguration } from '../../../common/agent_configuration/configuration_types'; +import { syncAgentConfigsToApmPackagePolicies } from '../fleet/sync_agent_configs_to_apm_package_policies'; + +const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.AgentConfigFleetSync]; + +export function registerAgentConfigFleetSyncAlertType({ + registry, + config$, + getFleetPluginStart, +}: RegisterRuleDependencies) { + registry.registerType( + createAPMLifecycleRuleType({ + id: AlertType.AgentConfigFleetSync, + name: alertTypeConfig.name, + actionGroups: alertTypeConfig.actionGroups, + defaultActionGroupId: alertTypeConfig.defaultActionGroupId, + validate: { + params: schema.any(), + }, + actionVariables: { + context: [], + }, + producer: 'apm', + minimumLicenseRequired: 'basic', + executor: async ({ services, state }) => { + const config = await config$.pipe(take(1)).toPromise(); + const indices = await getApmIndices({ + config, + savedObjectsClient: services.savedObjectsClient, + }); + + const searchParams = { + index: indices['apmAgentConfigurationIndex'], + body: { + size: 1, + query: { + match_all: {}, + }, + sort: { '@timestamp': 'desc' as const }, + }, + }; + + const response = await alertingEsClient({ + scopedClusterClient: services.scopedClusterClient, + params: searchParams, + }); + if (response.hits.total.value === 0) { + return {}; + } + + const { ['@timestamp']: lastTimestamp } = response.hits.hits[0] + ._source as { '@timestamp': number }; + // @ts-ignore + if (lastTimestamp > state.lastTimestamp) { + const fleetPluginStart = await getFleetPluginStart(); + if (fleetPluginStart) { + services.logger.info( + `New agent configurations detected. Updating fleet policy...` + ); + const configurationsSearchResponse = await alertingEsClient({ + scopedClusterClient: services.scopedClusterClient, + params: { + index: indices['apmAgentConfigurationIndex'], + body: { size: 200, query: { match_all: {} } }, + }, + }); + const agentConfigurations = configurationsSearchResponse.hits.hits + // @ts-ignore + .map(convertConfigSettingsToString) + .map((hit) => hit._source) as AgentConfiguration[]; + await syncAgentConfigsToApmPackagePolicies({ + fleetPluginStart, + savedObjectsClient: services.savedObjectsClient, + esClient: services.scopedClusterClient.asCurrentUser, + agentConfigurations, + }); + services.logger.info(`Policy updated.`); + } + } + return { lastTimestamp }; + }, + }) + ); +} diff --git a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts index 9a362efa90ac0..213bbbc352eaa 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts @@ -14,12 +14,15 @@ import { APMConfig } from '../..'; import { MlPluginSetup } from '../../../../ml/server'; import { registerTransactionErrorRateAlertType } from './register_transaction_error_rate_alert_type'; import { APMRuleRegistry } from '../../plugin'; +import { registerAgentConfigFleetSyncAlertType } from './register_agent_config_fleet_sync_alert_type'; +import { APMPluginStartDependencies } from '../../types'; export interface RegisterRuleDependencies { registry: APMRuleRegistry; ml?: MlPluginSetup; config$: Observable; logger: Logger; + getFleetPluginStart: () => Promise; } export function registerApmAlerts(dependencies: RegisterRuleDependencies) { @@ -27,4 +30,5 @@ export function registerApmAlerts(dependencies: RegisterRuleDependencies) { registerTransactionDurationAnomalyAlertType(dependencies); registerErrorCountAlertType(dependencies); registerTransactionErrorRateAlertType(dependencies); + registerAgentConfigFleetSyncAlertType(dependencies); } diff --git a/x-pack/plugins/apm/server/lib/fleet/agent_config_fleet_sync_alert.ts b/x-pack/plugins/apm/server/lib/fleet/agent_config_fleet_sync_alert.ts new file mode 100644 index 0000000000000..eff3382bc7f70 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/agent_config_fleet_sync_alert.ts @@ -0,0 +1,61 @@ +/* + * 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 { AlertsClient } from 'x-pack/plugins/alerting/server'; +import { APMPluginStartDependencies } from '../../types'; + +export async function createApmAgentFleetSyncAlert({ + alertsClient, +}: { + alertsClient: AlertsClient; +}) { + return alertsClient?.create({ + data: { + enabled: true, + name: 'APM - Agent config fleet sync', + tags: ['apm'], + alertTypeId: 'apm.agent_config_fleet_sync', + consumer: 'apm', + schedule: { interval: '1m' }, + actions: [], + params: {}, + throttle: null, + notifyWhen: null, + }, + }); +} +export async function getApmAgentFleetSyncAlert({ + alertsClient, +}: { + alertsClient: AlertsClient; +}) { + const { total, data } = (await alertsClient?.find({ + options: { + filter: `alert.attributes.alertTypeId: apm.agent_config_fleet_sync`, + }, + })) ?? { total: 0, data: [] }; + + return total === 0 ? null : data[0]; +} + +export async function runTaskForApmAgentFleetSyncAlert({ + alertsClient, + taskManagerPluginStart, +}: { + alertsClient: AlertsClient; + taskManagerPluginStart: APMPluginStartDependencies['taskManager']; +}) { + const alert = await getApmAgentFleetSyncAlert({ alertsClient }); + if (!alert) { + return; + } + const { scheduledTaskId } = alert; + if (!scheduledTaskId) { + return; + } + await taskManagerPluginStart?.runNow(scheduledTaskId); +} diff --git a/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts new file mode 100644 index 0000000000000..874540087cff5 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts @@ -0,0 +1,100 @@ +/* + * 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 { APMPlugin, APMRouteHandlerResources } from '../..'; +import { listConfigurations } from '../settings/agent_configuration/list_configurations'; +import { setupRequest } from '../helpers/setup_request'; +import { APMPluginStartDependencies } from '../../types'; +import { ExternalCallback } from 'x-pack/plugins/fleet/server'; + +export async function registerFleetPolicyCallbacks({ + plugins, + apmRuleRegistry, + config, + logger, +}: { + plugins: APMRouteHandlerResources['plugins']; + apmRuleRegistry: APMRouteHandlerResources['apmRuleRegistry']; + config: NonNullable; + logger: NonNullable; +}) { + if (!plugins.fleet) { + return; + } + const fleetPluginStart = await plugins.fleet.start(); + + registerAgentConfigExternalCallback({ + fleetPluginStart, + callbackName: 'packagePolicyCreate', + plugins, + apmRuleRegistry, + config, + logger, + }); + registerAgentConfigExternalCallback({ + fleetPluginStart, + callbackName: 'packagePolicyUpdate', + plugins, + apmRuleRegistry, + config, + logger, + }); +} + +type ExternalCallbackParams = Parameters; +type PackagePolicy = ExternalCallbackParams[0]; +type Context = ExternalCallbackParams[1]; +type Request = ExternalCallbackParams[2]; + +function registerAgentConfigExternalCallback({ + fleetPluginStart, + callbackName, + plugins, + apmRuleRegistry, + config, + logger, +}: { + fleetPluginStart: NonNullable; + callbackName: ExternalCallback[0]; + plugins: APMRouteHandlerResources['plugins']; + apmRuleRegistry: APMRouteHandlerResources['apmRuleRegistry']; + config: NonNullable; + logger: NonNullable; +}) { + const callbackFn: ExternalCallback[1] = async ( + packagePolicy: PackagePolicy, + context: Context, + request: Request + ) => { + if (packagePolicy.package?.name !== 'apm') { + return packagePolicy; + } + const setup = await setupRequest({ + context: context as any, + params: { query: { _inspect: false } }, + core: null as any, + plugins, + request, + config, + logger, + apmRuleRegistry, + }); + const configurations = await listConfigurations({ setup }); + const agentConfigValue = configurations.map((config) => ({ + service: config.service, + settings: config.settings, + })); + packagePolicy.inputs[0].config = { + agent_config: { + value: agentConfigValue, + }, + }; + return packagePolicy; + }; + + fleetPluginStart.registerExternalCallback(callbackName, callbackFn); +} diff --git a/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts b/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts new file mode 100644 index 0000000000000..c31a0e1db058f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts @@ -0,0 +1,66 @@ +/* + * 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 { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import { AgentConfiguration } from '../../../common/agent_configuration/configuration_types'; +import { APMPluginStartDependencies } from '../../types'; + +export async function syncAgentConfigsToApmPackagePolicies({ + fleetPluginStart, + savedObjectsClient, + esClient, + agentConfigurations, +}: { + fleetPluginStart: NonNullable; + savedObjectsClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + agentConfigurations: AgentConfiguration[]; +}) { + const agentConfigs = agentConfigurations.map((config) => ({ + service: config.service, + settings: config.settings, + })); + const packagePolicies = await fleetPluginStart.packagePolicyService.list( + savedObjectsClient, + { kuery: 'ingest-package-policies.package.name:apm' } + ); + + return Promise.all( + packagePolicies.items.map(async (item) => { + const { id, revision, updated_at, updated_by, ...packagePolicy } = item; + packagePolicy.inputs[0].config = { + agent_config: { + value: agentConfigs, + }, + }; + return fleetPluginStart.packagePolicyService.update( + savedObjectsClient, + esClient, + id, + packagePolicy + ); + }) + ); +} + +export async function getAgentConfigsFromApmPackagePolicy({ + savedObjectsClient, + fleetPluginStart, +}: { + savedObjectsClient: SavedObjectsClientContract; + fleetPluginStart: APMPluginStartDependencies['fleet']; +}) { + if (!fleetPluginStart) { + return; + } + const packagePolicies = await fleetPluginStart.packagePolicyService.list( + savedObjectsClient, + { kuery: 'ingest-package-policies.package.name:apm' } + ); + const { config } = packagePolicies.items[0].inputs[0]; + return config?.agent_config; +} diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index e12d089855834..419ccf0add2d2 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -22,6 +22,7 @@ import { mergeConfigs } from './index'; import { UI_SETTINGS } from '../../../../src/plugins/data/common'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { registerApmAlerts } from './lib/alerts/register_apm_alerts'; +import { registerFleetPolicyCallbacks } from './lib/fleet/register_fleet_policy_callbacks'; import { createApmTelemetry } from './lib/apm_telemetry'; import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; @@ -129,6 +130,19 @@ export class APMPlugin fieldMap: apmRuleFieldMap, }); + const resourcePlugins = mapValues(plugins, (value, key) => { + return { + setup: value, + start: () => + core.getStartServices().then((services) => { + const [, pluginsStartContracts] = services; + return pluginsStartContracts[ + key as keyof APMPluginStartDependencies + ]; + }), + }; + }) as APMRouteHandlerResources['plugins']; + registerRoutes({ core: { setup: core, @@ -138,18 +152,7 @@ export class APMPlugin config: currentConfig, repository: getGlobalApmServerRouteRepository(), apmRuleRegistry, - plugins: mapValues(plugins, (value, key) => { - return { - setup: value, - start: () => - core.getStartServices().then((services) => { - const [, pluginsStartContracts] = services; - return pluginsStartContracts[ - key as keyof APMPluginStartDependencies - ]; - }), - }; - }) as APMRouteHandlerResources['plugins'], + plugins: resourcePlugins, }); const boundGetApmIndices = async () => @@ -162,6 +165,14 @@ export class APMPlugin ml: plugins.ml, config$: mergedConfig$, logger: this.logger!.get('rule'), + getFleetPluginStart: async () => resourcePlugins.fleet?.start(), + }); + + registerFleetPolicyCallbacks({ + plugins: resourcePlugins, + apmRuleRegistry, + config: this.currentConfig, + logger: this.logger, }); return { @@ -197,7 +208,7 @@ export class APMPlugin }; } - public start(core: CoreStart) { + public start(core: CoreStart, plugins: APMPluginStartDependencies) { if (this.currentConfig == null || this.logger == null) { throw new Error('APMPlugin needs to be setup before calling start()'); } diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts index ef1ade645cc44..67b6650ae42ee 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts @@ -25,6 +25,12 @@ import { } from '../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt'; import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions'; import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { + createApmAgentFleetSyncAlert, + getApmAgentFleetSyncAlert, + runTaskForApmAgentFleetSyncAlert, +} from '../../lib/fleet/agent_config_fleet_sync_alert'; +import { getAgentConfigsFromApmPackagePolicy } from '../../lib/fleet/sync_agent_configs_to_apm_package_policies'; // get list of configurations const agentConfigurationRoute = createApmServerRoute({ @@ -114,7 +120,7 @@ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({ ]), handler: async (resources) => { const setup = await setupRequest(resources); - const { params, logger } = resources; + const { params, logger, plugins } = resources; const { body, query } = params; // if the config already exists, it is fetched and updated @@ -142,6 +148,22 @@ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({ configurationIntake: body, setup, }); + + try { + const alertingPluginStart = await plugins.alerting?.start(); + const alertsClient = alertingPluginStart?.getAlertsClientWithRequest( + resources.request + ); + const taskManagerPluginStart = await resources.plugins.taskManager?.start(); + if (alertsClient && taskManagerPluginStart) { + await runTaskForApmAgentFleetSyncAlert({ + alertsClient, + taskManagerPluginStart, + }); + } + } catch (error) { + logger.warn(`There was an error running fleet sync task: ${error}`); + } }, }); @@ -262,6 +284,57 @@ const agentConfigurationAgentNameRoute = createApmServerRoute({ }, }); +// enables syncing with fleet-managed apm package policy +const agentConfigurationFleetSync = createApmServerRoute({ + endpoint: 'POST /api/apm/settings/agent-configuration/fleet_sync', + options: { tags: ['access:apm', 'access:apm_write'] }, + handler: async (resources) => { + const { plugins } = resources; + const alertingPluginStart = await plugins.alerting?.start(); + const alertsClient = alertingPluginStart?.getAlertsClientWithRequest( + resources.request + ); + if (!alertsClient) { + throw Boom.serverUnavailable(); + } + return createApmAgentFleetSyncAlert({ alertsClient }); + }, +}); + +// returns agent config data from apm package policy +const agentConfigurationFleetSyncPackagePolicyInput = createApmServerRoute({ + endpoint: + 'GET /api/apm/settings/agent-configuration/fleet-sync/package-policy-input', + options: { tags: ['access:apm', 'access:apm_write'] }, + handler: async (resources) => { + const { plugins, context } = resources; + const alertingPluginStart = await plugins.alerting?.start(); + const alertsClient = alertingPluginStart?.getAlertsClientWithRequest( + resources.request + ); + + if (!alertsClient) { + throw Boom.serverUnavailable(); + } + const alert = await getApmAgentFleetSyncAlert({ alertsClient }); + const fleetPluginStart = await plugins.fleet?.start(); + if (!fleetPluginStart) { + throw Boom.serverUnavailable(); + } + const agent_config = await getAgentConfigsFromApmPackagePolicy({ + fleetPluginStart, + savedObjectsClient: context.core.savedObjects.client, + }); + if (!agent_config) { + throw Boom.notFound(); + } + return { + alert, + agent_config, + }; + }, +}); + export const agentConfigurationRouteRepository = createApmServerRouteRepository() .add(agentConfigurationRoute) .add(getSingleAgentConfigurationRoute) @@ -270,4 +343,6 @@ export const agentConfigurationRouteRepository = createApmServerRouteRepository( .add(agentConfigurationSearchRoute) .add(listAgentConfigurationServicesRoute) .add(listAgentConfigurationEnvironmentsRoute) - .add(agentConfigurationAgentNameRoute); + .add(agentConfigurationAgentNameRoute) + .add(agentConfigurationFleetSync) + .add(agentConfigurationFleetSyncPackagePolicyInput); diff --git a/x-pack/plugins/apm/server/types.ts b/x-pack/plugins/apm/server/types.ts index dbc220f9f6b15..c848ae7c29200 100644 --- a/x-pack/plugins/apm/server/types.ts +++ b/x-pack/plugins/apm/server/types.ts @@ -39,6 +39,10 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../task_manager/server'; +import { + FleetSetupContract as FleetPluginSetup, + FleetStartContract as FleetPluginStart, +} from '../../fleet/server'; import { APMConfig } from '.'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; @@ -115,6 +119,10 @@ interface DependencyMap { setup: DataPluginSetup; start: DataPluginStart; }; + fleet: { + setup: FleetPluginSetup; + start: FleetPluginStart; + }; } const requiredDependencies = [ @@ -139,6 +147,7 @@ const optionalDependencies = [ 'ml', 'home', 'maps', + 'fleet', ] as const; type RequiredDependencies = Pick< diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json index bb341059e2d43..192b7f4fe8c2e 100644 --- a/x-pack/plugins/apm/tsconfig.json +++ b/x-pack/plugins/apm/tsconfig.json @@ -41,6 +41,7 @@ { "path": "../rule_registry/tsconfig.json" }, { "path": "../security/tsconfig.json" }, { "path": "../task_manager/tsconfig.json" }, - { "path": "../triggers_actions_ui/tsconfig.json" } + { "path": "../triggers_actions_ui/tsconfig.json" }, + { "path": "../fleet/tsconfig.json" } ] }