Skip to content

Commit

Permalink
[Synthetics] project monitor - delete (#143379)
Browse files Browse the repository at this point in the history
* add basic delete route

* add additional tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* adjust types

* adjust types

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* adjust types

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* update delete route to DELETE verb

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* rename helper

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
dominiqueclarke and kibanamachine authored Oct 19, 2022
1 parent 53d07a2 commit 8c65dd1
Show file tree
Hide file tree
Showing 9 changed files with 653 additions and 17 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/synthetics/common/constants/rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ export enum API_URLS {
SYNTHETICS_HAS_ZIP_URL_MONITORS = '/internal/uptime/fleet/has_zip_url_monitors',

// Project monitor public endpoint
SYNTHETICS_MONITORS_PROJECT_LEGACY = '/api/synthetics/service/project/monitors',
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/project/{projectName}/monitors',
SYNTHETICS_MONITORS_PROJECT_LEGACY = '/api/synthetics/service/project/monitors',
}
8 changes: 4 additions & 4 deletions x-pack/plugins/synthetics/server/routes/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export const getMonitors = (
const locationFilter = parseLocationFilter(syntheticsService.locations, locations);

const filters =
getFilter('tags', tags) +
getFilter('type', monitorType) +
getFilter('locations.id', locationFilter);
getKqlFilter('tags', tags) +
getKqlFilter('type', monitorType) +
getKqlFilter('locations.id', locationFilter);

return savedObjectsClient.find({
type: syntheticsMonitorType,
Expand All @@ -69,7 +69,7 @@ export const getMonitors = (
});
};

const getFilter = (field: string, values?: string | string[], operator = 'OR') => {
export const getKqlFilter = (field: string, values?: string | string[], operator = 'OR') => {
if (!values) {
return '';
}
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/synthetics/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getSyntheticsMonitorOverviewRoute,
getSyntheticsMonitorRoute,
} from './monitor_cruds/get_monitor';
import { deleteSyntheticsMonitorProjectRoute } from './monitor_cruds/delete_monitor_project';
import { getSyntheticsProjectMonitorsRoute } from './monitor_cruds/get_monitor_project';
import { runOnceSyntheticsMonitorRoute } from './synthetics_service/run_once_monitor';
import { getServiceAllowedRoute } from './synthetics_service/get_service_allowed';
Expand All @@ -38,6 +39,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
addSyntheticsMonitorRoute,
getSyntheticsEnablementRoute,
deleteSyntheticsMonitorRoute,
deleteSyntheticsMonitorProjectRoute,
disableSyntheticsRoute,
editSyntheticsMonitorRoute,
enableSyntheticsRoute,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
formatTelemetryDeleteEvent,
sendTelemetryEvents,
} from '../../telemetry/monitor_upgrade_sender';
import { ConfigKey, MonitorFields, SyntheticsMonitor } from '../../../../common/runtime_types';
import {
ConfigKey,
MonitorFields,
SyntheticsMonitor,
EncryptedSyntheticsMonitor,
EncryptedSyntheticsMonitorWithId,
} from '../../../../common/runtime_types';
import { UptimeServerSetup } from '../../../legacy_uptime/lib/adapters';
import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
Expand All @@ -24,7 +30,7 @@ export const deleteMonitorBulk = async ({
}: {
savedObjectsClient: SavedObjectsClientContract;
server: UptimeServerSetup;
monitors: Array<SavedObject<SyntheticsMonitor>>;
monitors: Array<SavedObject<SyntheticsMonitor | EncryptedSyntheticsMonitor>>;
syntheticsMonitorClient: SyntheticsMonitorClient;
request: KibanaRequest;
}) => {
Expand All @@ -35,10 +41,8 @@ export const deleteMonitorBulk = async ({
const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
monitors.map((normalizedMonitor) => ({
...normalizedMonitor.attributes,
id:
(normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] ||
normalizedMonitor.id,
})),
id: normalizedMonitor.attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] || normalizedMonitor.id,
})) as EncryptedSyntheticsMonitorWithId[],
request,
savedObjectsClient,
spaceId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ConfigKey } from '../../../common/runtime_types';
import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types';
import { API_URLS } from '../../../common/constants';
import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor';
import { getMonitors, getKqlFilter } from '../common';
import { INSUFFICIENT_FLEET_PERMISSIONS } from '../../synthetics_service/project_monitor/project_monitor_formatter';
import { deleteMonitorBulk } from './bulk_cruds/delete_monitor_bulk';

