Skip to content

Commit 3f646d8

Browse files
committed
[APM] Inject agent config directly into APM Fleet policies (#95501)
1 parent ad9f1c3 commit 3f646d8

File tree

12 files changed

+521
-22
lines changed

12 files changed

+521
-22
lines changed

x-pack/plugins/apm/common/alert_types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export enum AlertType {
1515
TransactionErrorRate = 'apm.transaction_error_rate',
1616
TransactionDuration = 'apm.transaction_duration',
1717
TransactionDurationAnomaly = 'apm.transaction_duration_anomaly',
18+
AgentConfigFleetSync = 'apm.agent_config_fleet_sync',
1819
}
1920

2021
export const THRESHOLD_MET_GROUP_ID = 'threshold_met';
@@ -72,6 +73,15 @@ export const ALERT_TYPES_CONFIG: Record<
7273
minimumLicenseRequired: 'basic',
7374
producer: 'apm',
7475
},
76+
[AlertType.AgentConfigFleetSync]: {
77+
name: i18n.translate('xpack.apm.agentConfigFleetSyncAlert.name', {
78+
defaultMessage: 'Central agent configuration updates',
79+
}),
80+
actionGroups: [THRESHOLD_MET_GROUP],
81+
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
82+
minimumLicenseRequired: 'basic',
83+
producer: 'apm',
84+
},
7585
};
7686

7787
export const ANOMALY_ALERT_SEVERITY_TYPES = [

x-pack/plugins/apm/kibana.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"security",
2323
"ml",
2424
"home",
25-
"maps"
25+
"maps",
26+
"fleet"
2627
],
2728
"server": true,
2829
"ui": true,

x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,24 @@ import {
1717
} from '@elastic/eui';
1818
import { i18n } from '@kbn/i18n';
1919
import { isEmpty } from 'lodash';
20-
import React from 'react';
20+
import React, { useState } from 'react';
2121
import { useLocation } from 'react-router-dom';
22+
import { callApmApi } from '../../../../services/rest/createCallApmApi';
2223
import { useTrackPageview } from '../../../../../../observability/public';
2324
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
24-
import { useFetcher } from '../../../../hooks/use_fetcher';
25+
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
2526
import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks';
2627
import { AgentConfigurationList } from './List';
2728

2829
const INITIAL_DATA = { configurations: [] };
2930

