[APM] Migrate to data streams - Fleet on Cloud (#102682)
* [APM] Adds migration to fleet-managed APM server in APM UI Settings (#100657) * adds useStrictParams option to apm server routes to allow unknown record type in param.bopy * Adds checks for required roles, policies, and config before allowing user to initiate migration * refactored and cleaned up server-side code * i18n and link to Fleet * fixes linting issues and unit tests * updates the apm package policy to 0.3.0 and adds some missing config mappings * PR feedback * Handles case where the cloud policy doesn't exist * Reverts the addition of the useStrictParams option since strictKeysRt now supports records * fixes default input var values and uses correct published package version * displays reasons the switch to data streams is disabled * Store apm-server schema with the internal saved objects client
This commit is contained in:
parent
06eb881781
commit
bfd801078f
|
@ -15,3 +15,7 @@ export const APM_INDICES_SAVED_OBJECT_ID = 'apm-indices';
|
|||
// APM telemetry
|
||||
export const APM_TELEMETRY_SAVED_OBJECT_TYPE = 'apm-telemetry';
|
||||
export const APM_TELEMETRY_SAVED_OBJECT_ID = 'apm-telemetry';
|
||||
|
||||
// APM Server schema
|
||||
export const APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE = 'apm-server-schema';
|
||||
export const APM_SERVER_SCHEMA_SAVED_OBJECT_ID = 'apm-server-schema';
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import {
|
||||
EuiConfirmModal,
|
||||
EuiCallOut,
|
||||
EuiCheckbox,
|
||||
EuiSpacer,
|
||||
EuiCodeBlock,
|
||||
htmlIdGenerator,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink';
|
||||
|
||||
interface Props {
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
unsupportedConfigs: Array<{ key: string; value: string }>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
export function ConfirmSwitchModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
unsupportedConfigs,
|
||||
isLoading,
|
||||
}: Props) {
|
||||
const [isConfirmChecked, setIsConfirmChecked] = useState(false);
|
||||
const hasUnsupportedConfigs = !!unsupportedConfigs.length;
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate('xpack.apm.settings.schema.confirm.title', {
|
||||
defaultMessage: 'Please confirm your choice',
|
||||
})}
|
||||
cancelButtonText={i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.cancelText',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
onCancel={onCancel}
|
||||
confirmButtonText={i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.switchButtonText',
|
||||
{
|
||||
defaultMessage: 'Switch to data streams',
|
||||
}
|
||||
)}
|
||||
defaultFocusedButton="confirm"
|
||||
onConfirm={onConfirm}
|
||||
confirmButtonDisabled={!isConfirmChecked}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.settings.schema.confirm.descriptionText', {
|
||||
defaultMessage:
|
||||
'If you have custom dashboards, machine learning jobs, or source maps that use classic APM indices, you must reconfigure them for data streams. Stack monitoring is not currently supported with Fleet-managed APM.',
|
||||
})}
|
||||
</p>
|
||||
{!hasUnsupportedConfigs && (
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.unsupportedConfigs.descriptionText',
|
||||
{
|
||||
defaultMessage: `Compatible custom apm-server.yml user settings will be moved to Fleet Server settings for you. We'll let you know which settings are incompatible before removing them.`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.irreversibleWarning.title',
|
||||
{
|
||||
defaultMessage: `Switching to data streams is an irreversible action`,
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.irreversibleWarning.message',
|
||||
{
|
||||
defaultMessage: `It might temporarily affect your APM data collection while the migration is in progress. The process of migrating should only take a few minutes.`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
{hasUnsupportedConfigs && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.unsupportedConfigs.title',
|
||||
{
|
||||
defaultMessage: `The following apm-server.yml user settings are incompatible and will be removed`,
|
||||
}
|
||||
)}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<EuiCodeBlock language="yaml">
|
||||
{unsupportedConfigs
|
||||
.map(({ key, value }) => `${key}: ${JSON.stringify(value)}`)
|
||||
.join('\n')}
|
||||
</EuiCodeBlock>
|
||||
<p>
|
||||
<ElasticDocsLink
|
||||
section="/cloud"
|
||||
path="/ec-manage-apm-settings.html"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.apmServerSettingsCloudLinkText',
|
||||
{ defaultMessage: 'Go to APM Server settings in Cloud' }
|
||||
)}
|
||||
</ElasticDocsLink>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
<EuiCheckbox
|
||||
id={htmlIdGenerator()()}
|
||||
label={i18n.translate(
|
||||
'xpack.apm.settings.schema.confirm.checkboxLabel',
|
||||
{
|
||||
defaultMessage: `I confirm that I wish to switch to data streams`,
|
||||
}
|
||||
)}
|
||||
checked={isConfirmChecked}
|
||||
onChange={(e) => {
|
||||
setIsConfirmChecked(e.target.checked);
|
||||
}}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</p>
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { NotificationsStart } from 'kibana/public';
|
||||
import { SchemaOverview } from './schema_overview';
|
||||
import { ConfirmSwitchModal } from './confirm_switch_modal';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import {
|
||||
callApmApi,
|
||||
APIReturnType,
|
||||
} from '../../../../services/rest/createCallApmApi';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
|
||||
type FleetMigrationCheckResponse = APIReturnType<'GET /api/apm/fleet/migration_check'>;
|
||||
|
||||
export function Schema() {
|
||||
const { toasts } = useApmPluginContext().core.notifications;
|
||||
const [isSwitchActive, setIsSwitchActive] = useState(false);
|
||||
const [isLoadingMigration, setIsLoadingMigration] = useState(false);
|
||||
const [isLoadingConfirmation, setIsLoadingConfirmation] = useState(false);
|
||||
const [unsupportedConfigs, setUnsupportedConfigs] = useState<
|
||||
Array<{ key: string; value: any }>
|
||||
>([]);
|
||||
|
||||
const {
|
||||
refetch,
|
||||
data = {} as FleetMigrationCheckResponse,
|
||||
status,
|
||||
} = useFetcher(
|
||||
(callApi) => callApi({ endpoint: 'GET /api/apm/fleet/migration_check' }),
|
||||
[],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
const isLoading = status !== FETCH_STATUS.SUCCESS;
|
||||
const cloudApmMigrationEnabled = !!data.cloud_apm_migration_enabled;
|
||||
const hasCloudAgentPolicy = !!data.has_cloud_agent_policy;
|
||||
const hasCloudApmPackagePolicy = !!data.has_cloud_apm_package_policy;
|
||||
const hasRequiredRole = !!data.has_required_role;
|
||||
return (
|
||||
<>
|
||||
<SchemaOverview
|
||||
onSwitch={async () => {
|
||||
setIsLoadingConfirmation(true);
|
||||
const unsupported = await getUnsupportedApmServerConfigs(toasts);
|
||||
if (!unsupported) {
|
||||
setIsLoadingConfirmation(false);
|
||||
return;
|
||||
}
|
||||
setUnsupportedConfigs(unsupported);
|
||||
setIsLoadingConfirmation(false);
|
||||
setIsSwitchActive(true);
|
||||
}}
|
||||
isMigrated={hasCloudApmPackagePolicy}
|
||||
isLoading={isLoading}
|
||||
isLoadingConfirmation={isLoadingConfirmation}
|
||||
cloudApmMigrationEnabled={cloudApmMigrationEnabled}
|
||||
hasCloudAgentPolicy={hasCloudAgentPolicy}
|
||||
hasRequiredRole={hasRequiredRole}
|
||||
/>
|
||||
{isSwitchActive && (
|
||||
<ConfirmSwitchModal
|
||||
isLoading={isLoadingMigration}
|
||||
onConfirm={async () => {
|
||||
setIsLoadingMigration(true);
|
||||
const apmPackagePolicy = await createCloudApmPackagePolicy(toasts);
|
||||
if (!apmPackagePolicy) {
|
||||
setIsLoadingMigration(false);
|
||||
return;
|
||||
}
|
||||
setIsSwitchActive(false);
|
||||
refetch();
|
||||
}}
|
||||
onCancel={() => {
|
||||
if (isLoadingMigration) {
|
||||
return;
|
||||
}
|
||||
setIsSwitchActive(false);
|
||||
}}
|
||||
unsupportedConfigs={unsupportedConfigs}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function getUnsupportedApmServerConfigs(
|
||||
toasts: NotificationsStart['toasts']
|
||||
) {
|
||||
try {
|
||||
const { unsupported } = await callApmApi({
|
||||
endpoint: 'GET /api/apm/fleet/apm_server_schema/unsupported',
|
||||
signal: null,
|
||||
});
|
||||
return unsupported;
|
||||
} catch (error) {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate(
|
||||
'xpack.apm.settings.unsupportedConfigs.errorToast.title',
|
||||
{
|
||||
defaultMessage: 'Unable to fetch APM Server settings',
|
||||
}
|
||||
),
|
||||
text: error.body?.message || error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createCloudApmPackagePolicy(
|
||||
toasts: NotificationsStart['toasts']
|
||||
) {
|
||||
try {
|
||||
const {
|
||||
cloud_apm_package_policy: cloudApmPackagePolicy,
|
||||
} = await callApmApi({
|
||||
endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy',
|
||||
signal: null,
|
||||
});
|
||||
return cloudApmPackagePolicy;
|
||||
} catch (error) {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate(
|
||||
'xpack.apm.settings.createApmPackagePolicy.errorToast.title',
|
||||
{
|
||||
defaultMessage:
|
||||
'Unable to create APM package policy on cloud agent policy',
|
||||
}
|
||||
),
|
||||
text: error.body?.message || error.message,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* 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';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiCard,
|
||||
EuiIcon,
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiLoadingSpinner,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink';
|
||||
import rocketLaunchGraphic from './blog-rocket-720x420.png';
|
||||
import { APMLink } from '../../../shared/Links/apm/APMLink';
|
||||
import { useFleetCloudAgentPolicyHref } from '../../../shared/Links/kibana';
|
||||
|
||||
interface Props {
|
||||
onSwitch: () => void;
|
||||
isMigrated: boolean;
|
||||
isLoading: boolean;
|
||||
isLoadingConfirmation: boolean;
|
||||
cloudApmMigrationEnabled: boolean;
|
||||
hasCloudAgentPolicy: boolean;
|
||||
hasRequiredRole: boolean;
|
||||
}
|
||||
export function SchemaOverview({
|
||||
onSwitch,
|
||||
isMigrated,
|
||||
isLoading,
|
||||
isLoadingConfirmation,
|
||||
cloudApmMigrationEnabled,
|
||||
hasCloudAgentPolicy,
|
||||
hasRequiredRole,
|
||||
}: Props) {
|
||||
const fleetCloudAgentPolicyHref = useFleetCloudAgentPolicyHref();
|
||||
const isDisabled =
|
||||
!cloudApmMigrationEnabled || !hasCloudAgentPolicy || !hasRequiredRole;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<SchemaOverviewHeading />
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isMigrated) {
|
||||
return (
|
||||
<>
|
||||
<SchemaOverviewHeading />
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiCard
|
||||
icon={
|
||||
<EuiIcon
|
||||
size="xxl"
|
||||
type="checkInCircleFilled"
|
||||
color="success"
|
||||
/>
|
||||
}
|
||||
title={i18n.translate('xpack.apm.settings.schema.success.title', {
|
||||
defaultMessage: 'Data streams successfully setup!',
|
||||
})}
|
||||
description={i18n.translate(
|
||||
'xpack.apm.settings.schema.success.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Your APM integration is now setup and ready to receive data from your currently instrumented agents. Feel free to review the policies applied to your integtration.',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
<div>
|
||||
<EuiButton href={fleetCloudAgentPolicyHref}>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.success.viewIntegrationInFleet.buttonText',
|
||||
{ defaultMessage: 'View the APM integration in Fleet' }
|
||||
)}
|
||||
</EuiButton>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.settings.schema.success.returnText"
|
||||
defaultMessage="or simply return to the {serviceInventoryLink}."
|
||||
values={{
|
||||
serviceInventoryLink: (
|
||||
<APMLink path="/services">
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.success.returnText.serviceInventoryLink',
|
||||
{ defaultMessage: 'Service inventory' }
|
||||
)}
|
||||
</APMLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SchemaOverviewHeading />
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type="documents" />}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.classicIndices.title',
|
||||
{ defaultMessage: 'Classic APM indices' }
|
||||
)}
|
||||
display="subdued"
|
||||
description={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.classicIndices.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'You are currently using classic APM indices for your data. This data schema is going away and is being replaced by data streams in Elastic Stack version 8.0.',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
<div>
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.classicIndices.currentSetup',
|
||||
{ defaultMessage: 'Current setup' }
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
betaBadgeLabel={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.betaBadge.label',
|
||||
{ defaultMessage: 'Beta' }
|
||||
)}
|
||||
betaBadgeTitle={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.betaBadge.title',
|
||||
{ defaultMessage: 'Data streams' }
|
||||
)}
|
||||
betaBadgeTooltipContent={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.betaBadge.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'The switch to data streams is not GA. Please help us by reporting any bugs.',
|
||||
}
|
||||
)}
|
||||
image={
|
||||
<div>
|
||||
<img src={rocketLaunchGraphic} alt="rocket launch" />
|
||||
</div>
|
||||
}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.title',
|
||||
{ defaultMessage: 'Data streams' }
|
||||
)}
|
||||
description={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Going forward, any newly ingested data gets stored in data streams. Previously ingested data remains in classic APM indices. The APM and UX apps will continue to support both indices.',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
<div>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={getDisabledReason({
|
||||
cloudApmMigrationEnabled,
|
||||
hasCloudAgentPolicy,
|
||||
hasRequiredRole,
|
||||
})}
|
||||
>
|
||||
<EuiButton
|
||||
fill
|
||||
isLoading={isLoadingConfirmation}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.dataStreams.buttonText',
|
||||
{ defaultMessage: 'Switch to data streams' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
}
|
||||
onClick={onSwitch}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup justifyContent="center" gutterSize="s">
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.calloutNote.title',
|
||||
{ defaultMessage: 'Please note before switching' }
|
||||
)}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.migrate.calloutNote.message',
|
||||
{
|
||||
defaultMessage:
|
||||
'If you have custom dashboards, machine learning jobs, or source maps that use classic APM indices, you must reconfigure them for data streams.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function SchemaOverviewHeading() {
|
||||
return (
|
||||
<>
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.apm.settings.schema.descriptionText"
|
||||
defaultMessage="We have created a simple and seamless process for switching from the classic APM indices to immediately take advantage of the new data streams features. Beware this action is {irreversibleEmphasis} and can only be performed by a {superuserEmphasis} with access to Fleet. Learn more about {dataStreamsDocLink}."
|
||||
values={{
|
||||
irreversibleEmphasis: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.descriptionText.irreversibleEmphasisText',
|
||||
{ defaultMessage: 'irreversible' }
|
||||
)}
|
||||
</strong>
|
||||
),
|
||||
superuserEmphasis: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.descriptionText.superuserEmphasisText',
|
||||
{ defaultMessage: 'superuser' }
|
||||
)}
|
||||
</strong>
|
||||
),
|
||||
dataStreamsDocLink: (
|
||||
<ElasticDocsLink
|
||||
section="/elasticsearch/reference"
|
||||
path="/data-streams.html"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.settings.schema.descriptionText.dataStreamsDocLinkText',
|
||||
{ defaultMessage: 'data streams' }
|
||||
)}
|
||||
</ElasticDocsLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.settings.schema.title', {
|
||||
defaultMessage: 'Schema',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getDisabledReason({
|
||||
cloudApmMigrationEnabled,
|
||||
hasCloudAgentPolicy,
|
||||
hasRequiredRole,
|
||||
}: {
|
||||
cloudApmMigrationEnabled: boolean;
|
||||
hasCloudAgentPolicy: boolean;
|
||||
hasRequiredRole: boolean;
|
||||
}) {
|
||||
const reasons: string[] = [];
|
||||
if (!cloudApmMigrationEnabled) {
|
||||
reasons.push(
|
||||
i18n.translate(
|
||||
'xpack.apm.settings.schema.disabledReason.cloudApmMigrationEnabled',
|
||||
{ defaultMessage: 'Cloud migration is not enabled' }
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!hasCloudAgentPolicy) {
|
||||
reasons.push(
|
||||
i18n.translate(
|
||||
'xpack.apm.settings.schema.disabledReason.hasCloudAgentPolicy',
|
||||
{ defaultMessage: 'Cloud agent policy does not exist' }
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!hasRequiredRole) {
|
||||
reasons.push(
|
||||
i18n.translate(
|
||||
'xpack.apm.settings.schema.disabledReason.hasRequiredRole',
|
||||
{ defaultMessage: 'User does not have superuser role' }
|
||||
)
|
||||
);
|
||||
}
|
||||
if (reasons.length) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.apm.settings.schema.disabledReason"
|
||||
defaultMessage="Switch to data streams is unavailable: {reasons}"
|
||||
values={{
|
||||
reasons: (
|
||||
<ul>
|
||||
{reasons.map((reasonText, index) => (
|
||||
<li key={index}>- {reasonText}</li>
|
||||
))}
|
||||
</ul>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import { AgentConfigurations } from '../app/Settings/AgentConfigurations';
|
|||
import { AnomalyDetection } from '../app/Settings/anomaly_detection';
|
||||
import { ApmIndices } from '../app/Settings/ApmIndices';
|
||||
import { CustomizeUI } from '../app/Settings/CustomizeUI';
|
||||
import { Schema } from '../app/Settings/schema';
|
||||
import { TraceLink } from '../app/TraceLink';
|
||||
import { TransactionLink } from '../app/transaction_link';
|
||||
import { TransactionDetails } from '../app/transaction_details';
|
||||
|
@ -250,6 +251,14 @@ function SettingsCustomizeUI() {
|
|||
);
|
||||
}
|
||||
|
||||
function SettingsSchema() {
|
||||
return (
|
||||
<SettingsTemplate selectedTab="schema">
|
||||
<Schema />
|
||||
</SettingsTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditAgentConfigurationRouteView(props: RouteComponentProps) {
|
||||
const { search } = props.history.location;
|
||||
|
||||
|
@ -315,6 +324,10 @@ const SettingsCustomizeUITitle = i18n.translate(
|
|||
'xpack.apm.views.settings.customizeUI.title',
|
||||
{ defaultMessage: 'Customize app' }
|
||||
);
|
||||
const SettingsSchemaTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.schema.title',
|
||||
{ defaultMessage: 'Schema' }
|
||||
);
|
||||
const SettingsAnomalyDetectionTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.anomalyDetection.title',
|
||||
{ defaultMessage: 'Anomaly detection' }
|
||||
|
@ -395,6 +408,12 @@ export const apmRouteConfig: APMRouteDefinition[] = [
|
|||
component: SettingsCustomizeUI,
|
||||
breadcrumb: SettingsCustomizeUITitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/schema',
|
||||
component: SettingsSchema,
|
||||
breadcrumb: SettingsSchemaTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/anomaly-detection',
|
||||
|
|
|
@ -20,7 +20,8 @@ type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
|||
| 'agent-configurations'
|
||||
| 'anomaly-detection'
|
||||
| 'apm-indices'
|
||||
| 'customize-ui';
|
||||
| 'customize-ui'
|
||||
| 'schema';
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
|
@ -100,6 +101,13 @@ function getTabs({
|
|||
}),
|
||||
href: getAPMHref({ basePath, path: `/settings/apm-indices`, search }),
|
||||
},
|
||||
{
|
||||
key: 'schema',
|
||||
label: i18n.translate('xpack.apm.settings.schema', {
|
||||
defaultMessage: 'Schema',
|
||||
}),
|
||||
href: getAPMHref({ basePath, path: `/settings/schema`, search }),
|
||||
},
|
||||
];
|
||||
|
||||
return tabs
|
||||
|
|
|
@ -10,7 +10,13 @@ import React from 'react';
|
|||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
|
||||
// union type constisting of valid guide sections that we link to
|
||||
type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server' | '/kibana';
|
||||
type DocsSection =
|
||||
| '/apm/get-started'
|
||||
| '/x-pack'
|
||||
| '/apm/server'
|
||||
| '/kibana'
|
||||
| '/elasticsearch/reference'
|
||||
| '/cloud';
|
||||
|
||||
interface Props extends EuiLinkAnchorProps {
|
||||
section: DocsSection;
|
||||
|
@ -20,7 +26,7 @@ interface Props extends EuiLinkAnchorProps {
|
|||
export function ElasticDocsLink({ section, path, children, ...rest }: Props) {
|
||||
const { docLinks } = useApmPluginContext().core;
|
||||
const baseUrl = docLinks.ELASTIC_WEBSITE_URL;
|
||||
const version = docLinks.DOC_LINK_VERSION;
|
||||
const version = section === '/cloud' ? 'current' : docLinks.DOC_LINK_VERSION;
|
||||
const href = `${baseUrl}guide/en${section}/${version}${path}`;
|
||||
|
||||
return typeof children === 'function' ? (
|
||||
|
|
|
@ -17,3 +17,12 @@ export function useUpgradeAssistantHref() {
|
|||
|
||||
return getUpgradeAssistantHref(core.http.basePath);
|
||||
}
|
||||
|
||||
export function useFleetCloudAgentPolicyHref() {
|
||||
const {
|
||||
core: {
|
||||
http: { basePath },
|
||||
},
|
||||
} = useApmPluginContext();
|
||||
return basePath.prepend('/app/fleet#/policies/policy-elastic-agent-on-cloud');
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ describe('mergeConfigs', () => {
|
|||
ui: { enabled: false },
|
||||
enabled: true,
|
||||
metricsInterval: 2000,
|
||||
agent: { migrations: { enabled: true } },
|
||||
} as APMXPackConfig;
|
||||
|
||||
expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
|
||||
|
@ -35,6 +36,7 @@ describe('mergeConfigs', () => {
|
|||
'apm_oss.transactionIndices': 'apm-*-transaction-*',
|
||||
'xpack.apm.metricsInterval': 2000,
|
||||
'xpack.apm.ui.enabled': false,
|
||||
'xpack.apm.agent.migrations.enabled': true,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -47,7 +49,7 @@ describe('mergeConfigs', () => {
|
|||
fleetMode: true,
|
||||
} as APMOSSConfig;
|
||||
|
||||
const apmConfig = { ui: {} } as APMXPackConfig;
|
||||
const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig;
|
||||
|
||||
expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
|
||||
'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*',
|
||||
|
@ -66,7 +68,7 @@ describe('mergeConfigs', () => {
|
|||
fleetMode: false,
|
||||
} as APMOSSConfig;
|
||||
|
||||
const apmConfig = { ui: {} } as APMXPackConfig;
|
||||
const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig;
|
||||
|
||||
expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
|
||||
'apm_oss.errorIndices': 'apm-*-error-*',
|
||||
|
|
|
@ -49,6 +49,11 @@ export const config = {
|
|||
maxServiceEnvironments: schema.number({ defaultValue: 100 }),
|
||||
maxServiceSelection: schema.number({ defaultValue: 50 }),
|
||||
profilingEnabled: schema.boolean({ defaultValue: false }),
|
||||
agent: schema.object({
|
||||
migrations: schema.object({
|
||||
enabled: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -94,6 +99,7 @@ export function mergeConfigs(
|
|||
'xpack.apm.searchAggregatedTransactions':
|
||||
apmConfig.searchAggregatedTransactions,
|
||||
'xpack.apm.metricsInterval': apmConfig.metricsInterval,
|
||||
'xpack.apm.agent.migrations.enabled': apmConfig.agent.migrations.enabled,
|
||||
};
|
||||
|
||||
if (apmOssConfig.fleetMode) {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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,
|
||||
Logger,
|
||||
} from 'kibana/server';
|
||||
import {
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_ID,
|
||||
} from '../../../common/apm_saved_object_constants';
|
||||
import { APMPluginStartDependencies } from '../../types';
|
||||
import { getApmPackagePolicyDefinition } from './get_apm_package_policy_definition';
|
||||
|
||||
export async function createCloudApmPackgePolicy({
|
||||
fleetPluginStart,
|
||||
savedObjectsClient,
|
||||
esClient,
|
||||
logger,
|
||||
}: {
|
||||
fleetPluginStart: NonNullable<APMPluginStartDependencies['fleet']>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
}) {
|
||||
const { attributes } = await savedObjectsClient.get(
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_ID
|
||||
);
|
||||
const apmServerSchema: Record<string, any> = JSON.parse(
|
||||
(attributes as { schemaJson: string }).schemaJson
|
||||
);
|
||||
const apmPackagePolicyDefinition = getApmPackagePolicyDefinition(
|
||||
apmServerSchema
|
||||
);
|
||||
logger.info(`Fleet migration on Cloud - apmPackagePolicy create start`);
|
||||
const apmPackagePolicy = await fleetPluginStart.packagePolicyService.create(
|
||||
savedObjectsClient,
|
||||
esClient,
|
||||
apmPackagePolicyDefinition,
|
||||
{ force: true, bumpRevision: true }
|
||||
);
|
||||
logger.info(`Fleet migration on Cloud - apmPackagePolicy create end`);
|
||||
return apmPackagePolicy;
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 {
|
||||
POLICY_ELASTIC_AGENT_ON_CLOUD,
|
||||
APM_PACKAGE_NAME,
|
||||
} from './get_cloud_apm_package_policy';
|
||||
|
||||
export function getApmPackagePolicyDefinition(
|
||||
apmServerSchema: Record<string, any>
|
||||
) {
|
||||
return {
|
||||
name: 'apm',
|
||||
namespace: 'default',
|
||||
enabled: true,
|
||||
policy_id: POLICY_ELASTIC_AGENT_ON_CLOUD,
|
||||
output_id: '',
|
||||
inputs: [
|
||||
{
|
||||
type: 'apm',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
vars: getApmPackageInputVars(apmServerSchema),
|
||||
},
|
||||
],
|
||||
package: {
|
||||
name: APM_PACKAGE_NAME,
|
||||
version: '0.3.0-dev.1',
|
||||
title: 'Elastic APM',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getApmPackageInputVars(apmServerSchema: Record<string, any>) {
|
||||
const apmServerConfigs = Object.entries(
|
||||
apmConfigMapping
|
||||
).map(([key, { name, type }]) => ({ key, name, type }));
|
||||
|
||||
const inputVars: Record<
|
||||
string,
|
||||
{ type: string; value: any }
|
||||
> = apmServerConfigs.reduce((acc, { key, name, type }) => {
|
||||
const value = apmServerSchema[key] ?? ''; // defaults to an empty string to be edited in Fleet UI
|
||||
return {
|
||||
...acc,
|
||||
[name]: { type, value },
|
||||
};
|
||||
}, {});
|
||||
return inputVars;
|
||||
}
|
||||
|
||||
export const apmConfigMapping: Record<
|
||||
string,
|
||||
{ name: string; type: string }
|
||||
> = {
|
||||
'apm-server.host': {
|
||||
name: 'host',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.url': {
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.secret_token': {
|
||||
name: 'secret_token',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.api_key.enabled': {
|
||||
name: 'api_key_enabled',
|
||||
type: 'bool',
|
||||
},
|
||||
'apm-server.rum.enabled': {
|
||||
name: 'enable_rum',
|
||||
type: 'bool',
|
||||
},
|
||||
'apm-server.default_service_environment': {
|
||||
name: 'default_service_environment',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.rum.allow_service_names': {
|
||||
name: 'rum_allow_service_names',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.rum.allow_origins': {
|
||||
name: 'rum_allow_origins',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.rum.allow_headers': {
|
||||
name: 'rum_allow_headers',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.rum.response_headers': {
|
||||
name: 'rum_response_headers',
|
||||
type: 'yaml',
|
||||
},
|
||||
'apm-server.rum.event_rate.limit': {
|
||||
name: 'rum_event_rate_limit',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.rum.event_rate.lru_size': {
|
||||
name: 'rum_event_rate_lru_size',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.api_key.limit': {
|
||||
name: 'api_key_limit',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.max_event_size': {
|
||||
name: 'max_event_bytes',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.capture_personal_data': {
|
||||
name: 'capture_personal_data',
|
||||
type: 'bool',
|
||||
},
|
||||
'apm-server.max_header_size': {
|
||||
name: 'max_header_bytes',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.idle_timeout': {
|
||||
name: 'idle_timeout',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.read_timeout': {
|
||||
name: 'read_timeout',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.shutdown_timeout': {
|
||||
name: 'shutdown_timeout',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.write_timeout': {
|
||||
name: 'write_timeout',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.max_connections': {
|
||||
name: 'max_connections',
|
||||
type: 'integer',
|
||||
},
|
||||
'apm-server.response_headers': {
|
||||
name: 'response_headers',
|
||||
type: 'yaml',
|
||||
},
|
||||
'apm-server.expvar.enabled': {
|
||||
name: 'expvar_enabled',
|
||||
type: 'bool',
|
||||
},
|
||||
'apm-server.ssl.enabled': {
|
||||
name: 'tls_enabled',
|
||||
type: 'bool',
|
||||
},
|
||||
'apm-server.ssl.certificate': {
|
||||
name: 'tls_certificate',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.ssl.key': {
|
||||
name: 'tls_key',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.ssl.supported_protocols': {
|
||||
name: 'tls_supported_protocols',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.ssl.cipher_suites': {
|
||||
name: 'tls_cipher_suites',
|
||||
type: 'text',
|
||||
},
|
||||
'apm-server.ssl.curve_types': {
|
||||
name: 'tls_curve_types',
|
||||
type: 'text',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { Maybe } from '../../../typings/common';
|
||||
import { AgentPolicy, PackagePolicy } from '../../../../fleet/common';
|
||||
import { APMPluginStartDependencies } from '../../types';
|
||||
|
||||
export const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud';
|
||||
export const APM_PACKAGE_NAME = 'apm';
|
||||
|
||||
export async function getCloudAgentPolicy({
|
||||
fleetPluginStart,
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
fleetPluginStart: NonNullable<APMPluginStartDependencies['fleet']>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}) {
|
||||
try {
|
||||
return await fleetPluginStart.agentPolicyService.get(
|
||||
savedObjectsClient,
|
||||
POLICY_ELASTIC_AGENT_ON_CLOUD
|
||||
);
|
||||
} catch (error) {
|
||||
if (error?.output.statusCode === 404) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function getApmPackagePolicy(agentPolicy: Maybe<AgentPolicy>) {
|
||||
if (!agentPolicy) {
|
||||
return;
|
||||
}
|
||||
const packagePolicies = agentPolicy.package_policies as PackagePolicy[];
|
||||
return packagePolicies.find(
|
||||
(packagePolicy) => packagePolicy?.package?.name === APM_PACKAGE_NAME
|
||||
);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract } from 'kibana/server';
|
||||
import {
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_ID,
|
||||
} from '../../../common/apm_saved_object_constants';
|
||||
import { apmConfigMapping } from './get_apm_package_policy_definition';
|
||||
|
||||
export async function getUnsupportedApmServerSchema({
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}) {
|
||||
const { attributes } = await savedObjectsClient.get(
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_ID
|
||||
);
|
||||
const apmServerSchema: Record<string, any> = JSON.parse(
|
||||
(attributes as { schemaJson: string }).schemaJson
|
||||
);
|
||||
return Object.entries(apmServerSchema)
|
||||
.filter(([name]) => !(name in apmConfigMapping))
|
||||
.map(([key, value]) => ({ key, value }));
|
||||
}
|
20
x-pack/plugins/apm/server/lib/fleet/is_superuser.ts
Normal file
20
x-pack/plugins/apm/server/lib/fleet/is_superuser.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { KibanaRequest } from 'kibana/server';
|
||||
import { APMPluginStartDependencies } from '../../types';
|
||||
|
||||
export function isSuperuser({
|
||||
securityPluginStart,
|
||||
request,
|
||||
}: {
|
||||
securityPluginStart: NonNullable<APMPluginStartDependencies['security']>;
|
||||
request: KibanaRequest;
|
||||
}) {
|
||||
const user = securityPluginStart.authc.getCurrentUser(request);
|
||||
return user?.roles.includes('superuser');
|
||||
}
|
|
@ -31,7 +31,7 @@ import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_
|
|||
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
|
||||
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
|
||||
import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index';
|
||||
import { apmIndices, apmTelemetry } from './saved_objects';
|
||||
import { apmIndices, apmTelemetry, apmServerSettings } from './saved_objects';
|
||||
import { uiSettings } from './ui_settings';
|
||||
import type {
|
||||
ApmPluginRequestHandlerContext,
|
||||
|
@ -78,6 +78,7 @@ export class APMPlugin
|
|||
|
||||
core.savedObjects.registerType(apmIndices);
|
||||
core.savedObjects.registerType(apmTelemetry);
|
||||
core.savedObjects.registerType(apmServerSettings);
|
||||
|
||||
core.uiSettings.register(uiSettings);
|
||||
|
||||
|
|
|
@ -7,18 +7,24 @@
|
|||
|
||||
import { keyBy } from 'lodash';
|
||||
import Boom from '@hapi/boom';
|
||||
import * as t from 'io-ts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_ID,
|
||||
} from '../../common/apm_saved_object_constants';
|
||||
import { getFleetAgents } from '../lib/fleet/get_agents';
|
||||
import { getApmPackgePolicies } from '../lib/fleet/get_apm_package_policies';
|
||||
import { createApmServerRoute } from './create_apm_server_route';
|
||||
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||
|
||||
const FLEET_REQUIRED_MESSAGE = i18n.translate(
|
||||
'xpack.apm.fleet_has_data.fleetRequired',
|
||||
{
|
||||
defaultMessage: `Fleet plugin is required`,
|
||||
}
|
||||
);
|
||||
import {
|
||||
getCloudAgentPolicy,
|
||||
getApmPackagePolicy,
|
||||
} from '../lib/fleet/get_cloud_apm_package_policy';
|
||||
import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_package_policy';
|
||||
import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema';
|
||||
import { isSuperuser } from '../lib/fleet/is_superuser';
|
||||
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
|
||||
|
||||
const hasFleetDataRoute = createApmServerRoute({
|
||||
endpoint: 'GET /api/apm/fleet/has_data',
|
||||
|
@ -84,6 +90,119 @@ const fleetAgentsRoute = createApmServerRoute({
|
|||
},
|
||||
});
|
||||
|
||||
export const ApmFleetRouteRepository = createApmServerRouteRepository()
|
||||
const saveApmServerSchemaRoute = createApmServerRoute({
|
||||
endpoint: 'POST /api/apm/fleet/apm_server_schema',
|
||||
options: { tags: ['access:apm', 'access:apm_write'] },
|
||||
params: t.type({
|
||||
body: t.type({
|
||||
schema: t.record(t.string, t.unknown),
|
||||
}),
|
||||
}),
|
||||
handler: async (resources) => {
|
||||
const { params, logger, core } = resources;
|
||||
const savedObjectsClient = await getInternalSavedObjectsClient(core.setup);
|
||||
const { schema } = params.body;
|
||||
await savedObjectsClient.create(
|
||||
APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
{ schemaJson: JSON.stringify(schema) },
|
||||
{ id: APM_SERVER_SCHEMA_SAVED_OBJECT_ID, overwrite: true }
|
||||
);
|
||||
logger.info(`Stored apm-server schema.`);
|
||||
},
|
||||
});
|
||||
|
||||
const getUnsupportedApmServerSchemaRoute = createApmServerRoute({
|
||||
endpoint: 'GET /api/apm/fleet/apm_server_schema/unsupported',
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const { context } = resources;
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
return {
|
||||
unsupported: await getUnsupportedApmServerSchema({ savedObjectsClient }),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const getMigrationCheckRoute = createApmServerRoute({
|
||||
endpoint: 'GET /api/apm/fleet/migration_check',
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const { plugins, context, config, request } = resources;
|
||||
const cloudApmMigrationEnabled =
|
||||
config['xpack.apm.agent.migrations.enabled'];
|
||||
if (!plugins.fleet || !plugins.security) {
|
||||
throw Boom.internal(FLEET_SECURITY_REQUIRED_MESSAGE);
|
||||
}
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const fleetPluginStart = await plugins.fleet.start();
|
||||
const securityPluginStart = await plugins.security.start();
|
||||
const hasRequiredRole = isSuperuser({ securityPluginStart, request });
|
||||
const cloudAgentPolicy = await getCloudAgentPolicy({
|
||||
savedObjectsClient,
|
||||
fleetPluginStart,
|
||||
});
|
||||
return {
|
||||
has_cloud_agent_policy: !!cloudAgentPolicy,
|
||||
has_cloud_apm_package_policy: !!getApmPackagePolicy(cloudAgentPolicy),
|
||||
cloud_apm_migration_enabled: cloudApmMigrationEnabled,
|
||||
has_required_role: hasRequiredRole,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const createCloudApmPackagePolicyRoute = createApmServerRoute({
|
||||
endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy',
|
||||
options: { tags: ['access:apm', 'access:apm_write'] },
|
||||
handler: async (resources) => {
|
||||
const { plugins, context, config, request, logger } = resources;
|
||||
const cloudApmMigrationEnabled =
|
||||
config['xpack.apm.agent.migrations.enabled'];
|
||||
if (!plugins.fleet || !plugins.security) {
|
||||
throw Boom.internal(FLEET_SECURITY_REQUIRED_MESSAGE);
|
||||
}
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const coreStart = await resources.core.start();
|
||||
const esClient = coreStart.elasticsearch.client.asScoped(resources.request)
|
||||
.asCurrentUser;
|
||||
const fleetPluginStart = await plugins.fleet.start();
|
||||
const securityPluginStart = await plugins.security.start();
|
||||
const hasRequiredRole = isSuperuser({ securityPluginStart, request });
|
||||
if (!hasRequiredRole || !cloudApmMigrationEnabled) {
|
||||
throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE);
|
||||
}
|
||||
return {
|
||||
cloud_apm_package_policy: await createCloudApmPackgePolicy({
|
||||
fleetPluginStart,
|
||||
savedObjectsClient,
|
||||
esClient,
|
||||
logger,
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const apmFleetRouteRepository = createApmServerRouteRepository()
|
||||
.add(hasFleetDataRoute)
|
||||
.add(fleetAgentsRoute);
|
||||
.add(fleetAgentsRoute)
|
||||
.add(saveApmServerSchemaRoute)
|
||||
.add(getUnsupportedApmServerSchemaRoute)
|
||||
.add(getMigrationCheckRoute)
|
||||
.add(createCloudApmPackagePolicyRoute);
|
||||
|
||||
const FLEET_REQUIRED_MESSAGE = i18n.translate(
|
||||
'xpack.apm.fleet_has_data.fleetRequired',
|
||||
{ defaultMessage: `Fleet plugin is required` }
|
||||
);
|
||||
|
||||
const FLEET_SECURITY_REQUIRED_MESSAGE = i18n.translate(
|
||||
'xpack.apm.api.fleet.fleetSecurityRequired',
|
||||
{ defaultMessage: `Fleet and Security plugins are required` }
|
||||
);
|
||||
|
||||
const CLOUD_SUPERUSER_REQUIRED_MESSAGE = i18n.translate(
|
||||
'xpack.apm.api.fleet.cloud_apm_package_policy.requiredRoleOnCloud',
|
||||
{
|
||||
defaultMessage:
|
||||
'Operation only permitted by Elastic Cloud users with the superuser role.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ import { correlationsRouteRepository } from './correlations';
|
|||
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||
import { environmentsRouteRepository } from './environments';
|
||||
import { errorsRouteRepository } from './errors';
|
||||
import { apmFleetRouteRepository } from './fleet';
|
||||
import { indexPatternRouteRepository } from './index_pattern';
|
||||
import { metricsRouteRepository } from './metrics';
|
||||
import { observabilityOverviewRouteRepository } from './observability_overview';
|
||||
|
@ -30,7 +31,6 @@ import { sourceMapsRouteRepository } from './source_maps';
|
|||
import { traceRouteRepository } from './traces';
|
||||
import { transactionRouteRepository } from './transactions';
|
||||
import { APMRouteHandlerResources } from './typings';
|
||||
import { ApmFleetRouteRepository } from './fleet';
|
||||
|
||||
const getTypedGlobalApmServerRouteRepository = () => {
|
||||
const repository = createApmServerRouteRepository()
|
||||
|
@ -52,7 +52,7 @@ const getTypedGlobalApmServerRouteRepository = () => {
|
|||
.merge(apmIndicesRouteRepository)
|
||||
.merge(customLinkRouteRepository)
|
||||
.merge(sourceMapsRouteRepository)
|
||||
.merge(ApmFleetRouteRepository);
|
||||
.merge(apmFleetRouteRepository);
|
||||
|
||||
return repository;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { SavedObjectsType } from 'src/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE } from '../../common/apm_saved_object_constants';
|
||||
|
||||
export const apmServerSettings: SavedObjectsType = {
|
||||
name: APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
schemaJson: {
|
||||
type: 'text',
|
||||
index: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
management: {
|
||||
importableAndExportable: false,
|
||||
icon: 'apmApp',
|
||||
getTitle: () =>
|
||||
i18n.translate('xpack.apm.apmSchema.index', {
|
||||
defaultMessage: 'APM Server Schema - Index',
|
||||
}),
|
||||
},
|
||||
};
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
export { apmIndices } from './apm_indices';
|
||||
export { apmTelemetry } from './apm_telemetry';
|
||||
export { apmServerSettings } from './apm_server_settings';
|
||||
|
|
Loading…
Reference in a new issue