-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,8 @@ | |
"security", | ||
"ml", | ||
"home", | ||
"maps" | ||
"maps", | ||
"fleet" | ||
], | ||
"server": true, | ||
"ui": true, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 ( | ||
<> | ||
<EuiTitle size="l"> | ||
|
@@ -62,13 +99,38 @@ export function AgentConfigurations() { | |
<EuiTitle size="s"> | ||
<h2> | ||
{i18n.translate( | ||
'xpack.apm.agentConfig.configurationsPanelTitle', | ||
'xpack.apm.agentConfig.configurationsPanel.title', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
cauemarcondes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<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> | ||
|
||
|
@@ -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", | ||
|
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use Type guards to avoid this @ts-ignore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll see what i can do, I did it because |
||
.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 }; | ||
}, | ||
}) | ||
); | ||
} |
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); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.