export const deleteSyntheticsMonitorProjectRoute: SyntheticsRestApiRouteFactory = () => ({
method: 'DELETE',
path: API_URLS.SYNTHETICS_MONITORS_PROJECT,
validate: {
body: schema.object({
monitors: schema.arrayOf(schema.string()),
}),
params: schema.object({
projectName: schema.string(),
}),
},
handler: async ({
request,
response,
savedObjectsClient,
server,
syntheticsMonitorClient,
}): Promise<any> => {
const { projectName } = request.params;
const { monitors: monitorsToDelete } = request.body;
const decodedProjectName = decodeURI(projectName);
if (monitorsToDelete.length > 250) {
return response.badRequest({
body: {
message: REQUEST_TOO_LARGE,
},
});
}

const { saved_objects: monitors } = await getMonitors(
{
filter: `${syntheticsMonitorType}.attributes.${
ConfigKey.PROJECT_ID
}: "${decodedProjectName}" AND ${getKqlFilter(
'journey_id',
monitorsToDelete.map((id: string) => `"${id}"`)
)}`,
fields: [],
perPage: 500,
},
syntheticsMonitorClient.syntheticsService,
savedObjectsClient
);

const {
integrations: { writeIntegrationPolicies },
} = await server.fleet.authz.fromRequest(request);

const hasPrivateMonitor = monitors.some((monitor) =>
monitor.attributes.locations.some((location) => !location.isServiceManaged)
);

if (!writeIntegrationPolicies && hasPrivateMonitor) {
return response.forbidden({
body: {
message: INSUFFICIENT_FLEET_PERMISSIONS,
},
});
}

await deleteMonitorBulk({
monitors,
server,
savedObjectsClient,
syntheticsMonitorClient,
request,
});

return {
deleted_monitors: monitorsToDelete,
};
},
});

export const REQUEST_TOO_LARGE = i18n.translate('xpack.synthetics.server.project.delete.toolarge', {
defaultMessage:
'Delete request payload is too large. Please send a max of 250 monitors to delete per request',
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
*/
import type { Subject } from 'rxjs';
import { isEqual } from 'lodash';
import pMap from 'p-map';
import { KibanaRequest } from '@kbn/core/server';
import {
SavedObjectsUpdateResponse,
SavedObjectsClientContract,
SavedObjectsFindResult,
} from '@kbn/core/server';
import pMap from 'p-map';
import { i18n } from '@kbn/i18n';
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
import { syncNewMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/add_monitor_bulk';
import { deleteMonitorBulk } from '../../routes/monitor_cruds/bulk_cruds/delete_monitor_bulk';
Expand Down Expand Up @@ -50,8 +51,13 @@ interface StaleMonitor {
type StaleMonitorMap = Record<string, StaleMonitor>;
type FailedError = Array<{ id?: string; reason: string; details: string; payload?: object }>;

export const INSUFFICIENT_FLEET_PERMISSIONS =
'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.';
export const INSUFFICIENT_FLEET_PERMISSIONS = i18n.translate(
'xpack.synthetics.service.projectMonitors.insufficientFleetPermissions',
{
defaultMessage:
'Insufficient permissions. In order to configure private locations, you must have Fleet and Integrations write permissions. To resolve, please generate a new API key with a user who has Fleet and Integrations write permissions.',
}
);

export class ProjectMonitorFormatter {
private projectId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ConfigKey,
MonitorFields,
SyntheticsMonitorWithId,
EncryptedSyntheticsMonitorWithId,
HeartbeatConfig,
PrivateLocation,
EncryptedSyntheticsMonitor,
Expand Down Expand Up @@ -120,19 +121,23 @@ export class SyntheticsMonitorClient {
}
}
async deleteMonitors(
monitors: SyntheticsMonitorWithId[],
monitors: Array<EncryptedSyntheticsMonitorWithId | SyntheticsMonitorWithId>,
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract,
spaceId: string
) {
/* Type cast encrypted saved objects to decrypted saved objects for delete flow only.
* Deletion does not require all monitor fields */
const privateDeletePromise = this.privateLocationAPI.deleteMonitors(
monitors,
monitors as SyntheticsMonitorWithId[],
request,
savedObjectsClient,
spaceId
);

const publicDeletePromise = this.syntheticsService.deleteConfigs(monitors);
const publicDeletePromise = this.syntheticsService.deleteConfigs(
monitors as SyntheticsMonitorWithId[]
);
const [pubicResponse] = await Promise.all([publicDeletePromise, privateDeletePromise]);

return pubicResponse;
Expand Down
Loading

0 comments on commit 8c65dd1

Please sign in to comment.