31+
async function enableFleetSync() {
32+
return await callApmApi({
33+
endpoint: 'POST /api/apm/settings/agent-configuration/fleet_sync',
34+
signal: null,
35+
});
36+
}
37+
3038
export function AgentConfigurations() {
3139
const { refetch, data = INITIAL_DATA, status } = useFetcher(
3240
(callApmApi) =>
@@ -38,8 +46,37 @@ export function AgentConfigurations() {
3846
useTrackPageview({ app: 'apm', path: 'agent_configuration' });
3947
useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 });
4048

49+
const {
50+
data: packagePolicyInput,
51+
status: packagePolicyInputStatus,
52+
error: packagePolicyError,
53+
refetch: refetchPackagePolicyInput,
54+
} = useFetcher(
55+
(callApmApi) =>
56+
callApmApi({
57+
endpoint:
58+
'GET /api/apm/settings/agent-configuration/fleet-sync/package-policy-input',
59+
}),
60+
[],
61+
{ preservePreviousData: false, showToastOnError: false }
62+
);
63+
4164
const hasConfigurations = !isEmpty(data.configurations);
4265

66+
const isFleetSyncLoading =
67+
packagePolicyInputStatus !== FETCH_STATUS.FAILURE &&
68+
packagePolicyInputStatus !== FETCH_STATUS.SUCCESS;
69+
const isFleetSyncUnavailable = packagePolicyError?.response?.status === 503;
70+
const isFleetSyncEnabled = Boolean(
71+
packagePolicyInputStatus === FETCH_STATUS.SUCCESS &&
72+
packagePolicyInput?.agent_config.value &&
73+
packagePolicyInput?.alert
74+
);
75+
76+
const [isFleetSyncEnableLoading, setIsFleetSyncEnableLoading] = useState(
77+
false
78+
);
79+
4380
return (
4481
<>
4582
<EuiTitle size="l">
@@ -62,13 +99,38 @@ export function AgentConfigurations() {
6299
<EuiTitle size="s">
63100
<h2>
64101
{i18n.translate(
65-
'xpack.apm.agentConfig.configurationsPanelTitle',
102+
'xpack.apm.agentConfig.configurationsPanel.title',
66103
{ defaultMessage: 'Configurations' }
67104
)}
68105
</h2>
69106
</EuiTitle>
70107
</EuiFlexItem>
71-
108+
{!isFleetSyncLoading && !isFleetSyncUnavailable && (
109+
<EuiFlexItem>
110+
<div>
111+
<EuiButton
112+
disabled={isFleetSyncEnabled}
113+
onClick={async () => {
114+
setIsFleetSyncEnableLoading(true);
115+
await enableFleetSync();
116+
refetchPackagePolicyInput();
117+
setIsFleetSyncEnableLoading(false);
118+
}}
119+
isLoading={isFleetSyncEnableLoading}
120+
>
121+
{isFleetSyncEnabled
122+
? i18n.translate(
123+
'xpack.apm.agentConfig.configurationsPanel.fleetSyncingEnabledLabel',
124+
{ defaultMessage: 'Syncing with fleet policy' }
125+
)
126+
: i18n.translate(
127+
'xpack.apm.agentConfig.configurationsPanel.enableFleetSyncButtonLabel',
128+
{ defaultMessage: 'Sync with fleet policy' }
129+
)}
130+
</EuiButton>
131+
</div>
132+
</EuiFlexItem>
133+
)}
72134
{hasConfigurations ? <CreateConfigurationButton /> : null}
73135
</EuiFlexGroup>
74136

@@ -98,7 +160,7 @@ function CreateConfigurationButton() {
98160
content={
99161
!canSave &&
100162
i18n.translate(
101-
'xpack.apm.agentConfig.configurationsPanelTitle.noPermissionTooltipLabel',
163+
'xpack.apm.agentConfig.configurationsPanel.title.noPermissionTooltipLabel',
102164
{
103165
defaultMessage:
104166
"Your user role doesn't have permissions to create agent configurations",
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { schema } from '@kbn/config-schema';
9+
import { take } from 'rxjs/operators';
10+
import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
11+
import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
12+
import { alertingEsClient } from './alerting_es_client';
13+
import { RegisterRuleDependencies } from './register_apm_alerts';
14+
import { createAPMLifecycleRuleType } from './create_apm_lifecycle_rule_type';
15+
import { convertConfigSettingsToString } from '../settings/agent_configuration/convert_settings_to_string';
16+
import { AgentConfiguration } from '../../../common/agent_configuration/configuration_types';
17+
import { syncAgentConfigsToApmPackagePolicies } from '../fleet/sync_agent_configs_to_apm_package_policies';
18+
19+
const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.AgentConfigFleetSync];
20+
21+
export function registerAgentConfigFleetSyncAlertType({
22+
registry,
23+
config$,
24+
getFleetPluginStart,
25+
}: RegisterRuleDependencies) {
26+
registry.registerType(
27+
createAPMLifecycleRuleType({
28+
id: AlertType.AgentConfigFleetSync,
29+
name: alertTypeConfig.name,
30+
actionGroups: alertTypeConfig.actionGroups,
31+
defaultActionGroupId: alertTypeConfig.defaultActionGroupId,
32+
validate: {
33+
params: schema.any(),
34+
},
35+
actionVariables: {
36+
context: [],
37+
},
38+
producer: 'apm',
39+
minimumLicenseRequired: 'basic',
40+
executor: async ({ services, state }) => {
41+
const config = await config$.pipe(take(1)).toPromise();
42+
const indices = await getApmIndices({
43+
config,
44+
savedObjectsClient: services.savedObjectsClient,
45+
});
46+
47+
const searchParams = {
48+
index: indices['apmAgentConfigurationIndex'],
49+
body: {
50+
size: 1,
51+
query: {
52+
match_all: {},
53+
},
54+
sort: { '@timestamp': 'desc' as const },
55+
},
56+
};
57+
58+
const response = await alertingEsClient({
59+
scopedClusterClient: services.scopedClusterClient,
60+
params: searchParams,
61+
});
62+
if (response.hits.total.value === 0) {
63+
return {};
64+
}
65+
66+
const { ['@timestamp']: lastTimestamp } = response.hits.hits[0]
67+
._source as { '@timestamp': number };
68+
// @ts-ignore
69+
if (lastTimestamp > state.lastTimestamp) {
70+
const fleetPluginStart = await getFleetPluginStart();
71+
if (fleetPluginStart) {
72+
services.logger.info(
73+
`New agent configurations detected. Updating fleet policy...`
74+
);
75+
const configurationsSearchResponse = await alertingEsClient({
76+
scopedClusterClient: services.scopedClusterClient,
77+
params: {
78+
index: indices['apmAgentConfigurationIndex'],
79+
body: { size: 200, query: { match_all: {} } },
80+
},
81+
});
82+
const agentConfigurations = configurationsSearchResponse.hits.hits
83+
// @ts-ignore
84+
.map(convertConfigSettingsToString)
85+
.map((hit) => hit._source) as AgentConfiguration[];
86+
await syncAgentConfigsToApmPackagePolicies({
87+
fleetPluginStart,
88+
savedObjectsClient: services.savedObjectsClient,
89+
esClient: services.scopedClusterClient.asCurrentUser,
90+
agentConfigurations,
91+
});
92+
services.logger.info(`Policy updated.`);
93+
}
94+
}
95+
return { lastTimestamp };
96+
},
97+
})
98+
);
99+
}

x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ import { APMConfig } from '../..';
1414
import { MlPluginSetup } from '../../../../ml/server';
1515
import { registerTransactionErrorRateAlertType } from './register_transaction_error_rate_alert_type';
1616
import { APMRuleRegistry } from '../../plugin';
17+
import { registerAgentConfigFleetSyncAlertType } from './register_agent_config_fleet_sync_alert_type';
18+
import { APMPluginStartDependencies } from '../../types';
1719

1820
export interface RegisterRuleDependencies {
1921
registry: APMRuleRegistry;
2022
ml?: MlPluginSetup;
2123
config$: Observable<APMConfig>;
2224
logger: Logger;
25+
getFleetPluginStart: () => Promise<APMPluginStartDependencies['fleet']>;
2326
}
2427

2528
export function registerApmAlerts(dependencies: RegisterRuleDependencies) {
2629
registerTransactionDurationAlertType(dependencies);
2730
registerTransactionDurationAnomalyAlertType(dependencies);
2831
registerErrorCountAlertType(dependencies);
2932
registerTransactionErrorRateAlertType(dependencies);
33+
registerAgentConfigFleetSyncAlertType(dependencies);
3034
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { AlertsClient } from 'x-pack/plugins/alerting/server';
9+
import { APMPluginStartDependencies } from '../../types';
10+
11+
export async function createApmAgentFleetSyncAlert({
12+
alertsClient,
13+
}: {
14+
alertsClient: AlertsClient;
15+
}) {
16+
return alertsClient?.create({
17+
data: {
18+
enabled: true,
19+
name: 'APM - Agent config fleet sync',
20+
tags: ['apm'],
21+
alertTypeId: 'apm.agent_config_fleet_sync',
22+
consumer: 'apm',
23+
schedule: { interval: '1m' },
24+
actions: [],
25+
params: {},
26+
throttle: null,
27+
notifyWhen: null,
28+
},
29+
});
30+
}
31+
export async function getApmAgentFleetSyncAlert({
32+
alertsClient,
33+
}: {
34+
alertsClient: AlertsClient;
35+
}) {
36+
const { total, data } = (await alertsClient?.find({
37+
options: {
38+
filter: `alert.attributes.alertTypeId: apm.agent_config_fleet_sync`,
39+
},
40+
})) ?? { total: 0, data: [] };
41+
42+
return total === 0 ? null : data[0];
43+
}
44+
45+
export async function runTaskForApmAgentFleetSyncAlert({
46+
alertsClient,
47+
taskManagerPluginStart,
48+
}: {
49+
alertsClient: AlertsClient;
50+
taskManagerPluginStart: APMPluginStartDependencies['taskManager'];
51+
}) {
52+
const alert = await getApmAgentFleetSyncAlert({ alertsClient });
53+
if (!alert) {
54+
return;
55+
}
56+
const { scheduledTaskId } = alert;
57+
if (!scheduledTaskId) {
58+
return;
59+
}
60+
await taskManagerPluginStart?.runNow(scheduledTaskId);
61+
}

0 commit comments

Comments
 (0)