Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] Inject agent config directly into APM Fleet policies #99193

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions x-pack/plugins/apm/common/alert_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = [
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/apm/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"security",
"ml",
"home",
"maps"
"maps",
"fleet"
],
"server": true,
"ui": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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;
Comment on lines +66 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you use Loading instead?

Suggested change
const isFleetSyncLoading =
packagePolicyInputStatus !== FETCH_STATUS.FAILURE &&
packagePolicyInputStatus !== FETCH_STATUS.SUCCESS;
const isFleetSyncLoading = packagePolicyInputStatus === FETCH_STATUS.LOADING

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also need it to be true when the status is NOT_INITIATED,i'll probably just include that in the check along with your suggestion.

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 (
<>
<EuiTitle size="l">
Expand All @@ -62,13 +99,38 @@ export function AgentConfigurations() {
<EuiTitle size="s">
<h2>
{i18n.translate(
'xpack.apm.agentConfig.configurationsPanelTitle',
'xpack.apm.agentConfig.configurationsPanel.title',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was there already was correct according to the Kibana i18n guidelines.

{ defaultMessage: 'Configurations' }
)}
</h2>
</EuiTitle>
</EuiFlexItem>

{!isFleetSyncLoading && !isFleetSyncUnavailable && (
<EuiFlexItem>
<div>
<EuiButton
disabled={isFleetSyncEnabled}
onClick={async () => {
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' }
)}
</EuiButton>
</div>
</EuiFlexItem>
)}
{hasConfigurations ? <CreateConfigurationButton /> : null}
</EuiFlexGroup>

Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +66 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary?

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use Type guards to avoid this @ts-ignore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see what i can do, I did it because _source has unknown property types when querying with the alertingEsClient

.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 };
},
})
);
}
4 changes: 4 additions & 0 deletions x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ 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<APMConfig>;
logger: Logger;
getFleetPluginStart: () => Promise<APMPluginStartDependencies['fleet']>;
}

export function registerApmAlerts(dependencies: RegisterRuleDependencies) {
registerTransactionDurationAlertType(dependencies);
registerTransactionDurationAnomalyAlertType(dependencies);
registerErrorCountAlertType(dependencies);
registerTransactionErrorRateAlertType(dependencies);
registerAgentConfigFleetSyncAlertType(dependencies);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading