From ad5344ae6a944448855875a146501cb5b9414610 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 16 Jul 2024 17:54:29 +0200 Subject: [PATCH 01/11] supports slo assets installation --- .../context/fixtures/integration.nginx.ts | 1 + .../context/fixtures/integration.okta.ts | 1 + .../package_to_package_policy.test.ts | 1 + .../plugins/fleet/common/types/models/epm.ts | 2 + x-pack/plugins/fleet/kibana.jsonc | 1 + .../integrations/sections/epm/constants.tsx | 3 + x-pack/plugins/fleet/server/plugin.ts | 6 + .../fleet/server/routes/epm/handlers.ts | 1 + .../routes/epm/kibana_assets_handler.ts | 1 + .../fleet/server/services/app_context.ts | 8 + .../services/epm/kibana/assets/install.ts | 87 +++++++- .../server/services/epm/package_service.ts | 1 + .../services/epm/packages/_install_package.ts | 5 + .../server/services/epm/packages/install.ts | 38 ++-- .../_state_machine_package_install.ts | 3 +- .../steps/step_install_kibana_assets.ts | 12 +- .../server/services/epm/packages/remove.ts | 24 ++- x-pack/plugins/fleet/tsconfig.json | 1 + .../components/custom_threshold/types.ts | 2 - .../observability/tsconfig.json | 1 - .../observability_onboarding/kibana.jsonc | 2 +- .../server/domain/services/validate_slo.ts | 2 +- .../slo/server/index.ts | 2 + .../slo/server/plugin.ts | 76 ++++--- .../slo/server/routes/register_routes.ts | 3 + .../slo/server/routes/slo/route.ts | 28 --- .../slo/server/saved_objects/slo.ts | 6 + .../slo/server/services/delete_slo.ts | 4 +- .../slo/server/services/slo_client.ts | 190 ++++++++++++++++++ .../server/services/transform_manager.test.ts | 68 ------- .../slo/server/services/transform_manager.ts | 26 ++- 31 files changed, 446 insertions(+), 160 deletions(-) create mode 100644 x-pack/plugins/observability_solution/slo/server/services/slo_client.ts diff --git a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts index 37ac4fa811947..6edd2653c6244 100644 --- a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts +++ b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.nginx.ts @@ -257,6 +257,7 @@ export const item: GetInfoResponse['item'] = { tag: [], osquery_pack_asset: [], osquery_saved_query: [], + slo: [], }, elasticsearch: { ingest_pipeline: [ diff --git a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts index 8778938443661..d70529854aeec 100644 --- a/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts +++ b/x-pack/plugins/fleet/.storybook/context/fixtures/integration.okta.ts @@ -109,6 +109,7 @@ export const item: GetInfoResponse['item'] = { security_rule: [], csp_rule_template: [], tag: [], + slo: [], }, elasticsearch: { ingest_pipeline: [ diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index 1545caa39b917..b0576d056a970 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -37,6 +37,7 @@ describe('Fleet - packageToPackagePolicy', () => { tag: [], osquery_pack_asset: [], osquery_saved_query: [], + slo: [], }, elasticsearch: { ingest_pipeline: [], diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index fda07095de95d..e2e1d8ee1dee6 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -64,6 +64,7 @@ export enum KibanaAssetType { osqueryPackAsset = 'osquery_pack_asset', osquerySavedQuery = 'osquery_saved_query', tag = 'tag', + slo = 'slo', } /* @@ -82,6 +83,7 @@ export enum KibanaSavedObjectType { osqueryPackAsset = 'osquery-pack-asset', osquerySavedQuery = 'osquery-saved-query', tag = 'tag', + slo = 'slo', } export enum ElasticsearchAssetType { diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index 54b4a4928594c..a704ba54c4cb0 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -35,6 +35,7 @@ "telemetry", "discover", "ingestPipelines", + "slo", "spaces", "guidedOnboarding", "integrationAssistant", diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index 1a1fba813bd8c..24d067314d4d4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -86,6 +86,9 @@ export const AssetTitleMap: Record< osquery_saved_query: i18n.translate('xpack.fleet.epm.assetTitles.osquerySavedQuery', { defaultMessage: 'Osquery saved queries', }), + slo: i18n.translate('xpack.fleet.epm.assetTitles.slos', { + defaultMessage: 'SLOs', + }), // ES ilm_policy: i18n.translate('xpack.fleet.epm.assetTitles.ilmPolicies', { diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index fa3eefd90c227..51146238d7f4e 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -56,6 +56,8 @@ import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { SLOPluginSetup, SLOPluginStart } from '@kbn/slo-plugin/server/plugin'; + import type { FleetConfigType } from '../common/types'; import type { FleetAuthz } from '../common'; import { @@ -140,6 +142,7 @@ export interface FleetSetupDeps { telemetry?: TelemetryPluginSetup; taskManager: TaskManagerSetupContract; fieldsMetadata: FieldsMetadataServerSetup; + slo?: SLOPluginSetup; } export interface FleetStartDeps { @@ -150,6 +153,7 @@ export interface FleetStartDeps { telemetry?: TelemetryPluginStart; savedObjectsTagging: SavedObjectTaggingStart; taskManager: TaskManagerStartContract; + slo?: SLOPluginStart; } export interface FleetAppContext { @@ -177,6 +181,7 @@ export interface FleetAppContext { messageSigningService: MessageSigningServiceInterface; auditLogger?: AuditLogger; uninstallTokenService: UninstallTokenServiceInterface; + sloStart?: SLOPluginStart; } export type FleetSetupContract = void; @@ -638,6 +643,7 @@ export class FleetPlugin bulkActionsResolver: this.bulkActionsResolver!, messageSigningService, uninstallTokenService, + sloStart: plugins.slo, }); licenseService.start(plugins.licensing.license$); this.telemetryEventsSender.start(plugins.telemetry, core).catch(() => {}); diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 9ce8c9afa4e6a..0338763c9b68b 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -367,6 +367,7 @@ export const createCustomIntegrationHandler: FleetRequestHandler< force, authorizationHeader, kibanaVersion, + sloClient: appContextService.getSloStart()?.sloClient, }); if (!res.error) { diff --git a/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts b/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts index 8fe83f98669d1..2e2e965123fcb 100644 --- a/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts +++ b/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts @@ -57,6 +57,7 @@ export const installPackageKibanaAssetsHandler: FleetRequestHandler< const { packageInfo } = installedPkgWithAssets; await installKibanaAssetsAndReferences({ + esClient: appContextService.getInternalUserESClient(), savedObjectsClient, logger, pkgName, diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 970291bf7d552..7ed07af50cb51 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -34,6 +34,8 @@ import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/ import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { SLOPluginStart } from '@kbn/slo-plugin/server/plugin'; + import type { FleetConfigType } from '../../common/types'; import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { @@ -81,6 +83,7 @@ class AppContextService { private bulkActionsResolver: BulkActionsResolver | undefined; private messageSigningService: MessageSigningServiceInterface | undefined; private uninstallTokenService: UninstallTokenServiceInterface | undefined; + private sloStart: SLOPluginStart | undefined; public start(appContext: FleetAppContext) { this.data = appContext.data; @@ -105,6 +108,7 @@ class AppContextService { this.bulkActionsResolver = appContext.bulkActionsResolver; this.messageSigningService = appContext.messageSigningService; this.uninstallTokenService = appContext.uninstallTokenService; + this.sloStart = appContext.sloStart; if (appContext.config$) { this.config$ = appContext.config$; @@ -152,6 +156,10 @@ class AppContextService { return this.cloud; } + public getSloStart() { + return this.sloStart; + } + public getLogger() { if (!this.logger) { throw new Error('Logger not set.'); diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index 58385fb544a57..98f1740416b42 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -19,6 +19,12 @@ import type { import { createListStream } from '@kbn/utils'; import { partition } from 'lodash'; +import type { SLOClient } from '@kbn/slo-plugin/server/services/slo_client'; + +import type { ElasticsearchClient } from '@kbn/core/server'; + +import type { CreateSLOParams } from '@kbn/slo-schema'; + import { getAssetFromAssetsMap, getPathParts } from '../../archive'; import { KibanaAssetType, KibanaSavedObjectType } from '../../../../types'; import type { AssetReference, Installation, PackageSpecTags } from '../../../../types'; @@ -73,6 +79,7 @@ export const KibanaSavedObjectTypeMapping: Record ArchiveAsset[]> = { @@ -102,13 +109,17 @@ export function createSavedObjectKibanaAsset(asset: ArchiveAsset): SavedObjectTo } export async function installKibanaAssets(options: { - savedObjectsClient: SavedObjectsClientContract; + soClient: SavedObjectsClientContract; savedObjectsImporter: SavedObjectsImporterContract; logger: Logger; pkgName: string; kibanaAssets: Record; + sloClient?: SLOClient; + spaceId: string; + esClient: ElasticsearchClient; }): Promise { - const { kibanaAssets, savedObjectsClient, savedObjectsImporter, logger } = options; + const { kibanaAssets, soClient, esClient, savedObjectsImporter, logger, sloClient, spaceId } = + options; const assetsToInstall = Object.entries(kibanaAssets).flatMap(([assetType, assets]) => { if (!validKibanaAssetTypes.has(assetType as KibanaAssetType)) { @@ -142,15 +153,25 @@ export async function installKibanaAssets(options: { managed: true, }); - await makeManagedIndexPatternsGlobal(savedObjectsClient); + await makeManagedIndexPatternsGlobal(soClient); const installedAssets = await installKibanaSavedObjects({ logger, savedObjectsImporter, - kibanaAssets: assetsToInstall, + kibanaAssets: assetsToInstall.filter((asset) => asset.type !== KibanaSavedObjectType.slo), }); - return installedAssets; + const sloAssets = assetsToInstall.filter((asset) => asset.type === KibanaSavedObjectType.slo); + + const installedSlos = await installSLOAssets({ + sloAssets, + sloClient, + soClient, + esClient, + spaceId, + }); + + return installedAssets.concat(installedSlos); } export async function installKibanaAssetsAndReferencesMultispace({ @@ -162,6 +183,8 @@ export async function installKibanaAssetsAndReferencesMultispace({ installedPkg, spaceId, assetTags, + esClient, + sloClient, installAsAdditionalSpace, }: { savedObjectsClient: SavedObjectsClientContract; @@ -173,6 +196,8 @@ export async function installKibanaAssetsAndReferencesMultispace({ spaceId: string; assetTags?: PackageSpecTags[]; installAsAdditionalSpace?: boolean; + esClient: ElasticsearchClient; + sloClient?: SLOClient; }) { if (installedPkg && !installAsAdditionalSpace) { // Install in every space => upgrades @@ -185,10 +210,12 @@ export async function installKibanaAssetsAndReferencesMultispace({ installedPkg, spaceId, assetTags, + esClient, + sloClient, installAsAdditionalSpace, }); - for (const additionnalSpaceId of Object.keys( + for (const additionalSpaceId of Object.keys( installedPkg.attributes.additional_spaces_installed_kibana ?? {} )) { await installKibanaAssetsAndReferences({ @@ -198,8 +225,10 @@ export async function installKibanaAssetsAndReferencesMultispace({ pkgTitle, packageInstallContext, installedPkg, - spaceId: additionnalSpaceId, assetTags, + esClient, + sloClient, + spaceId: additionalSpaceId, installAsAdditionalSpace: true, }); } @@ -215,11 +244,47 @@ export async function installKibanaAssetsAndReferencesMultispace({ installedPkg, spaceId, assetTags, + esClient, + sloClient, installAsAdditionalSpace, }); } +export async function installSLOAssets({ + sloClient, + soClient, + esClient, + spaceId, + sloAssets, +}: { + sloAssets: ArchiveAsset[]; + sloClient?: SLOClient; + soClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + spaceId: string; +}) { + if (!sloClient || !sloAssets.length) { + return []; + } + + const installedSLOs = []; + + for (const asset of sloAssets) { + const attr = asset.attributes as CreateSLOParams; + const slo = await sloClient.createSLO({ + soClient, + esClient, + params: { ...attr, id: asset.id }, + spaceId, + }); + installedSLOs.push({ id: slo.id, type: 'slo', meta: {} }); + } + + return installedSLOs; +} + export async function installKibanaAssetsAndReferences({ + esClient, savedObjectsClient, logger, pkgName, @@ -229,6 +294,7 @@ export async function installKibanaAssetsAndReferences({ spaceId, assetTags, installAsAdditionalSpace, + sloClient, }: { savedObjectsClient: SavedObjectsClientContract; logger: Logger; @@ -239,6 +305,8 @@ export async function installKibanaAssetsAndReferences({ spaceId: string; assetTags?: PackageSpecTags[]; installAsAdditionalSpace?: boolean; + esClient: ElasticsearchClient; + sloClient?: SLOClient; }) { const { savedObjectsImporter, savedObjectTagAssignmentService, savedObjectTagClient } = getSpaceAwareSaveobjectsClients(spaceId); @@ -258,11 +326,14 @@ export async function installKibanaAssetsAndReferences({ } const importedAssets = await installKibanaAssets({ - savedObjectsClient, + soClient: savedObjectsClient, logger, savedObjectsImporter, pkgName, kibanaAssets, + sloClient, + esClient, + spaceId, }); if (installAsAdditionalSpace) { const assets = importedAssets.map( diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index ae2f5721aae8b..f12329f9f46d9 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -262,6 +262,7 @@ class PackageClientImpl implements PackageClient { datasets, spaceId, installSource: 'custom', + sloClient: appContextService.getSloStart()?.sloClient, esClient: this.internalEsClient, savedObjectsClient: this.internalSoClient, neverIgnoreVerificationError: !force, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 97b0eeb823e02..31ef9264fed04 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -12,6 +12,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import type { SLOClient } from '@kbn/slo-plugin/server/services/slo_client'; import type { HTTPAuthorizationHeader } from '../../../../common/http_authorization_header'; import type { PackageInstallContext } from '../../../../common/types'; @@ -69,6 +70,7 @@ export async function _installPackage({ authorizationHeader, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }: { savedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; @@ -83,6 +85,7 @@ export async function _installPackage({ authorizationHeader?: HTTPAuthorizationHeader | null; ignoreMappingUpdateErrors?: boolean; skipDataStreamRollover?: boolean; + sloClient?: SLOClient; }): Promise { const { packageInfo, paths } = packageInstallContext; const { name: pkgName, version: pkgVersion, title: pkgTitle } = packageInfo; @@ -147,6 +150,7 @@ export async function _installPackage({ logger.debug(`Package install - Installing Kibana assets`); const kibanaAssetPromise = withPackageSpan('Install Kibana assets', () => installKibanaAssetsAndReferences({ + esClient, savedObjectsClient, pkgName, pkgTitle, @@ -154,6 +158,7 @@ export async function _installPackage({ installedPkg, logger, spaceId, + sloClient, assetTags: packageInfo?.asset_tags, }) ); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index f27cb794475c9..f89c7c8b7bb76 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -22,11 +22,14 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; import pRetry from 'p-retry'; import type { LicenseType } from '@kbn/licensing-plugin/server'; +import type { SLOClient } from '@kbn/slo-plugin/server/services/slo_client'; + import type { KibanaAssetReference, PackageDataStreamTypes, PackageInstallContext, } from '../../../../common/types'; + import type { HTTPAuthorizationHeader } from '../../../../common/http_authorization_header'; import { isPackagePrerelease, getNormalizedDataStreams } from '../../../../common/services'; import { FLEET_INSTALL_FORMAT_VERSION } from '../../../constants/fleet_es_assets'; @@ -343,6 +346,7 @@ interface InstallRegistryPackageParams { authorizationHeader?: HTTPAuthorizationHeader | null; ignoreMappingUpdateErrors?: boolean; skipDataStreamRollover?: boolean; + sloClient?: SLOClient; } export interface CustomPackageDatasetConfiguration { @@ -358,6 +362,7 @@ interface InstallCustomPackageParams { force?: boolean; authorizationHeader?: HTTPAuthorizationHeader | null; kibanaVersion: string; + sloClient?: SLOClient; } interface InstallUploadedArchiveParams { savedObjectsClient: SavedObjectsClientContract; @@ -371,6 +376,7 @@ interface InstallUploadedArchiveParams { skipDataStreamRollover?: boolean; isBundledPackage?: boolean; skipRateLimitCheck?: boolean; + sloClient?: SLOClient; } function getTelemetryEvent(pkgName: string, pkgVersion: string): PackageUpdateEvent { @@ -407,6 +413,8 @@ async function installPackageFromRegistry({ skipDataStreamRollover = false, }: InstallRegistryPackageParams): Promise { const logger = appContextService.getLogger(); + const sloClient = appContextService.getSloStart()?.sloClient!; + // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion: version } = Registry.splitPkgKey(pkgkey); let pkgVersion = version ?? ''; @@ -489,6 +497,7 @@ async function installPackageFromRegistry({ authorizationHeader, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }); } else { return await installPackageCommon({ @@ -507,6 +516,7 @@ async function installPackageFromRegistry({ authorizationHeader, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }); } } catch (e) { @@ -545,6 +555,7 @@ async function installPackageCommon(options: { authorizationHeader?: HTTPAuthorizationHeader | null; ignoreMappingUpdateErrors?: boolean; skipDataStreamRollover?: boolean; + sloClient?: SLOClient; }): Promise { const packageInfo = options.packageInstallContext.packageInfo; @@ -563,6 +574,7 @@ async function installPackageCommon(options: { ignoreMappingUpdateErrors, skipDataStreamRollover, packageInstallContext, + sloClient, } = options; let { telemetryEvent } = options; const logger = appContextService.getLogger(); @@ -639,6 +651,7 @@ async function installPackageCommon(options: { force, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }) .then(async (assets) => { logger.debug(`Removing old assets from previous versions of ${pkgName}`); @@ -696,6 +709,7 @@ async function installPackageWitStateMachine(options: { installType: InstallType; savedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; + sloClient: SLOClient; spaceId: string; force?: boolean; packageInstallContext: PackageInstallContext; @@ -723,6 +737,7 @@ async function installPackageWitStateMachine(options: { ignoreMappingUpdateErrors, skipDataStreamRollover, packageInstallContext, + sloClient, } = options; let { telemetryEvent } = options; const logger = appContextService.getLogger(); @@ -819,6 +834,7 @@ async function installPackageWitStateMachine(options: { force, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }) .then(async (assets) => { logger.debug(`Removing old assets from previous versions of ${pkgName}`); @@ -882,6 +898,7 @@ async function installPackageByUpload({ skipRateLimitCheck, }: InstallUploadedArchiveParams): Promise { const logger = appContextService.getLogger(); + const sloClient = appContextService.getSloStart()?.sloClient!; // if an error happens during getInstallType, report that we don't know let installType: InstallType = 'unknown'; @@ -960,6 +977,7 @@ async function installPackageByUpload({ authorizationHeader, ignoreMappingUpdateErrors, skipDataStreamRollover, + sloClient, }); } catch (e) { return { @@ -986,6 +1004,7 @@ export async function installPackage(args: InstallPackageParams): Promise { - auditLoggingService.writeCustomSoAuditLog({ - action: 'update', - id: pkgName, - savedObjectType: PACKAGES_SAVED_OBJECT_TYPE, - }); - - return savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - version: pkgVersion, - }); -}; - export const updateInstallStatusToFailed = async ({ logger, savedObjectsClient, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts index afad28d28a461..5bcd6eb363b67 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts @@ -11,9 +11,9 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import type { SLOClient } from '@kbn/slo-plugin/server'; import { PackageSavedObjectConflictError } from '../../../../errors'; - import type { HTTPAuthorizationHeader } from '../../../../../common/http_authorization_header'; import { INSTALL_STATES } from '../../../../../common/types'; import type { PackageInstallContext, StateNames, StateContext } from '../../../../../common/types'; @@ -51,6 +51,7 @@ import { handleState } from './state_machine'; export interface InstallContext extends StateContext { savedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; + sloClient?: SLOClient; logger: Logger; installedPkg?: SavedObject; packageInstallContext: PackageInstallContext; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts index 2db6f622d3281..df15380f8eaa0 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts @@ -12,7 +12,15 @@ import { withPackageSpan } from '../../utils'; import type { InstallContext } from '../_state_machine_package_install'; export async function stepInstallKibanaAssets(context: InstallContext) { - const { savedObjectsClient, logger, installedPkg, packageInstallContext, spaceId } = context; + const { + savedObjectsClient, + logger, + installedPkg, + packageInstallContext, + spaceId, + sloClient, + esClient, + } = context; const { packageInfo } = packageInstallContext; const { name: pkgName, title: pkgTitle } = packageInfo; @@ -25,6 +33,8 @@ export async function stepInstallKibanaAssets(context: InstallContext) { installedPkg, logger, spaceId, + sloClient, + esClient, assetTags: packageInfo?.asset_tags, }) ); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index e66c25e407ff5..e3b1a795bff93 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -130,7 +130,7 @@ export async function removeInstallation(options: { * installed in 8.x or later. */ async function deleteKibanaAssets({ - installedObjects, + installedObjects: allAssets, packageInfo, spaceId = DEFAULT_SPACE_ID, }: { @@ -138,6 +138,9 @@ async function deleteKibanaAssets({ spaceId?: string; packageInfo: RegistryPackage | ArchivePackage; }) { + const installedObjects = allAssets.filter(({ type }) => type !== 'slo'); + const sloAssets = allAssets.filter(({ type }) => type === 'slo'); + const savedObjectsClient = new SavedObjectsClient( appContextService.getSavedObjects().createInternalRepository() ); @@ -154,6 +157,7 @@ async function deleteKibanaAssets({ // which might create high memory pressure if a package has a lot of assets. if (minKibana && minKibana.major >= 8) { await bulkDeleteSavedObjects(installedObjects, namespace, savedObjectsClient); + await deleteSloAssets(sloAssets, namespace, savedObjectsClient); } else { const { resolved_objects: resolvedObjects } = await savedObjectsClient.bulkResolve( installedObjects, @@ -177,9 +181,27 @@ async function deleteKibanaAssets({ const assetsToDelete = foundObjects.map(({ saved_object: { id, type } }) => ({ id, type })); await bulkDeleteSavedObjects(assetsToDelete, namespace, savedObjectsClient); + await deleteSloAssets(sloAssets, namespace, savedObjectsClient); } } +const deleteSloAssets = async ( + sloAssets: KibanaAssetReference[], + namespace: string | undefined, + soClient: SavedObjectsClientContract +) => { + const sloClient = appContextService.getSloStart()?.sloClient; + if (!sloClient) { + return; + } + + const esClient = appContextService.getInternalUserESClient(); + + for (const { id } of sloAssets) { + await sloClient.deleteSLO({ sloId: id, soClient, esClient, spaceId: namespace ?? 'default' }); + } +}; + async function bulkDeleteSavedObjects( assetsToDelete: Array<{ id: string; type: string }>, namespace: string | undefined, diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index b45bd90010b42..cb6c4d6f11b77 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -37,6 +37,7 @@ "@kbn/cloud-plugin", "@kbn/usage-collection-plugin", "@kbn/home-plugin", + "@kbn/slo-plugin", // requiredBundles from ./kibana.json "@kbn/kibana-react-plugin", diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts index 8d5b1260a809c..93e7d914b06eb 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts @@ -15,7 +15,6 @@ import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; -import { OsqueryPluginStart } from '@kbn/osquery-plugin/public'; import { ALERT_GROUP } from '@kbn/rule-data-utils'; import { SharePluginStart } from '@kbn/share-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; @@ -71,7 +70,6 @@ export interface InfraClientStartDeps { lens: LensPublicStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; - osquery?: OsqueryPluginStart; share: SharePluginStart; spaces: SpacesPluginStart; storage: IStorageWrapper; diff --git a/x-pack/plugins/observability_solution/observability/tsconfig.json b/x-pack/plugins/observability_solution/observability/tsconfig.json index 7432602b54c43..32c32b2064bde 100644 --- a/x-pack/plugins/observability_solution/observability/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability/tsconfig.json @@ -78,7 +78,6 @@ "@kbn/actions-plugin", "@kbn/core-capabilities-common", "@kbn/observability-ai-assistant-plugin", - "@kbn/osquery-plugin", "@kbn/content-management-plugin", "@kbn/embeddable-plugin", "@kbn/aiops-plugin", diff --git a/x-pack/plugins/observability_solution/observability_onboarding/kibana.jsonc b/x-pack/plugins/observability_solution/observability_onboarding/kibana.jsonc index 79eb387c2486c..e2959c38db586 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability_onboarding/kibana.jsonc @@ -34,4 +34,4 @@ "common" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/observability_solution/slo/server/domain/services/validate_slo.ts b/x-pack/plugins/observability_solution/slo/server/domain/services/validate_slo.ts index fadf4ea08e6ea..410c9e7c9d29e 100644 --- a/x-pack/plugins/observability_solution/slo/server/domain/services/validate_slo.ts +++ b/x-pack/plugins/observability_solution/slo/server/domain/services/validate_slo.ts @@ -76,7 +76,7 @@ function validateSettings(slo: SLODefinition) { function isValidId(id: string): boolean { const MIN_ID_LENGTH = 8; - const MAX_ID_LENGTH = 36; + const MAX_ID_LENGTH = 48; return MIN_ID_LENGTH <= id.length && id.length <= MAX_ID_LENGTH; } diff --git a/x-pack/plugins/observability_solution/slo/server/index.ts b/x-pack/plugins/observability_solution/slo/server/index.ts index 5d6ccadb7f323..8dc80cb092677 100644 --- a/x-pack/plugins/observability_solution/slo/server/index.ts +++ b/x-pack/plugins/observability_solution/slo/server/index.ts @@ -18,6 +18,8 @@ export async function plugin(initializerContext: PluginInitializerContext) { export type { PluginSetup, PluginStart } from './plugin'; +export { SLOClient } from './services/slo_client'; + export const config = { schema: configSchema, exposeToBrowser: { diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index 9c701a3477485..6fa7b0d719d3e 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -28,18 +28,19 @@ import { import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { CloudSetup } from '@kbn/cloud-plugin/server'; import { SharePluginSetup } from '@kbn/share-plugin/server'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { AlertsLocatorDefinition } from '@kbn/observability-plugin/common'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { sloFeatureId } from '@kbn/observability-plugin/common'; +import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; +import { SLOClient } from './services/slo_client'; import { registerSloUsageCollector } from './lib/collectors/register'; import { SloOrphanSummaryCleanupTask } from './services/tasks/orphan_summary_cleanup_task'; import { slo, SO_SLO_TYPE } from './saved_objects'; import { DefaultResourceInstaller, DefaultSLOInstaller } from './services'; import { registerBurnRateRule } from './lib/rules/register_burn_rate_rule'; import { SloConfig } from '.'; -import { registerRoutes } from './routes/register_routes'; +import { registerRoutes, RegisterRoutesDependencies } from './routes/register_routes'; import { getSloServerRouteRepository } from './routes/get_slo_server_route_repository'; import { sloSettings, SO_SLO_SETTINGS_TYPE } from './saved_objects/slo_settings'; @@ -53,7 +54,7 @@ export interface PluginSetup { taskManager: TaskManagerSetupContract; spaces?: SpacesPluginSetup; cloud?: CloudSetup; - usageCollection?: UsageCollectionSetup; + licensing: LicensingPluginSetup; } export interface PluginStart { @@ -64,11 +65,20 @@ export interface PluginStart { dataViews: DataViewsServerPluginStart; } +export interface SLOPluginStart { + sloClient: SLOClient; +} + +export interface SLOPluginSetup { + sloClient: SLOClient; +} + const sloRuleTypes = [SLO_BURN_RATE_RULE_TYPE_ID]; export class SloPlugin implements Plugin { private readonly logger: Logger; private sloOrphanCleanupTask?: SloOrphanSummaryCleanupTask; + private sloClient?: SLOClient; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -142,38 +152,44 @@ export class SloPlugin implements Plugin { registerSloUsageCollector(plugins.usageCollection); + const dependencies: RegisterRoutesDependencies = { + licensing: plugins.licensing, + logger: this.logger, + pluginsSetup: { + ...plugins, + core, + }, + getDataViewsStart: async () => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.dataViews; + }, + getSpacesStart: async () => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.spaces; + }, + ruleDataService, + getRulesClientWithRequest: async (request) => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.alerting.getRulesClientWithRequest(request); + }, + getRacClientWithRequest: async (request) => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.ruleRegistry.getRacClientWithRequest(request); + }, + }; + registerRoutes({ core, config, - dependencies: { - pluginsSetup: { - ...plugins, - core, - }, - getDataViewsStart: async () => { - const [, pluginStart] = await core.getStartServices(); - return pluginStart.dataViews; - }, - getSpacesStart: async () => { - const [, pluginStart] = await core.getStartServices(); - return pluginStart.spaces; - }, - ruleDataService, - getRulesClientWithRequest: async (request) => { - const [, pluginStart] = await core.getStartServices(); - return pluginStart.alerting.getRulesClientWithRequest(request); - }, - getRacClientWithRequest: async (request) => { - const [, pluginStart] = await core.getStartServices(); - return pluginStart.ruleRegistry.getRacClientWithRequest(request); - }, - }, + dependencies, logger: this.logger, repository: getSloServerRouteRepository({ isServerless: this.initContext.env.packageInfo.buildFlavor === 'serverless', }), }); + this.sloClient = new SLOClient(dependencies); + core .getStartServices() .then(async ([coreStart, pluginStart]) => { @@ -191,6 +207,10 @@ export class SloPlugin implements Plugin { this.logger, config ); + + return { + sloClient: this.sloClient, + }; } public start(core: CoreStart, plugins: PluginStart) { @@ -200,6 +220,10 @@ export class SloPlugin implements Plugin { this.sloOrphanCleanupTask ?.start(plugins.taskManager, internalSoClient, internalEsClient) .catch(() => {}); + + return { + sloClient: this.sloClient, + }; } public stop() {} diff --git a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts index 8ddabede0d190..0f4d33c4a0931 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts @@ -22,6 +22,7 @@ import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import axios from 'axios'; import * as t from 'io-ts'; import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; +import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { SloConfig } from '..'; import { getHTTPResponseCode, ObservabilityError } from '../errors'; import { SloRequestHandlerContext } from '../types'; @@ -36,6 +37,8 @@ interface RegisterRoutes { } export interface RegisterRoutesDependencies { + licensing: LicensingPluginSetup; + logger: Logger; pluginsSetup: { core: CoreSetup; ruleRegistry: RuleRegistryPluginSetupContract; diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index 1f4c3ee2e3bb3..937d601dcc351 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -30,7 +30,6 @@ import { } from '@kbn/slo-schema'; import { getOverviewParamsSchema } from '@kbn/slo-schema/src/rest_specs/routes/get_overview'; import { GetSLOsOverview } from '../../services/get_slos_overview'; -import type { IndicatorTypes } from '../../domain/models'; import { CreateSLO, DefaultBurnRatesClient, @@ -59,29 +58,9 @@ import { SloDefinitionClient } from '../../services/slo_definition_client'; import { getSloSettings, storeSloSettings } from '../../services/slo_settings'; import { DefaultSummarySearchClient } from '../../services/summary_search_client'; import { DefaultSummaryTransformGenerator } from '../../services/summary_transform_generator/summary_transform_generator'; -import { - ApmTransactionDurationTransformGenerator, - ApmTransactionErrorRateTransformGenerator, - HistogramTransformGenerator, - KQLCustomTransformGenerator, - MetricCustomTransformGenerator, - SyntheticsAvailabilityTransformGenerator, - TimesliceMetricTransformGenerator, - TransformGenerator, -} from '../../services/transform_generators'; import type { SloRequestHandlerContext } from '../../types'; import { createSloServerRoute } from '../create_slo_server_route'; -const transformGenerators: Record = { - 'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(), - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - 'sli.synthetics.availability': new SyntheticsAvailabilityTransformGenerator(), - 'sli.kql.custom': new KQLCustomTransformGenerator(), - 'sli.metric.custom': new MetricCustomTransformGenerator(), - 'sli.histogram.custom': new HistogramTransformGenerator(), - 'sli.metric.timeslice': new TimesliceMetricTransformGenerator(), -}; - const assertPlatinumLicense = async (context: SloRequestHandlerContext) => { const licensing = await context.licensing; const hasCorrectLicense = licensing.license.hasAtLeast('platinum'); @@ -112,7 +91,6 @@ const createSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -159,7 +137,6 @@ const inspectSLORoute = createSloServerRoute({ const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -205,7 +182,6 @@ const updateSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -255,7 +231,6 @@ const deleteSLORoute = createSloServerRoute({ const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -324,7 +299,6 @@ const enableSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -363,7 +337,6 @@ const disableSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, @@ -403,7 +376,6 @@ const resetSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( - transformGenerators, esClient, logger, spaceId, diff --git a/x-pack/plugins/observability_solution/slo/server/saved_objects/slo.ts b/x-pack/plugins/observability_solution/slo/server/saved_objects/slo.ts index 1b594db90ae79..3285cd95a6299 100644 --- a/x-pack/plugins/observability_solution/slo/server/saved_objects/slo.ts +++ b/x-pack/plugins/observability_solution/slo/server/saved_objects/slo.ts @@ -75,6 +75,12 @@ export const slo: SavedObjectsType = { getTitle(sloSavedObject: SavedObject) { return `SLO: [${sloSavedObject.attributes.name}]`; }, + getInAppUrl(sloSavedObject: SavedObject) { + return { + path: `/app/slos/edit/${sloSavedObject.id}`, + uiCapabilitiesPath: 'slo.show', + }; + }, }, migrations: { '8.9.0': migrateSlo890, diff --git a/x-pack/plugins/observability_solution/slo/server/services/delete_slo.ts b/x-pack/plugins/observability_solution/slo/server/services/delete_slo.ts index fa6652dbb30cf..1d142c8c172bb 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/delete_slo.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/delete_slo.ts @@ -24,7 +24,7 @@ export class DeleteSLO { private transformManager: TransformManager, private summaryTransformManager: TransformManager, private esClient: ElasticsearchClient, - private rulesClient: RulesClientApi + private rulesClient?: RulesClientApi ) {} public async execute(sloId: string): Promise { @@ -76,7 +76,7 @@ export class DeleteSLO { } private async deleteAssociatedRules(sloId: string): Promise { try { - await this.rulesClient.bulkDeleteRules({ + await this.rulesClient?.bulkDeleteRules({ filter: `alert.attributes.params.sloId:${sloId}`, }); } catch (err) { diff --git a/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts b/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts new file mode 100644 index 0000000000000..68151aebe12bd --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts @@ -0,0 +1,190 @@ +/* + * 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 '@kbn/core-saved-objects-api-server'; +import { IBasePath, KibanaRequest } from '@kbn/core-http-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/logging'; +import { CreateSLOParams, createSLOParamsSchema, updateSLOParamsSchema } from '@kbn/slo-schema'; +import { isLeft } from 'fp-ts/Either'; +import { forbidden } from '@hapi/boom'; +import { firstValueFrom } from 'rxjs'; +import { RulesClientApi } from '@kbn/alerting-plugin/server/types'; +import { DeleteSLO } from './delete_slo'; +import { UpdateSLO } from './update_slo'; +import { RegisterRoutesDependencies } from '../routes/register_routes'; +import { KibanaSavedObjectsSLORepository } from './slo_repository'; +import { DefaultTransformManager } from './transform_manager'; +import { DefaultSummaryTransformManager } from './summay_transform_manager'; +import { DefaultSummaryTransformGenerator } from './summary_transform_generator/summary_transform_generator'; +import { CreateSLO } from './create_slo'; + +export class SLOClient { + private basePath: IBasePath; + private dependencies: RegisterRoutesDependencies; + private logger: Logger; + + constructor(dependencies: RegisterRoutesDependencies) { + this.dependencies = dependencies; + this.basePath = dependencies.pluginsSetup.core.http.basePath; + this.logger = dependencies.logger; + } + + async assertPlatinumLicense() { + const licensing = await this.dependencies.licensing; + const license = await firstValueFrom(licensing.license$); + + const hasPlatinumLicense = license.hasAtLeast('platinum'); + if (!hasPlatinumLicense) { + throw forbidden('Platinum license or higher is needed to make use of this feature.'); + } + } + + async createSLO({ + soClient, + esClient, + spaceId, + params: rawParams, + }: { + spaceId: string; + soClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + params: CreateSLOParams; + }) { + await this.assertPlatinumLicense(); + const params = createSLOParamsSchema.decode({ body: rawParams }); + if (isLeft(params)) { + throw new Error('Invalid params'); + } + const dataViews = await this.dependencies.getDataViewsStart(); + + const repository = new KibanaSavedObjectsSLORepository(soClient, this.logger); + + const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); + const transformManager = new DefaultTransformManager( + esClient, + this.logger, + spaceId, + dataViewsService + ); + const summaryTransformManager = new DefaultSummaryTransformManager( + new DefaultSummaryTransformGenerator(), + esClient, + this.logger + ); + + const createSLO = new CreateSLO( + esClient, + repository, + transformManager, + summaryTransformManager, + this.logger, + spaceId, + this.basePath + ); + + return await createSLO.execute(params.right.body); + } + + async updateSLO({ + sloId, + soClient, + esClient, + spaceId, + params: rawParams, + }: { + sloId: string; + spaceId: string; + soClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + params: CreateSLOParams; + }) { + await this.assertPlatinumLicense(); + + const params = updateSLOParamsSchema.decode({ body: rawParams, path: { id: sloId } }); + if (isLeft(params)) { + throw new Error('Invalid params'); + } + + const dataViews = await this.dependencies.getDataViewsStart(); + + const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); + const repository = new KibanaSavedObjectsSLORepository(soClient, this.logger); + const transformManager = new DefaultTransformManager( + esClient, + this.logger, + spaceId, + dataViewsService + ); + const summaryTransformManager = new DefaultSummaryTransformManager( + new DefaultSummaryTransformGenerator(), + esClient, + this.logger + ); + + const updateSLO = new UpdateSLO( + repository, + transformManager, + summaryTransformManager, + esClient, + this.logger, + spaceId, + this.basePath + ); + + return await updateSLO.execute(sloId, params.right.body); + } + + async deleteSLO({ + sloId, + soClient, + esClient, + spaceId, + request, + }: { + sloId: string; + spaceId: string; + soClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + request?: KibanaRequest; + }) { + await this.assertPlatinumLicense(); + + let rulesClient: RulesClientApi | undefined; + + const dataViews = await this.dependencies.getDataViewsStart(); + if (request) { + rulesClient = await this.dependencies.getRulesClientWithRequest(request); + } + + const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); + + const repository = new KibanaSavedObjectsSLORepository(soClient, this.logger); + const transformManager = new DefaultTransformManager( + esClient, + this.logger, + spaceId, + dataViewsService + ); + + const summaryTransformManager = new DefaultSummaryTransformManager( + new DefaultSummaryTransformGenerator(), + esClient, + this.logger + ); + + const deleteSLO = new DeleteSLO( + repository, + transformManager, + summaryTransformManager, + esClient, + rulesClient + ); + + await deleteSLO.execute(sloId); + } +} diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts index 7e6cf50c169fa..2cc1260d098a5 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -/* eslint-disable max-classes-per-file */ import { ElasticsearchClientMock, @@ -12,22 +11,15 @@ import { loggingSystemMock, } from '@kbn/core/server/mocks'; import { MockedLogger } from '@kbn/logging-mocks'; -import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { DefaultTransformManager } from './transform_manager'; -import { - ApmTransactionErrorRateTransformGenerator, - TransformGenerator, -} from './transform_generators'; -import { SLODefinition, IndicatorTypes } from '../domain/models'; import { createAPMTransactionDurationIndicator, createAPMTransactionErrorRateIndicator, createSLO, } from './fixtures/slo'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; describe('TransformManager', () => { let esClientMock: ElasticsearchClientMock; @@ -42,12 +34,7 @@ describe('TransformManager', () => { describe('Install', () => { describe('Unhappy path', () => { it('throws when no generator exists for the slo indicator type', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionDuration': new DummyTransformGenerator(), - }; const service = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -60,12 +47,7 @@ describe('TransformManager', () => { }); it('throws when transform generator fails', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionDuration': new FailTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -81,12 +63,7 @@ describe('TransformManager', () => { }); it('installs the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -103,12 +80,7 @@ describe('TransformManager', () => { describe('Preview', () => { it('previews the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -123,12 +95,7 @@ describe('TransformManager', () => { describe('Start', () => { it('starts the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -143,12 +110,7 @@ describe('TransformManager', () => { describe('Stop', () => { it('stops the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -163,12 +125,7 @@ describe('TransformManager', () => { describe('Uninstall', () => { it('uninstalls the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -184,12 +141,7 @@ describe('TransformManager', () => { esClientMock.transform.deleteTransform.mockRejectedValueOnce( new EsErrors.ConnectionError('irrelevant') ); - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, esClientMock, loggerMock, spaceId, @@ -202,23 +154,3 @@ describe('TransformManager', () => { }); }); }); - -class DummyTransformGenerator extends TransformGenerator { - async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise { - return {} as TransformPutTransformRequest; - } -} - -class FailTransformGenerator extends TransformGenerator { - getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise { - throw new Error('Some error'); - } -} diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts index febd24a457222..60e12f387bd45 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts @@ -12,10 +12,29 @@ import { DataViewsService } from '@kbn/data-views-plugin/server'; import { SLODefinition, IndicatorTypes } from '../domain/models'; import { SecurityException } from '../errors'; import { retryTransientEsErrors } from '../utils/retry'; -import { TransformGenerator } from './transform_generators'; +import { + ApmTransactionDurationTransformGenerator, + ApmTransactionErrorRateTransformGenerator, + HistogramTransformGenerator, + KQLCustomTransformGenerator, + MetricCustomTransformGenerator, + SyntheticsAvailabilityTransformGenerator, + TimesliceMetricTransformGenerator, + TransformGenerator, +} from './transform_generators'; type TransformId = string; +const transformGenerators: Record = { + 'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.synthetics.availability': new SyntheticsAvailabilityTransformGenerator(), + 'sli.kql.custom': new KQLCustomTransformGenerator(), + 'sli.metric.custom': new MetricCustomTransformGenerator(), + 'sli.histogram.custom': new HistogramTransformGenerator(), + 'sli.metric.timeslice': new TimesliceMetricTransformGenerator(), +}; + export interface TransformManager { install(slo: SLODefinition): Promise; inspect(slo: SLODefinition): Promise; @@ -27,7 +46,6 @@ export interface TransformManager { export class DefaultTransformManager implements TransformManager { constructor( - private generators: Record, private esClient: ElasticsearchClient, private logger: Logger, private spaceId: string, @@ -35,7 +53,7 @@ export class DefaultTransformManager implements TransformManager { ) {} async install(slo: SLODefinition): Promise { - const generator = this.generators[slo.indicator.type]; + const generator = transformGenerators[slo.indicator.type]; if (!generator) { this.logger.error(`No transform generator found for indicator type [${slo.indicator.type}]`); throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); @@ -63,7 +81,7 @@ export class DefaultTransformManager implements TransformManager { } async inspect(slo: SLODefinition): Promise { - const generator = this.generators[slo.indicator.type]; + const generator = transformGenerators[slo.indicator.type]; if (!generator) { this.logger.error(`No transform generator found for indicator type [${slo.indicator.type}]`); throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); From 199b2b7b33f8ffc0687b94836baeef67d95f5717 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 16 Jul 2024 18:07:48 +0200 Subject: [PATCH 02/11] update types --- x-pack/plugins/observability_solution/slo/server/plugin.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index 6fa7b0d719d3e..b23cdf854c955 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -16,6 +16,7 @@ import { SavedObjectsClient, } from '@kbn/core/server'; import { PluginSetupContract, PluginStartContract } from '@kbn/alerting-plugin/server'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; import { RuleRegistryPluginSetupContract, @@ -54,6 +55,7 @@ export interface PluginSetup { taskManager: TaskManagerSetupContract; spaces?: SpacesPluginSetup; cloud?: CloudSetup; + usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; } From a2566bab63778c2eb653e51f309232b88fc31aaa Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 16 Jul 2024 18:18:17 +0200 Subject: [PATCH 03/11] update test --- .../plugins/fleet/server/services/epm/packages/install.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts index 53112c5eea673..d038e21919307 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -59,6 +59,7 @@ jest.mock('../../app_context', () => { getSavedObjectsTagging: jest.fn(() => mockedSavedObjectTagging), getInternalUserSOClientForSpaceId: jest.fn(), getExperimentalFeatures: jest.fn(), + getSloStart: jest.fn(), }, }; }); From 6f952535b65673433fb2e57d8e09c2992edfc372 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:29:32 +0000 Subject: [PATCH 04/11] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/fleet/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index cb6c4d6f11b77..f2a158de59e37 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -112,5 +112,6 @@ "@kbn/core-saved-objects-utils-server", "@kbn/integration-assistant-plugin", "@kbn/core-security-server-mocks", + "@kbn/slo-schema", ] } From 1f55c77e3a3452e3c21437ae18caf84655e732e9 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 18 Jul 2024 18:34:18 +0200 Subject: [PATCH 05/11] wip --- x-pack/plugins/observability_solution/slo/server/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index c9676786318ee..3541e266a2cf7 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -17,7 +17,8 @@ import { } from '@kbn/core/server'; import { PluginSetupContract, PluginStartContract } from '@kbn/alerting-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; -import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; +import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; + import { RuleRegistryPluginSetupContract, RuleRegistryPluginStartContract, From 4532b604bea9089f5a86945c7fb64945c7661f82 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 18 Jul 2024 18:43:29 +0200 Subject: [PATCH 06/11] updat types --- .../pages/policy/view/ingest_manager_integration/mocks.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx index 945a841c132fd..6b6ef27f9ddac 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx @@ -173,6 +173,7 @@ export const generateFleetPackageInfo = (): PackageInfo => { tag: [], osquery_pack_asset: [], osquery_saved_query: [], + slo: [], }, elasticsearch: { ingest_pipeline: [], From 5f315c2ae0b552849246ac7ee38a2b883f039404 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 18 Jul 2024 18:47:37 +0200 Subject: [PATCH 07/11] update test --- .../server/services/transform_manager.test.ts | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts index 2cc1260d098a5..14abc2e4e50be 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts @@ -14,11 +14,7 @@ import { MockedLogger } from '@kbn/logging-mocks'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { DefaultTransformManager } from './transform_manager'; -import { - createAPMTransactionDurationIndicator, - createAPMTransactionErrorRateIndicator, - createSLO, -} from './fixtures/slo'; +import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; describe('TransformManager', () => { @@ -32,36 +28,6 @@ describe('TransformManager', () => { }); describe('Install', () => { - describe('Unhappy path', () => { - it('throws when no generator exists for the slo indicator type', async () => { - const service = new DefaultTransformManager( - esClientMock, - loggerMock, - spaceId, - dataViewsService - ); - - await expect( - service.install(createSLO({ indicator: createAPMTransactionErrorRateIndicator() })) - ).rejects.toThrowError('Unsupported indicator type [sli.apm.transactionErrorRate]'); - }); - - it('throws when transform generator fails', async () => { - const transformManager = new DefaultTransformManager( - esClientMock, - loggerMock, - spaceId, - dataViewsService - ); - - await expect( - transformManager.install( - createSLO({ indicator: createAPMTransactionDurationIndicator() }) - ) - ).rejects.toThrowError('Some error'); - }); - }); - it('installs the transform', async () => { const transformManager = new DefaultTransformManager( esClientMock, From 7edc6b973617e1c9b13e1a6ada6c2ebedcc326ee Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 25 Jul 2024 15:45:00 +0200 Subject: [PATCH 08/11] deletion exception handling --- .../server/services/epm/packages/remove.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index b36fd119f2e79..7be75496a9eaf 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -194,15 +194,19 @@ const deleteSloAssets = async ( namespace: string | undefined, soClient: SavedObjectsClientContract ) => { - const sloClient = appContextService.getSloStart()?.sloClient; - if (!sloClient) { - return; - } + try { + const sloClient = appContextService.getSloStart()?.sloClient; + if (!sloClient) { + return; + } - const esClient = appContextService.getInternalUserESClient(); + const esClient = appContextService.getInternalUserESClient(); - for (const { id } of sloAssets) { - await sloClient.deleteSLO({ sloId: id, soClient, esClient, spaceId: namespace ?? 'default' }); + for (const { id } of sloAssets) { + await sloClient.deleteSLO({ sloId: id, soClient, esClient, spaceId: namespace ?? 'default' }); + } + } catch (err) { + appContextService.getLogger().error(err); } }; From 145fef90f582760d05ab4ad1e3032d14d89f5def Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Thu, 8 Aug 2024 07:02:36 +0200 Subject: [PATCH 09/11] wip --- .../services/epm/kibana/assets/install.ts | 26 ++++++++++++------- .../services/epm/packages/get_bulk_assets.ts | 3 ++- .../slo/server/routes/slo/route.ts | 2 +- .../slo/server/services/create_slo.ts | 11 +++++--- .../slo/server/services/slo_client.ts | 2 +- .../slo/server/services/slo_repository.ts | 24 ++++++++++++----- 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index 3a47580e416d3..9028f008899c3 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -25,6 +25,8 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import type { CreateSLOParams } from '@kbn/slo-schema'; +import { appContextService } from '../../..'; + import { getAssetFromAssetsMap, getPathParts } from '../../archive'; import { KibanaAssetType, KibanaSavedObjectType } from '../../../../types'; import type { AssetReference, Installation, PackageSpecTags } from '../../../../types'; @@ -292,16 +294,22 @@ export async function installSLOAssets({ } const installedSLOs = []; + try { + for (const asset of sloAssets) { + const attr = asset.attributes as CreateSLOParams; + const { sloSavedObjectId } = await sloClient.createSLO({ + soClient, + esClient, + params: { ...attr, id: asset.id }, + spaceId, + }); - for (const asset of sloAssets) { - const attr = asset.attributes as CreateSLOParams; - const slo = await sloClient.createSLO({ - soClient, - esClient, - params: { ...attr, id: asset.id }, - spaceId, - }); - installedSLOs.push({ id: slo.id, type: 'slo', meta: {} }); + installedSLOs.push({ id: sloSavedObjectId, type: 'slo', meta: {} }); + } + } catch (err) { + // If we fail to install an SLO, we should not fail the whole package install + // but we should log the error + appContextService.getLogger().error(err); } return installedSLOs; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts index 8171ee33f22f7..1b294f8ca7a6f 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_bulk_assets.ts @@ -49,8 +49,9 @@ export async function getBulkAssets( assetIds: AssetSOObject[] ) { const { resolved_objects: resolvedObjects } = await soClient.bulkResolve( - assetIds + assetIds.filter((asset) => asset.type !== 'slo') ); + const types: Record = {}; const res: SimpleSOAssetType[] = resolvedObjects diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index 4ac880d1602a2..11725979b3232 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -114,7 +114,7 @@ const createSLORoute = createSloServerRoute({ const response = await createSLO.execute(params.body); - return response; + return response.slo; }, }); diff --git a/x-pack/plugins/observability_solution/slo/server/services/create_slo.ts b/x-pack/plugins/observability_solution/slo/server/services/create_slo.ts index f3abb8554f3dd..d8b48f1c1311e 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/create_slo.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/create_slo.ts @@ -40,13 +40,18 @@ export class CreateSLO { private basePath: IBasePath ) {} - public async execute(params: CreateSLOParams): Promise { + public async execute( + params: CreateSLOParams, + throwOnConflict = true + ): Promise<{ slo: CreateSLOResponse; sloSavedObjectId: string }> { const slo = this.toSLO(params); validateSLO(slo); const rollbackOperations = []; - await this.repository.save(slo, { throwOnConflict: true }); + const { sloSavedObjectId } = await this.repository.save(slo, { + throwOnConflict, + }); rollbackOperations.push(() => this.repository.deleteById(slo.id)); const rollupTransformId = getSLOTransformId(slo.id, slo.revision); @@ -119,7 +124,7 @@ export class CreateSLO { throw err; } - return this.toResponse(slo); + return { slo: this.toResponse(slo), sloSavedObjectId }; } public async inspect(params: CreateSLOParams): Promise<{ diff --git a/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts b/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts index 68151aebe12bd..d7179a4be3036 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/slo_client.ts @@ -87,7 +87,7 @@ export class SLOClient { this.basePath ); - return await createSLO.execute(params.right.body); + return await createSLO.execute(params.right.body, false); } async updateSLO({ diff --git a/x-pack/plugins/observability_solution/slo/server/services/slo_repository.ts b/x-pack/plugins/observability_solution/slo/server/services/slo_repository.ts index 9f300a148ac2e..6ae0f2952954c 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/slo_repository.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/slo_repository.ts @@ -15,7 +15,10 @@ import { SLOIdConflict, SLONotFound } from '../errors'; import { SO_SLO_TYPE } from '../saved_objects'; export interface SLORepository { - save(slo: SLODefinition, options?: { throwOnConflict: boolean }): Promise; + save( + slo: SLODefinition, + options?: { throwOnConflict: boolean } + ): Promise<{ sloSavedObjectId: string; slo: SLODefinition }>; findAllByIds(ids: string[]): Promise; findById(id: string): Promise; deleteById(id: string): Promise; @@ -29,7 +32,10 @@ export interface SLORepository { export class KibanaSavedObjectsSLORepository implements SLORepository { constructor(private soClient: SavedObjectsClientContract, private logger: Logger) {} - async save(slo: SLODefinition, options = { throwOnConflict: false }): Promise { + async save( + slo: SLODefinition, + options = { throwOnConflict: false } + ): Promise<{ sloSavedObjectId: string; slo: SLODefinition }> { let existingSavedObjectId; const findResponse = await this.soClient.find({ type: SO_SLO_TYPE, @@ -45,12 +51,16 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { existingSavedObjectId = findResponse.saved_objects[0].id; } - await this.soClient.create(SO_SLO_TYPE, toStoredSLO(slo), { - id: existingSavedObjectId, - overwrite: true, - }); + const sloSavedObject = await this.soClient.create( + SO_SLO_TYPE, + toStoredSLO(slo), + { + id: existingSavedObjectId, + overwrite: true, + } + ); - return slo; + return { sloSavedObjectId: sloSavedObject.id, slo }; } async findById(id: string): Promise { From 6c9feaa8e66ac8245a480d40c3bd577349365746 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 10 Sep 2024 12:49:44 +0200 Subject: [PATCH 10/11] coflicts --- .../server/services/transform_manager.test.ts | 105 +----------------- 1 file changed, 5 insertions(+), 100 deletions(-) diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts index b7b5d7ba4fcd9..9a70dc0ef1949 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -/* eslint-disable max-classes-per-file */ import { ScopedClusterClientMock, @@ -12,22 +11,11 @@ import { loggingSystemMock, } from '@kbn/core/server/mocks'; import { MockedLogger } from '@kbn/logging-mocks'; -import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { DefaultTransformManager } from './transform_manager'; -import { - ApmTransactionErrorRateTransformGenerator, - TransformGenerator, -} from './transform_generators'; -import { SLODefinition, IndicatorTypes } from '../domain/models'; -import { - createAPMTransactionDurationIndicator, - createAPMTransactionErrorRateIndicator, - createSLO, -} from './fixtures/slo'; +import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; describe('TransformManager', () => { let scopedClusterClientMock: ScopedClusterClientMock; @@ -40,53 +28,10 @@ describe('TransformManager', () => { }); describe('Install', () => { - describe('Unhappy path', () => { - it('throws when no generator exists for the slo indicator type', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionDuration': new DummyTransformGenerator(), - }; - const service = new DefaultTransformManager( - generators, - scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService - ); - - await expect( - service.install(createSLO({ indicator: createAPMTransactionErrorRateIndicator() })) - ).rejects.toThrowError('Unsupported indicator type [sli.apm.transactionErrorRate]'); - }); - - it('throws when transform generator fails', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionDuration': new FailTransformGenerator(), - }; - const transformManager = new DefaultTransformManager( - generators, - scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService - ); - - await expect( - transformManager.install( - createSLO({ indicator: createAPMTransactionDurationIndicator() }) - ) - ).rejects.toThrowError('Some error'); - }); - }); - it('installs the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; + const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -106,11 +51,8 @@ describe('TransformManager', () => { describe('Preview', () => { it('previews the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; + const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -128,11 +70,8 @@ describe('TransformManager', () => { describe('Start', () => { it('starts the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; + const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -149,12 +88,7 @@ describe('TransformManager', () => { describe('Stop', () => { it('stops the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -171,12 +105,7 @@ describe('TransformManager', () => { describe('Uninstall', () => { it('uninstalls the transform', async () => { - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -194,12 +123,8 @@ describe('TransformManager', () => { scopedClusterClientMock.asSecondaryAuthUser.transform.deleteTransform.mockRejectedValueOnce( new EsErrors.ConnectionError('irrelevant') ); - // @ts-ignore defining only a subset of the possible SLI - const generators: Record = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - }; + const transformManager = new DefaultTransformManager( - generators, scopedClusterClientMock, loggerMock, spaceId, @@ -214,23 +139,3 @@ describe('TransformManager', () => { }); }); }); - -class DummyTransformGenerator extends TransformGenerator { - async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise { - return {} as TransformPutTransformRequest; - } -} - -class FailTransformGenerator extends TransformGenerator { - getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise { - throw new Error('Some error'); - } -} From 10997e575a87735265951af30d9e5f477da27ac4 Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 10 Sep 2024 12:52:16 +0200 Subject: [PATCH 11/11] coflicts --- .../slo/server/services/slo_repository.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/slo/server/services/slo_repository.test.ts b/x-pack/plugins/observability_solution/slo/server/services/slo_repository.test.ts index 1b6eec0ef4f97..57cbd4e7d1304 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/slo_repository.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/slo_repository.test.ts @@ -90,7 +90,7 @@ describe('KibanaSavedObjectsSLORepository', () => { const savedSLO = await repository.save(slo); - expect(savedSLO).toEqual(slo); + expect(savedSLO.slo).toEqual(slo); expect(soClientMock.find).toHaveBeenCalledWith({ type: SO_SLO_TYPE, page: 1, @@ -131,7 +131,7 @@ describe('KibanaSavedObjectsSLORepository', () => { const savedSLO = await repository.save(slo); - expect(savedSLO).toEqual(slo); + expect(savedSLO.slo).toEqual(slo); expect(soClientMock.find).toHaveBeenCalledWith({ type: SO_SLO_TYPE, page: 1,