From 164b9f3bc88950433a917e0eb94039fbb7e9ae23 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 7 Nov 2024 22:31:09 +0100 Subject: [PATCH] [Synthetics] Handle private locations simultaneous edits !! (#195874) ## Summary Fixes https://github.com/elastic/kibana/issues/190801 !! Handle private locations simultaneous edits !! Registered a new saved object to handle private locations properly. Instead of using a singleton, now each private location will be represented by it's own saved object. ### Existing private locations When we are doing any write operation, we migrate them to new kind of saved object and remove the legacy saved object type. ### Testing - Create multiple private locations on main - Switch to branch and create few more locations - Make sure all private locations are editable, deleteable and have been migrated to new saved object types in the course --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 96c9b5b5d0a82c6e4b0a9b86fc1ab4dd9fa4d707) --- .../current_fields.json | 1 + .../current_mappings.json | 4 + .../kbn_client/kbn_client_saved_objects.ts | 1 + .../check_registered_types.test.ts | 1 + .../group3/type_registrations.test.ts | 1 + .../common/saved_objects/private_locations.ts | 6 +- .../journeys/private_locations.journey.ts | 19 ++- .../journeys/services/add_monitor.ts | 26 +--- .../journeys/services/synthetics_services.ts | 13 +- .../synthetics/server/feature.ts | 10 +- .../routes/monitor_cruds/edit_monitor.test.ts | 10 ++ .../private_locations/add_private_location.ts | 31 ++--- .../delete_private_location.ts | 30 ++--- .../get_private_locations.ts | 12 +- .../private_locations/helpers.test.ts | 6 +- .../settings/private_locations/helpers.ts | 16 +++ .../migrate_legacy_private_locations.test.ts | 119 ++++++++++++++++++ .../migrate_legacy_private_locations.ts | 70 +++++++++++ .../get_service_locations.ts | 4 +- .../private_locations/model_version_1.test.ts | 4 +- .../server/saved_objects/private_locations.ts | 30 ++++- .../server/saved_objects/saved_objects.ts | 8 +- .../synthetics_service/get_all_locations.ts | 7 +- .../get_private_locations.ts | 50 ++++++-- .../private_location_test_service.ts | 83 ------------ .../synthetics/synthetics_rule_helper.ts | 7 +- .../add_monitor_private_location.ts | 89 +++---------- .../apis/synthetics/add_monitor_project.ts | 7 +- .../add_monitor_project_private_location.ts | 8 +- .../apis/synthetics/delete_monitor.ts | 6 +- .../apis/synthetics/delete_monitor_project.ts | 7 +- .../apis/synthetics/edit_monitor.ts | 6 +- .../synthetics/edit_monitor_public_api.ts | 2 +- .../apis/synthetics/get_monitor_project.ts | 8 +- .../api_integration/apis/synthetics/index.ts | 1 + .../apis/synthetics/inspect_monitor.ts | 2 +- .../apis/synthetics/private_location_apis.ts | 64 ++++++++++ .../services/private_location_test_service.ts | 89 +++++++++---- .../synthetics_monitor_test_service.ts | 2 +- .../apis/synthetics/sync_global_params.ts | 10 +- .../apis/synthetics/synthetics_enablement.ts | 9 ++ .../platform_security/authorization.ts | 24 ++++ 42 files changed, 576 insertions(+), 327 deletions(-) create mode 100644 x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.test.ts create mode 100644 x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/synthetics/private_location_test_service.ts create mode 100644 x-pack/test/api_integration/apis/synthetics/private_location_apis.ts diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 5dabd353a8a9a..5ca2fc677b167 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -1072,6 +1072,7 @@ "urls" ], "synthetics-param": [], + "synthetics-private-location": [], "synthetics-privates-locations": [], "tag": [ "color", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 414faf88b304f..1e9ff6ac20c79 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -3549,6 +3549,10 @@ "dynamic": false, "properties": {} }, + "synthetics-private-location": { + "dynamic": false, + "properties": {} + }, "synthetics-privates-locations": { "dynamic": false, "properties": {} diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index d5483f1fe0f9f..0b6ba0be80fab 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -109,6 +109,7 @@ const STANDARD_LIST_TYPES = [ 'synthetics-monitor', 'uptime-dynamic-settings', 'synthetics-privates-locations', + 'synthetics-private-location', 'osquery-saved-query', 'osquery-pack', diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index aedc9e3364c8f..a25e77491c0bc 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -165,6 +165,7 @@ describe('checking migration metadata changes on all registered SO types', () => "synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7", "synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea", "synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e", + "synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be", "synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c", "tag": "e2544392fe6563e215bb677abc8b01c2601ef2dc", "task": "3c89a7c918d5b896a5f8800f06e9114ad7e7aea3", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index e95a82e63d0ff..ba06073e454a9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -139,6 +139,7 @@ const previouslyRegisteredTypes = [ 'synthetics-monitor', 'synthetics-param', 'synthetics-privates-locations', + 'synthetics-private-location', 'tag', 'task', 'telemetry', diff --git a/x-pack/plugins/observability_solution/synthetics/common/saved_objects/private_locations.ts b/x-pack/plugins/observability_solution/synthetics/common/saved_objects/private_locations.ts index bb3639e816059..1b5bb92dd7d88 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/saved_objects/private_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/saved_objects/private_locations.ts @@ -5,5 +5,7 @@ * 2.0. */ -export const privateLocationsSavedObjectId = 'synthetics-privates-locations-singleton'; -export const privateLocationsSavedObjectName = 'synthetics-privates-locations'; +export const legacyPrivateLocationsSavedObjectId = 'synthetics-privates-locations-singleton'; +export const legacyPrivateLocationsSavedObjectName = 'synthetics-privates-locations'; + +export const privateLocationSavedObjectName = 'synthetics-private-location'; diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/private_locations.journey.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/private_locations.journey.ts index 9e6bb8352c35f..cdc5961991579 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/private_locations.journey.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/private_locations.journey.ts @@ -7,17 +7,14 @@ import { journey, step, before, after, expect } from '@elastic/synthetics'; import { waitForLoadingToFinish } from '@kbn/ux-plugin/e2e/journeys/utils'; +import { SyntheticsServices } from './services/synthetics_services'; import { byTestId } from '../../helpers/utils'; -import { - addTestMonitor, - cleanPrivateLocations, - cleanTestMonitors, - getPrivateLocations, -} from './services/add_monitor'; +import { addTestMonitor, cleanPrivateLocations, cleanTestMonitors } from './services/add_monitor'; import { syntheticsAppPageProvider } from '../page_objects/synthetics_app'; journey(`PrivateLocationsSettings`, async ({ page, params }) => { const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl, params }); + const services = new SyntheticsServices(params); page.setDefaultTimeout(2 * 30000); @@ -78,16 +75,14 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => { await page.click('text=Private Locations'); await page.click('h1:has-text("Settings")'); - const privateLocations = await getPrivateLocations(params); + const privateLocations = await services.getPrivateLocations(); - const locations = privateLocations.attributes.locations; + expect(privateLocations.length).toBe(1); - expect(locations.length).toBe(1); - - locationId = locations[0].id; + locationId = privateLocations[0].id; await addTestMonitor(params.kibanaUrl, 'test-monitor', { - locations: [locations[0]], + locations: [privateLocations[0]], type: 'browser', }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor.ts index 6384179a71bb9..6a527da275eb3 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor.ts @@ -7,10 +7,7 @@ import axios from 'axios'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, -} from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; +import { legacyPrivateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; export const enableMonitorManagedViaApi = async (kibanaUrl: string) => { try { @@ -46,21 +43,6 @@ export const addTestMonitor = async ( } }; -export const getPrivateLocations = async (params: Record) => { - const getService = params.getService; - const server = getService('kibanaServer'); - - try { - return await server.savedObjects.get({ - id: privateLocationsSavedObjectId, - type: privateLocationsSavedObjectName, - }); - } catch (e) { - // eslint-disable-next-line no-console - console.log(e); - } -}; - export const cleanTestMonitors = async (params: Record) => { const getService = params.getService; const server = getService('kibanaServer'); @@ -79,7 +61,11 @@ export const cleanPrivateLocations = async (params: Record) => { try { await server.savedObjects.clean({ - types: [privateLocationsSavedObjectName, 'ingest-agent-policies', 'ingest-package-policies'], + types: [ + legacyPrivateLocationsSavedObjectName, + 'ingest-agent-policies', + 'ingest-package-policies', + ], }); } catch (e) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts index 5c356492f1c24..507efe52c453f 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts @@ -10,7 +10,10 @@ import type { Client } from '@elastic/elasticsearch'; import { KbnClient } from '@kbn/test'; import pMap from 'p-map'; import { makeDownSummary, makeUpSummary } from '@kbn/observability-synthetics-test-data'; -import { SyntheticsMonitor } from '@kbn/synthetics-plugin/common/runtime_types'; +import { + SyntheticsMonitor, + SyntheticsPrivateLocations, +} from '@kbn/synthetics-plugin/common/runtime_types'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { journeyStart, journeySummary, step1, step2 } from './data/browser_docs'; @@ -251,4 +254,12 @@ export class SyntheticsServices { }); return connector.data; } + + async getPrivateLocations(): Promise { + const response = await this.requester.request({ + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, + method: 'GET', + }); + return response.data as SyntheticsPrivateLocations; + } } diff --git a/x-pack/plugins/observability_solution/synthetics/server/feature.ts b/x-pack/plugins/observability_solution/synthetics/server/feature.ts index c8b4b721a9ce1..bf86ac7b0c890 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/feature.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/feature.ts @@ -14,7 +14,10 @@ import { import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import { syntheticsMonitorType, syntheticsParamType } from '../common/types/saved_objects'; import { SYNTHETICS_RULE_TYPES } from '../common/constants/synthetics_alerts'; -import { privateLocationsSavedObjectName } from '../common/saved_objects/private_locations'; +import { + legacyPrivateLocationsSavedObjectName, + privateLocationSavedObjectName, +} from '../common/saved_objects/private_locations'; import { PLUGIN } from '../common/constants/plugin'; import { syntheticsSettingsObjectType, @@ -71,7 +74,8 @@ export const syntheticsFeature = { syntheticsSettingsObjectType, syntheticsMonitorType, syntheticsApiKeyObjectType, - privateLocationsSavedObjectName, + privateLocationSavedObjectName, + legacyPrivateLocationsSavedObjectName, syntheticsParamType, // uptime settings object is also registered here since feature is shared between synthetics and uptime uptimeSettingsObjectType, @@ -102,7 +106,7 @@ export const syntheticsFeature = { syntheticsSettingsObjectType, syntheticsMonitorType, syntheticsApiKeyObjectType, - privateLocationsSavedObjectName, + legacyPrivateLocationsSavedObjectName, // uptime settings object is also registered here since feature is shared between synthetics and uptime uptimeSettingsObjectType, ], diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts index 637c0fc5c6193..cb50708c04eca 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.test.ts @@ -33,6 +33,16 @@ describe('syncEditedMonitor', () => { bulkUpdate: jest.fn(), get: jest.fn(), update: jest.fn(), + createPointInTimeFinder: jest.fn().mockImplementation(({ perPage, type: soType }) => ({ + close: jest.fn(async () => {}), + find: jest.fn().mockReturnValue({ + async *[Symbol.asyncIterator]() { + yield { + saved_objects: [], + }; + }, + }), + })), }, logger, config: { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/add_private_location.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/add_private_location.ts index ac6eff7dea90d..1feb120b2ea14 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/add_private_location.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/add_private_location.ts @@ -6,14 +6,12 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { getPrivateLocationsAndAgentPolicies } from './get_private_locations'; -import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, -} from '../../../../common/saved_objects/private_locations'; +import { privateLocationSavedObjectName } from '../../../../common/saved_objects/private_locations'; import { SYNTHETICS_API_URLS } from '../../../../common/constants'; -import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations'; +import { PrivateLocationAttributes } from '../../../runtime_types/private_locations'; import { toClientContract, toSavedObjectContract } from './helpers'; import { PrivateLocation } from '../../../../common/runtime_types'; @@ -40,7 +38,11 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory { + handler: async (routeContext) => { + await migrateLegacyPrivateLocations(routeContext); + + const { response, request, savedObjectsClient, syntheticsMonitorClient } = routeContext; + const location = request.body as PrivateLocationObject; const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies( @@ -65,7 +67,6 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory loc.id !== location.agentPolicyId); const formattedLocation = toSavedObjectContract({ ...location, id: location.agentPolicyId, @@ -80,17 +81,17 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory( - privateLocationsSavedObjectName, - { locations: [...existingLocations, formattedLocation] }, + const soClient = routeContext.server.coreStart.savedObjects.createInternalRepository(); + + const result = await soClient.create( + privateLocationSavedObjectName, + formattedLocation, { - id: privateLocationsSavedObjectId, - overwrite: true, + id: location.agentPolicyId, + initialNamespaces: ['*'], } ); - const allLocations = toClientContract(result.attributes, agentPolicies); - - return allLocations.find((loc) => loc.id === location.agentPolicyId)!; + return toClientContract(result.attributes, agentPolicies); }, }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/delete_private_location.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/delete_private_location.ts index 1c6ede5a2ad00..bac3907eac871 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/delete_private_location.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/delete_private_location.ts @@ -7,15 +7,12 @@ import { schema } from '@kbn/config-schema'; import { isEmpty } from 'lodash'; +import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations'; import { getMonitorsByLocation } from './get_location_monitors'; import { getPrivateLocationsAndAgentPolicies } from './get_private_locations'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { SYNTHETICS_API_URLS } from '../../../../common/constants'; -import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, -} from '../../../../common/saved_objects/private_locations'; -import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations'; +import { privateLocationSavedObjectName } from '../../../../common/saved_objects/private_locations'; export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'DELETE', @@ -28,12 +25,16 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory { + handler: async (routeContext) => { + await migrateLegacyPrivateLocations(routeContext); + + const { savedObjectsClient, syntheticsMonitorClient, request, response, server } = routeContext; const { locationId } = request.params as { locationId: string }; const { locations } = await getPrivateLocationsAndAgentPolicies( savedObjectsClient, - syntheticsMonitorClient + syntheticsMonitorClient, + true ); if (!locations.find((loc) => loc.id === locationId)) { @@ -55,17 +56,8 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory loc.id !== locationId); - - await savedObjectsClient.create( - privateLocationsSavedObjectName, - { locations: remainingLocations }, - { - id: privateLocationsSavedObjectId, - overwrite: true, - } - ); - - return; + await savedObjectsClient.delete(privateLocationSavedObjectName, locationId, { + force: true, + }); }, }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/get_private_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/get_private_locations.ts index f7adc1e7ac16e..d884bba5c2b0a 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/get_private_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/get_private_locations.ts @@ -7,6 +7,7 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { schema } from '@kbn/config-schema'; +import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations'; import { AgentPolicyInfo } from '../../../../common/types'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../common/runtime_types'; @@ -14,7 +15,7 @@ import { SYNTHETICS_API_URLS } from '../../../../common/constants'; import { getPrivateLocations } from '../../../synthetics_service/get_private_locations'; import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations'; import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; -import { toClientContract } from './helpers'; +import { allLocationsToClientContract } from './helpers'; export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory< SyntheticsPrivateLocations | PrivateLocation @@ -29,14 +30,17 @@ export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory< }), }, }, - handler: async ({ savedObjectsClient, syntheticsMonitorClient, request, response }) => { + handler: async (routeContext) => { + await migrateLegacyPrivateLocations(routeContext); + + const { savedObjectsClient, syntheticsMonitorClient, request, response } = routeContext; const { id } = request.params as { id?: string }; const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies( savedObjectsClient, syntheticsMonitorClient ); - const list = toClientContract({ locations }, agentPolicies); + const list = allLocationsToClientContract({ locations }, agentPolicies); if (!id) return list; const location = list.find((loc) => loc.id === id || loc.label === id); if (!location) { @@ -53,7 +57,7 @@ export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory< export const getPrivateLocationsAndAgentPolicies = async ( savedObjectsClient: SavedObjectsClientContract, syntheticsMonitorClient: SyntheticsMonitorClient, - excludeAgentPolicies: boolean = false + excludeAgentPolicies = false ): Promise => { try { const [privateLocations, agentPolicies] = await Promise.all([ diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.test.ts index 6055b217f8794..84c531cb9ce70 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { toClientContract } from './helpers'; +import { allLocationsToClientContract } from './helpers'; const testLocations = { locations: [ @@ -56,7 +56,7 @@ const testLocations2 = { describe('toClientContract', () => { it('formats SO attributes to client contract with falsy geo location', () => { // @ts-ignore fixtures are purposely wrong types for testing - expect(toClientContract(testLocations)).toEqual([ + expect(allLocationsToClientContract(testLocations)).toEqual([ { agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb', geo: { @@ -86,7 +86,7 @@ describe('toClientContract', () => { it('formats SO attributes to client contract with truthy geo location', () => { // @ts-ignore fixtures are purposely wrong types for testing - expect(toClientContract(testLocations2)).toEqual([ + expect(allLocationsToClientContract(testLocations2)).toEqual([ { agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb', geo: { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.ts index 1c6c03067a817..8df065ad3e48d 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/helpers.ts @@ -13,6 +13,22 @@ import type { import { PrivateLocation } from '../../../../common/runtime_types'; export const toClientContract = ( + location: PrivateLocationAttributes, + agentPolicies?: AgentPolicyInfo[] +): PrivateLocation => { + const agPolicy = agentPolicies?.find((policy) => policy.id === location.agentPolicyId); + return { + label: location.label, + id: location.id, + agentPolicyId: location.agentPolicyId, + isServiceManaged: false, + isInvalid: !Boolean(agPolicy), + tags: location.tags, + geo: location.geo, + }; +}; + +export const allLocationsToClientContract = ( attributes: SyntheticsPrivateLocationsAttributes, agentPolicies?: AgentPolicyInfo[] ): SyntheticsPrivateLocations => { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.test.ts new file mode 100644 index 0000000000000..2305853aab3f1 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.test.ts @@ -0,0 +1,119 @@ +/* + * 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 { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations'; +import { SyntheticsServerSetup } from '../../../types'; +import { coreMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import { + type ISavedObjectsRepository, + SavedObjectsClientContract, +} from '@kbn/core-saved-objects-api-server'; + +describe('migrateLegacyPrivateLocations', () => { + let serverMock: SyntheticsServerSetup; + let savedObjectsClient: jest.Mocked; + let repositoryMock: ISavedObjectsRepository; + beforeEach(() => { + const coreStartMock = coreMock.createStart(); + serverMock = { + coreStart: coreStartMock, + logger: loggerMock.create(), + } as any; + savedObjectsClient = savedObjectsClientMock.create(); + repositoryMock = coreMock.createStart().savedObjects.createInternalRepository(); + + coreStartMock.savedObjects.createInternalRepository.mockReturnValue(repositoryMock); + }); + + it('should get the legacy private locations', async () => { + savedObjectsClient.get.mockResolvedValueOnce({ + attributes: { locations: [{ id: '1', label: 'Location 1' }] }, + } as any); + savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any); + + await migrateLegacyPrivateLocations({ + server: serverMock, + savedObjectsClient, + } as any); + + expect(savedObjectsClient.get).toHaveBeenCalledWith( + 'synthetics-privates-locations', + 'synthetics-privates-locations-singleton' + ); + }); + + it('should log and return if an error occurs while getting legacy private locations', async () => { + const error = new Error('Get error'); + savedObjectsClient.get.mockRejectedValueOnce(error); + + await migrateLegacyPrivateLocations({ + server: serverMock, + savedObjectsClient, + } as any); + + expect(serverMock.logger.error).toHaveBeenCalledWith( + `Error getting legacy private locations: ${error}` + ); + expect(repositoryMock.bulkCreate).not.toHaveBeenCalled(); + }); + + it('should return if there are no legacy locations', async () => { + savedObjectsClient.get.mockResolvedValueOnce({ + attributes: { locations: [] }, + } as any); + + await migrateLegacyPrivateLocations({ + server: serverMock, + savedObjectsClient: savedObjectsClientMock, + } as any); + + expect(repositoryMock.bulkCreate).not.toHaveBeenCalled(); + }); + + it('should bulk create new private locations if there are legacy locations', async () => { + const legacyLocations = [{ id: '1', label: 'Location 1' }]; + savedObjectsClient.get.mockResolvedValueOnce({ + attributes: { locations: legacyLocations }, + } as any); + savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any); + + await migrateLegacyPrivateLocations({ + server: serverMock, + savedObjectsClient, + } as any); + + expect(repositoryMock.bulkCreate).toHaveBeenCalledWith( + legacyLocations.map((location) => ({ + id: location.id, + attributes: location, + type: 'synthetics-private-location', + initialNamespaces: ['*'], + })), + { overwrite: true } + ); + }); + + it('should delete legacy private locations if bulk create count matches', async () => { + const legacyLocations = [{ id: '1', label: 'Location 1' }]; + savedObjectsClient.get.mockResolvedValueOnce({ + attributes: { locations: legacyLocations }, + } as any); + savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any); + + await migrateLegacyPrivateLocations({ + server: serverMock, + savedObjectsClient, + } as any); + + expect(savedObjectsClient.delete).toHaveBeenCalledWith( + 'synthetics-privates-locations', + 'synthetics-privates-locations-singleton', + {} + ); + }); +}); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.ts new file mode 100644 index 0000000000000..cd73e27b950e3 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/private_locations/migrate_legacy_private_locations.ts @@ -0,0 +1,70 @@ +/* + * 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 { SavedObject } from '@kbn/core-saved-objects-server'; +import { + type PrivateLocationAttributes, + SyntheticsPrivateLocationsAttributes, +} from '../../../runtime_types/private_locations'; +import { + legacyPrivateLocationsSavedObjectId, + legacyPrivateLocationsSavedObjectName, + privateLocationSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; +import { RouteContext } from '../../types'; + +export const migrateLegacyPrivateLocations = async ({ + server, + savedObjectsClient, +}: RouteContext) => { + try { + let obj: SavedObject | undefined; + try { + obj = await savedObjectsClient.get( + legacyPrivateLocationsSavedObjectName, + legacyPrivateLocationsSavedObjectId + ); + } catch (e) { + server.logger.error(`Error getting legacy private locations: ${e}`); + return; + } + const legacyLocations = obj?.attributes.locations ?? []; + if (legacyLocations.length === 0) { + return; + } + + const soClient = server.coreStart.savedObjects.createInternalRepository(); + + await soClient.bulkCreate( + legacyLocations.map((location) => ({ + id: location.id, + attributes: location, + type: privateLocationSavedObjectName, + initialNamespaces: ['*'], + })), + { + overwrite: true, + } + ); + + const { total } = await savedObjectsClient.find({ + type: privateLocationSavedObjectName, + fields: [], + perPage: 0, + }); + + if (total === legacyLocations.length) { + await savedObjectsClient.delete( + legacyPrivateLocationsSavedObjectName, + legacyPrivateLocationsSavedObjectId, + {} + ); + } + } catch (e) { + server.logger.error(`Error migrating legacy private locations: ${e}`); + } +}; diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/get_service_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/get_service_locations.ts index a9142170c9e26..ca704cdff1b28 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/get_service_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/get_service_locations.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { toClientContract } from '../settings/private_locations/helpers'; +import { allLocationsToClientContract } from '../settings/private_locations/helpers'; import { getPrivateLocationsAndAgentPolicies } from '../settings/private_locations/get_private_locations'; import { SyntheticsRestApiRouteFactory } from '../types'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; @@ -45,7 +45,7 @@ export const getServiceLocationsRoute: SyntheticsRestApiRouteFactory = () => ({ const { locations: privateLocations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(savedObjectsClient, syntheticsMonitorClient); - const result = toClientContract({ locations: privateLocations }, agentPolicies); + const result = allLocationsToClientContract({ locations: privateLocations }, agentPolicies); return { locations: result, }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/migrations/private_locations/model_version_1.test.ts b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/migrations/private_locations/model_version_1.test.ts index 63a9f940143a4..dbcdea546a9f8 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/migrations/private_locations/model_version_1.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/migrations/private_locations/model_version_1.test.ts @@ -5,7 +5,7 @@ * 2.0. */ import { transformGeoProperty } from './model_version_1'; -import { privateLocationsSavedObjectName } from '../../../../common/saved_objects/private_locations'; +import { legacyPrivateLocationsSavedObjectName } from '../../../../common/saved_objects/private_locations'; describe('model version 1 migration', () => { const testLocation = { @@ -19,7 +19,7 @@ describe('model version 1 migration', () => { concurrentMonitors: 1, }; const testObject = { - type: privateLocationsSavedObjectName, + type: legacyPrivateLocationsSavedObjectName, id: 'synthetics-privates-locations-singleton', attributes: { locations: [testLocation], diff --git a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/private_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/private_locations.ts index ee7426ead23af..370c8d203dff6 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/private_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/private_locations.ts @@ -7,11 +7,33 @@ import { SavedObjectsType } from '@kbn/core/server'; import { modelVersion1 } from './migrations/private_locations/model_version_1'; -import { privateLocationsSavedObjectName } from '../../common/saved_objects/private_locations'; -export const privateLocationsSavedObjectId = 'synthetics-privates-locations-singleton'; +import { + legacyPrivateLocationsSavedObjectName, + privateLocationSavedObjectName, +} from '../../common/saved_objects/private_locations'; -export const PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE: SavedObjectsType = { - name: privateLocationsSavedObjectName, +export const PRIVATE_LOCATION_SAVED_OBJECT_TYPE: SavedObjectsType = { + name: privateLocationSavedObjectName, + hidden: false, + namespaceType: 'multiple', + mappings: { + dynamic: false, + properties: { + /* Leaving these commented to make it clear that these fields exist, even though we don't want them indexed. + When adding new fields please add them here. If they need to be searchable put them in the uncommented + part of properties. + */ + }, + }, + management: { + importableAndExportable: true, + }, +}; + +export const legacyPrivateLocationsSavedObjectId = 'synthetics-privates-locations-singleton'; + +export const LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE: SavedObjectsType = { + name: legacyPrivateLocationsSavedObjectName, hidden: false, namespaceType: 'agnostic', mappings: { diff --git a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/saved_objects.ts b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/saved_objects.ts index 9b4a365941a7d..d59ecb507166b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/saved_objects.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/saved_objects.ts @@ -24,7 +24,10 @@ import { SYNTHETICS_SECRET_ENCRYPTED_TYPE, syntheticsParamSavedObjectType, } from './synthetics_param'; -import { PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE } from './private_locations'; +import { + LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE, + PRIVATE_LOCATION_SAVED_OBJECT_TYPE, +} from './private_locations'; import { DYNAMIC_SETTINGS_DEFAULT_ATTRIBUTES } from '../constants/settings'; import { DynamicSettingsAttributes } from '../runtime_types/settings'; import { @@ -37,7 +40,8 @@ export const registerSyntheticsSavedObjects = ( savedObjectsService: SavedObjectsServiceSetup, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ) => { - savedObjectsService.registerType(PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE); + savedObjectsService.registerType(LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE); + savedObjectsService.registerType(PRIVATE_LOCATION_SAVED_OBJECT_TYPE); savedObjectsService.registerType(getSyntheticsMonitorSavedObjectType(encryptedSavedObjects)); savedObjectsService.registerType(syntheticsServiceApiKey); diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_all_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_all_locations.ts index c24b28c00ca99..0d8355cebc1f6 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_all_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_all_locations.ts @@ -5,7 +5,7 @@ * 2.0. */ import { SavedObjectsClientContract } from '@kbn/core/server'; -import { toClientContract } from '../routes/settings/private_locations/helpers'; +import { allLocationsToClientContract } from '../routes/settings/private_locations/helpers'; import { getPrivateLocationsAndAgentPolicies } from '../routes/settings/private_locations/get_private_locations'; import { SyntheticsServerSetup } from '../types'; import { getServiceLocations } from './get_service_locations'; @@ -34,7 +34,10 @@ export async function getAllLocations({ ), getServicePublicLocations(server, syntheticsMonitorClient), ]); - const pvtLocations = toClientContract({ locations: privateLocations }, agentPolicies); + const pvtLocations = allLocationsToClientContract( + { locations: privateLocations }, + agentPolicies + ); return { publicLocations, privateLocations: pvtLocations, diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_private_locations.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_private_locations.ts index a850cbf081e68..a476df9dfe038 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_private_locations.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_private_locations.ts @@ -5,22 +5,42 @@ * 2.0. */ -import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { - privateLocationsSavedObjectId, - privateLocationsSavedObjectName, + SavedObject, + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '@kbn/core/server'; +import { uniqBy } from 'lodash'; +import { + legacyPrivateLocationsSavedObjectId, + legacyPrivateLocationsSavedObjectName, + privateLocationSavedObjectName, } from '../../common/saved_objects/private_locations'; -import type { SyntheticsPrivateLocationsAttributes } from '../runtime_types/private_locations'; +import { + PrivateLocationAttributes, + SyntheticsPrivateLocationsAttributes, +} from '../runtime_types/private_locations'; export const getPrivateLocations = async ( client: SavedObjectsClientContract ): Promise => { try { - const obj = await client.get( - privateLocationsSavedObjectName, - privateLocationsSavedObjectId - ); - return obj?.attributes.locations ?? []; + const finder = client.createPointInTimeFinder({ + type: privateLocationSavedObjectName, + perPage: 1000, + }); + + const results: Array> = []; + + for await (const response of finder.find()) { + results.push(...response.saved_objects); + } + + finder.close().catch((e) => {}); + + const legacyLocations = await getLegacyPrivateLocations(client); + + return uniqBy([...results.map((r) => r.attributes), ...legacyLocations], 'id'); } catch (getErr) { if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) { return []; @@ -28,3 +48,15 @@ export const getPrivateLocations = async ( throw getErr; } }; + +const getLegacyPrivateLocations = async (client: SavedObjectsClientContract) => { + try { + const obj = await client.get( + legacyPrivateLocationsSavedObjectName, + legacyPrivateLocationsSavedObjectId + ); + return obj?.attributes.locations ?? []; + } catch (getErr) { + return []; + } +}; diff --git a/x-pack/test/alerting_api_integration/observability/synthetics/private_location_test_service.ts b/x-pack/test/alerting_api_integration/observability/synthetics/private_location_test_service.ts deleted file mode 100644 index e9ac7237dca52..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/synthetics/private_location_test_service.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { v4 as uuidv4 } from 'uuid'; -import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; -import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/saved_objects/private_locations'; -import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types'; -import { Agent as SuperTestAgent } from 'supertest'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export const INSTALLED_VERSION = '1.1.1'; - -export class PrivateLocationTestService { - private supertest: SuperTestAgent; - private readonly getService: FtrProviderContext['getService']; - - constructor(getService: FtrProviderContext['getService']) { - this.supertest = getService('supertest'); - this.getService = getService; - } - - async installSyntheticsPackage() { - await this.supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); - const response = await this.supertest - .get(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) - .set('kbn-xsrf', 'true') - .expect(200); - if (response.body.item.status !== 'installed') { - await this.supertest - .post(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) - .set('kbn-xsrf', 'true') - .send({ force: true }) - .expect(200); - } - } - - async addTestPrivateLocation() { - const apiResponse = await this.addFleetPolicy(uuidv4()); - const testPolicyId = apiResponse.body.item.id; - return (await this.setTestLocations([testPolicyId]))[0]; - } - - async addFleetPolicy(name: string) { - return this.supertest - .post('/api/fleet/agent_policies?sys_monitoring=true') - .set('kbn-xsrf', 'true') - .send({ - name, - description: '', - namespace: 'default', - monitoring_enabled: [], - }) - .expect(200); - } - - async setTestLocations(testFleetPolicyIds: string[]) { - const server = this.getService('kibanaServer'); - - const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({ - label: 'Test private location ' + index, - agentPolicyId: id, - id, - geo: { - lat: 0, - lon: 0, - }, - isServiceManaged: false, - })); - - await server.savedObjects.create({ - type: privateLocationsSavedObjectName, - id: privateLocationsSavedObjectId, - attributes: { - locations, - }, - overwrite: true, - }); - return locations; - } -} diff --git a/x-pack/test/alerting_api_integration/observability/synthetics/synthetics_rule_helper.ts b/x-pack/test/alerting_api_integration/observability/synthetics/synthetics_rule_helper.ts index 2915cb5ee5d3b..a2da1c849945f 100644 --- a/x-pack/test/alerting_api_integration/observability/synthetics/synthetics_rule_helper.ts +++ b/x-pack/test/alerting_api_integration/observability/synthetics/synthetics_rule_helper.ts @@ -16,9 +16,9 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { Agent as SuperTestAgent } from 'supertest'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; +import { PrivateLocationTestService } from '../../../api_integration/apis/synthetics/services/private_location_test_service'; import { waitForAlertInIndex } from '../helpers/alerting_wait_for_helpers'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { PrivateLocationTestService } from './private_location_test_service'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; export const SYNTHETICS_ALERT_ACTION_INDEX = 'alert-action-synthetics'; @@ -172,11 +172,6 @@ export class SyntheticsRuleHelper { return result.body as EncryptedSyntheticsSavedMonitor; } - async addPrivateLocation() { - await this.locService.installSyntheticsPackage(); - return this.locService.addTestPrivateLocation(); - } - async waitForStatusAlert({ ruleId, filters, diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index 051ae14396687..5b0c967601638 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -37,6 +37,7 @@ export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); let testFleetPolicyID: string; + let pvtLoc: PrivateLocation; const testPolicyName = 'Fleet test server policy' + Date.now(); let _httpMonitorJson: HTTPFields; @@ -68,29 +69,15 @@ export default function ({ getService }: FtrProviderContext) { httpMonitorJson = _httpMonitorJson; }); - it('adds a test fleet policy', async () => { - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testFleetPolicyID = apiResponse.body.item.id; - }); - it('add a test private location', async () => { - await testPrivateLocations.setTestLocations([testFleetPolicyID]); + pvtLoc = await testPrivateLocations.addPrivateLocation(); + testFleetPolicyID = pvtLoc.id; const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS); const testResponse: Array = [ ...getDevLocation('mockDevUrl'), - { - id: testFleetPolicyID, - isServiceManaged: false, - isInvalid: false, - label: 'Test private location 0', - geo: { - lat: 0, - lon: 0, - }, - agentPolicyId: testFleetPolicyID, - }, + { ...pvtLoc, isInvalid: false }, ]; expect(apiResponse.body.locations).eql(testResponse); @@ -137,16 +124,7 @@ export default function ({ getService }: FtrProviderContext) { it('adds a monitor in private location', async () => { const newMonitor = httpMonitorJson; - newMonitor.locations.push({ - id: testFleetPolicyID, - agentPolicyId: testFleetPolicyID, - label: 'Test private location 0', - isServiceManaged: false, - geo: { - lat: 0, - lon: 0, - }, - }); + newMonitor.locations.push(pvtLoc); const { body, rawBody } = await addMonitorAPI(newMonitor); @@ -182,19 +160,13 @@ export default function ({ getService }: FtrProviderContext) { const resPolicy = await testPrivateLocations.addFleetPolicy(testPolicyName + 1); testFleetPolicyID2 = resPolicy.body.item.id; - await testPrivateLocations.setTestLocations([testFleetPolicyID, testFleetPolicyID2]); - - httpMonitorJson.locations.push({ - id: testFleetPolicyID2, - agentPolicyId: testFleetPolicyID2, - label: 'Test private location ' + 1, - isServiceManaged: false, - geo: { - lat: 0, - lon: 0, - }, + const pvtLoc2 = await testPrivateLocations.addPrivateLocation({ + policyId: testFleetPolicyID2, + label: 'Test private location 1', }); + httpMonitorJson.locations.push(pvtLoc2); + const apiResponse = await supertestAPI .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId) .set('kbn-xsrf', 'true') @@ -308,54 +280,23 @@ export default function ({ getService }: FtrProviderContext) { }); it('handles spaces', async () => { - const username = 'admin'; - const password = `${username}-password`; - const roleName = 'uptime-role'; - const SPACE_ID = `test-space-${uuidv4()}`; - const SPACE_NAME = `test-space-name ${uuidv4()}`; + const { username, password, SPACE_ID, roleName } = await monitorTestService.addsNewSpace(); + let monitorId = ''; const monitor = { ...httpMonitorJson, name: `Test monitor ${uuidv4()}`, [ConfigKey.NAMESPACE]: 'default', - locations: [ - { - id: testFleetPolicyID, - agentPolicyId: testFleetPolicyID, - label: 'Test private location 0', - isServiceManaged: false, - geo: { - lat: 0, - lon: 0, - }, - }, - ], + locations: [pvtLoc], }; try { - await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - await security.role.create(roleName, { - kibana: [ - { - feature: { - uptime: ['all'], - actions: ['all'], - }, - spaces: ['*'], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); const apiResponse = await supertestWithoutAuth .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) .auth(username, password) .set('kbn-xsrf', 'true') - .send(monitor) - .expect(200); + .send(monitor); + expect(apiResponse.status).eql(200, JSON.stringify(apiResponse.body)); const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body; expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index ea8821901c9e9..5518988ff78c7 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -46,8 +46,6 @@ export default function ({ getService }: FtrProviderContext) { let icmpProjectMonitors: ProjectMonitorsRequest; let testPolicyId = ''; - const testPolicyName = 'Fleet test server policy' + Date.now(); - const setUniqueIds = (request: ProjectMonitorsRequest) => { return { ...request, @@ -87,9 +85,8 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); await testPrivateLocations.installSyntheticsPackage(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + const loc = await testPrivateLocations.addPrivateLocation(); + testPolicyId = loc.id; await supertest .post(SYNTHETICS_API_URLS.PARAMS) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts index 8ab44a6615890..c9c6c293d6130 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts @@ -24,9 +24,6 @@ export default function ({ getService }: FtrProviderContext) { let projectMonitors: ProjectMonitorsRequest; const monitorTestService = new SyntheticsMonitorTestService(getService); - - let testPolicyId = ''; - const testPolicyName = 'Fleet test server policy' + Date.now(); const testPrivateLocations = new PrivateLocationTestService(getService); const setUniqueIds = (request: ProjectMonitorsRequest) => { @@ -42,10 +39,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); await testPrivateLocations.installSyntheticsPackage(); - - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + await testPrivateLocations.addPrivateLocation(); }); after(async () => { diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts index f8781295e8005..4192529654a28 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts @@ -59,10 +59,8 @@ export default function ({ getService }: FtrProviderContext) { await testPrivateLocations.installSyntheticsPackage(); - const testPolicyName = 'Fleet test server policy' + Date.now(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + const loc = await testPrivateLocations.addPrivateLocation(); + testPolicyId = loc.id; }); beforeEach(() => { diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts index ecd824a1052e7..0cb982ec90cc6 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts @@ -37,11 +37,10 @@ export default function ({ getService }: FtrProviderContext) { }; before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); await testPrivateLocations.installSyntheticsPackage(); - const testPolicyName = 'Fleet test server policy' + Date.now(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + const loc = await testPrivateLocations.addPrivateLocation(); + testPolicyId = loc.id; }); beforeEach(() => { diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index 7c23c4981c9cf..9767d1e447927 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -79,10 +79,8 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - const testPolicyName = 'Fleet test server policy' + Date.now(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + const loc = await testPrivateLocations.addPrivateLocation(); + testPolicyId = loc.id; }); after(async () => { diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts index e8112627397c4..aeb0eaa0299b3 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts @@ -240,7 +240,7 @@ export default function ({ getService }: FtrProviderContext) { it('can add private location to existing monitor', async () => { await testPrivateLocations.installSyntheticsPackage(); - pvtLoc = await testPrivateLocations.addTestPrivateLocation(); + pvtLoc = await testPrivateLocations.addPrivateLocation(); expect(pvtLoc).not.empty(); diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts index d996d0181df6b..53089d2bec2d3 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts @@ -22,13 +22,13 @@ export default function ({ getService }: FtrProviderContext) { this.tags('skipCloud'); const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); let projectMonitors: LegacyProjectMonitorsRequest; let httpProjectMonitors: LegacyProjectMonitorsRequest; let tcpProjectMonitors: LegacyProjectMonitorsRequest; let icmpProjectMonitors: LegacyProjectMonitorsRequest; - let testPolicyId = ''; const testPrivateLocations = new PrivateLocationTestService(getService); const setUniqueIds = (request: LegacyProjectMonitorsRequest) => { @@ -39,12 +39,10 @@ export default function ({ getService }: FtrProviderContext) { }; before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); await testPrivateLocations.installSyntheticsPackage(); - const testPolicyName = 'Fleet test server policy' + Date.now(); - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testPolicyId = apiResponse.body.item.id; - await testPrivateLocations.setTestLocations([testPolicyId]); + await testPrivateLocations.addPrivateLocation(); }); beforeEach(() => { diff --git a/x-pack/test/api_integration/apis/synthetics/index.ts b/x-pack/test/api_integration/apis/synthetics/index.ts index a8b39893570c2..15e76126e9555 100644 --- a/x-pack/test/api_integration/apis/synthetics/index.ts +++ b/x-pack/test/api_integration/apis/synthetics/index.ts @@ -35,5 +35,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./inspect_monitor')); loadTestFile(require.resolve('./test_now_monitor')); loadTestFile(require.resolve('./suggestions')); + loadTestFile(require.resolve('./private_location_apis')); }); } diff --git a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts index 71132965ee520..7889f4ad37dfc 100644 --- a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts @@ -171,7 +171,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('inspect http monitor in private location', async () => { - const location = await testPrivateLocations.addTestPrivateLocation(); + const location = await testPrivateLocations.addPrivateLocation(); const apiResponse = await monitorTestService.inspectMonitor({ ..._monitors[0], locations: [ diff --git a/x-pack/test/api_integration/apis/synthetics/private_location_apis.ts b/x-pack/test/api_integration/apis/synthetics/private_location_apis.ts new file mode 100644 index 0000000000000..415c91af28347 --- /dev/null +++ b/x-pack/test/api_integration/apis/synthetics/private_location_apis.ts @@ -0,0 +1,64 @@ +/* + * 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 expect from '@kbn/expect'; +import { + legacyPrivateLocationsSavedObjectId, + legacyPrivateLocationsSavedObjectName, + privateLocationSavedObjectName, +} from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { PrivateLocationTestService } from './services/private_location_test_service'; + +export default function ({ getService }: FtrProviderContext) { + describe('PrivateLocationAPI', function () { + this.tags('skipCloud'); + + const kServer = getService('kibanaServer'); + + const testPrivateLocations = new PrivateLocationTestService(getService); + + before(async () => { + await testPrivateLocations.installSyntheticsPackage(); + + await kServer.savedObjects.clean({ + types: [legacyPrivateLocationsSavedObjectName, privateLocationSavedObjectName], + }); + }); + + it('adds a test legacy private location', async () => { + const locs = await testPrivateLocations.addLegacyPrivateLocations(); + expect(locs.length).to.be(2); + }); + + it('adds a test private location', async () => { + await testPrivateLocations.addPrivateLocation(); + }); + + it('list all locations', async () => { + const locs = await testPrivateLocations.fetchAll(); + expect(locs.body.length).to.be(3); + }); + + it('migrates to new saved objet type', async () => { + const newData = await kServer.savedObjects.find({ + type: privateLocationSavedObjectName, + }); + + expect(newData.saved_objects.length).to.be(3); + + try { + await kServer.savedObjects.get({ + type: legacyPrivateLocationsSavedObjectName, + id: legacyPrivateLocationsSavedObjectId, + }); + } catch (e) { + expect(e.response.status).to.be(404); + } + }); + }); +} diff --git a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts index f08a708358a2f..f923a5dd887c1 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts @@ -4,11 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { v4 as uuidv4 } from 'uuid'; -import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; -import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/saved_objects/private_locations'; -import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types'; +import expect from '@kbn/expect'; +import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types'; import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services'; +import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { + legacyPrivateLocationsSavedObjectId, + legacyPrivateLocationsSavedObjectName, +} from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; import { FtrProviderContext } from '../../../ftr_provider_context'; export const INSTALLED_VERSION = '1.1.1'; @@ -37,18 +40,12 @@ export class PrivateLocationTestService { } } - async addTestPrivateLocation() { - const apiResponse = await this.addFleetPolicy(uuidv4()); - const testPolicyId = apiResponse.body.item.id; - return (await this.setTestLocations([testPolicyId]))[0]; - } - - async addFleetPolicy(name: string) { + async addFleetPolicy(name?: string) { return this.supertest .post('/api/fleet/agent_policies?sys_monitoring=true') .set('kbn-xsrf', 'true') .send({ - name, + name: name ?? 'Fleet test server policy' + Date.now(), description: '', namespace: 'default', monitoring_enabled: [], @@ -56,28 +53,72 @@ export class PrivateLocationTestService { .expect(200); } - async setTestLocations(testFleetPolicyIds: string[]) { - const server = this.getService('kibanaServer'); + async addPrivateLocation({ policyId, label }: { policyId?: string; label?: string } = {}) { + let agentPolicyId = policyId; - const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({ - label: 'Test private location ' + index, - agentPolicyId: id, - id, + if (!agentPolicyId) { + const apiResponse = await this.addFleetPolicy(); + agentPolicyId = apiResponse.body.item.id; + } + + const location: Omit = { + label: label ?? 'Test private location 0', + agentPolicyId: agentPolicyId!, geo: { lat: 0, lon: 0, }, - isServiceManaged: false, - })); + }; + + const response = await this.supertest + .post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS) + .set('kbn-xsrf', 'true') + .send(location); + + expect(response.status).to.be(200); + + const { isInvalid, ...loc } = response.body; + + return loc; + } + + async addLegacyPrivateLocations() { + const server = this.getService('kibanaServer'); + const fleetPolicy = await this.addFleetPolicy(); + const fleetPolicy2 = await this.addFleetPolicy(); + + const locs = [ + { + id: fleetPolicy.body.item.id, + agentPolicyId: fleetPolicy.body.item.id, + name: 'Test private location 1', + lat: 0, + lon: 0, + }, + { + id: fleetPolicy2.body.item.id, + agentPolicyId: fleetPolicy2.body.item.id, + name: 'Test private location 2', + lat: 0, + lon: 0, + }, + ]; await server.savedObjects.create({ - type: privateLocationsSavedObjectName, - id: privateLocationsSavedObjectId, + type: legacyPrivateLocationsSavedObjectName, + id: legacyPrivateLocationsSavedObjectId, attributes: { - locations, + locations: locs, }, overwrite: true, }); - return locations; + return locs; + } + + async fetchAll() { + return this.supertest + .get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS) + .set('kbn-xsrf', 'true') + .expect(200); } } diff --git a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts index c0c15024b5401..498da8c6b1800 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts @@ -202,7 +202,7 @@ export class SyntheticsMonitorTestService { full_name: 'a kibana user', }); - return { username, password, SPACE_ID }; + return { username, password, SPACE_ID, roleName }; } async deleteMonitor(monitorId?: string | string[], statusCode = 200, spaceId?: string) { diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts index e0a79a8905ee8..44cd5b19d6697 100644 --- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts @@ -60,15 +60,9 @@ export default function ({ getService }: FtrProviderContext) { httpMonitorJson = _httpMonitorJson; }); - const testPolicyName = 'Fleet test server policy' + Date.now(); - - it('adds a test fleet policy', async () => { - const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); - testFleetPolicyID = apiResponse.body.item.id; - }); - it('add a test private location', async () => { - await testPrivateLocations.setTestLocations([testFleetPolicyID]); + const loc = await testPrivateLocations.addPrivateLocation(); + testFleetPolicyID = loc.id; const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS); diff --git a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts index 0dcb52e348f8d..f6a98bf77e4fe 100644 --- a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts +++ b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts @@ -48,6 +48,15 @@ export default function ({ getService }: FtrProviderContext) { ); }; + before(async () => { + // clean up all api keys + const { body } = await esSupertest.get(`/_security/api_key`).query({ with_limited_by: true }); + const apiKeys = body.api_keys || []; + for (const apiKey of apiKeys) { + await esSupertest.delete(`/_security/api_key`).send({ ids: [apiKey.id] }); + } + }); + describe('[PUT] /internal/uptime/service/enablement', () => { beforeEach(async () => { const apiKeys = await getApiKeys(); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts index d4cca44448676..1e3ca0eecfafe 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts @@ -8728,6 +8728,18 @@ export default function ({ getService }: FtrProviderContext) { "saved_object:uptime-synthetics-api-key/delete", "saved_object:uptime-synthetics-api-key/bulk_delete", "saved_object:uptime-synthetics-api-key/share_to_space", + "saved_object:synthetics-private-location/bulk_get", + "saved_object:synthetics-private-location/get", + "saved_object:synthetics-private-location/find", + "saved_object:synthetics-private-location/open_point_in_time", + "saved_object:synthetics-private-location/close_point_in_time", + "saved_object:synthetics-private-location/create", + "saved_object:synthetics-private-location/bulk_create", + "saved_object:synthetics-private-location/update", + "saved_object:synthetics-private-location/bulk_update", + "saved_object:synthetics-private-location/delete", + "saved_object:synthetics-private-location/bulk_delete", + "saved_object:synthetics-private-location/share_to_space", "saved_object:synthetics-privates-locations/bulk_get", "saved_object:synthetics-privates-locations/get", "saved_object:synthetics-privates-locations/find", @@ -9417,6 +9429,18 @@ export default function ({ getService }: FtrProviderContext) { "saved_object:uptime-synthetics-api-key/delete", "saved_object:uptime-synthetics-api-key/bulk_delete", "saved_object:uptime-synthetics-api-key/share_to_space", + "saved_object:synthetics-private-location/bulk_get", + "saved_object:synthetics-private-location/get", + "saved_object:synthetics-private-location/find", + "saved_object:synthetics-private-location/open_point_in_time", + "saved_object:synthetics-private-location/close_point_in_time", + "saved_object:synthetics-private-location/create", + "saved_object:synthetics-private-location/bulk_create", + "saved_object:synthetics-private-location/update", + "saved_object:synthetics-private-location/bulk_update", + "saved_object:synthetics-private-location/delete", + "saved_object:synthetics-private-location/bulk_delete", + "saved_object:synthetics-private-location/share_to_space", "saved_object:synthetics-privates-locations/bulk_get", "saved_object:synthetics-privates-locations/get", "saved_object:synthetics-privates-locations/find",