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 904ccc385bd7d..f70123029e6c4 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 @@ -36,6 +36,7 @@ interface SavedObjectResponse> { interface GetOptions { type: string; id: string; + space?: string; } interface IndexOptions { @@ -110,11 +111,13 @@ export class KbnClientSavedObjects { * Get an object */ public async get>(options: GetOptions) { - this.log.debug('Gettings saved object: %j', options); + this.log.debug('Getting saved object: %j', options); const { data } = await this.requester.request>({ description: 'get saved object', - path: uriencode`/api/saved_objects/${options.type}/${options.id}`, + path: options.space + ? uriencode`/s/${options.space}/api/saved_objects/${options.type}/${options.id}` + : uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'GET', }); return data; @@ -174,7 +177,9 @@ export class KbnClientSavedObjects { const { data } = await this.requester.request({ description: 'delete saved object', - path: uriencode`/api/saved_objects/${options.type}/${options.id}`, + path: options.space + ? uriencode`/s/${options.space}/api/saved_objects/${options.type}/${options.id}` + : uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'DELETE', }); diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index f7b446cc53c7d..de92ddf1fcfae 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -402,6 +402,7 @@ export interface Installation extends SavedObjectAttributes { install_version: string; install_started_at: string; install_source: InstallSource; + installed_kibana_space_id?: string; keep_policies_up_to_date?: boolean; } diff --git a/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json b/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json index 397bc6d653409..b8c4c31767f3d 100644 --- a/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json +++ b/x-pack/plugins/fleet/cypress/fixtures/integrations/apache.json @@ -645,6 +645,7 @@ "updated_at": "2021-09-30T10:47:12.961Z", "version": "WzI1NjgsMV0=", "attributes": { + "installed_kibana_space_id": "default", "installed_kibana": [ { "id": "apache-Logs-Apache-Dashboard", diff --git a/x-pack/plugins/fleet/dev_docs/data_model.md b/x-pack/plugins/fleet/dev_docs/data_model.md index be0e06e5439dc..1399cec4c3318 100644 --- a/x-pack/plugins/fleet/dev_docs/data_model.md +++ b/x-pack/plugins/fleet/dev_docs/data_model.md @@ -127,12 +127,10 @@ used for other types of outputs like separate monitoring clusters, Logstash, etc - `installed_es` - array of assets installed into Elasticsearch - `installed_es.id` - ID in Elasticsearch of an asset (eg. `logs-system.application-1.1.2`) - `installed_es.type` - type of Elasticsearch asset (eg. `ingest_pipeline`) + - `installed_kibana_space_id` - the id of the space the assets were installed in (eg. `default`) - `installed_kibana` - array of assets that were installed into Kibana - `installed_kibana.id` - Saved Object ID (eg. `system-01c54730-fee6-11e9-8405-516218e3d268`) - `installed_kibana.type` - Saved Object type name (eg. `dashboard`) - - One caveat with this array is that the IDs are currently space-specific so if a package's assets were installed in - one space, they may not be visible in other spaces. We also do not keep track of which space these assets were - installed into. - `package_assets` - array of original file contents of the package as it was installed - `package_assets.id` - Saved Object ID for a `epm-package-assets` type - `package_assets.type` - Saved Object type for the asset. As of now, only `epm-packages-assets` are supported. diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index d516827ebf9a7..f78681bd725df 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces"], "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch", "telemetry"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils", "usageCollection"] diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx index 86bac94bc50cd..499b5f2512e7c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -63,6 +63,7 @@ export const Installed = ({ width, ...props }: Args) => { install_version: props.version, es_index_patterns: {}, installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], install_status: 'installed', install_source: 'registry', diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index acdc0ba5e3fdd..ab2e86e969f1c 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -80,6 +80,7 @@ export const createFleetRequestHandlerContextMock = (): jest.Mocked< epm: { internalSoClient: savedObjectsClientMock.create(), }, + spaceId: 'default', }; }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 06fb78cbb67ce..9a7cc6535e41d 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -40,6 +40,7 @@ import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/ import type { FleetConfigType, FleetAuthz } from '../common'; import { INTEGRATIONS_PLUGIN_ID } from '../common'; import type { CloudSetup } from '../../cloud/server'; +import type { SpacesPluginStart } from '../../spaces/server'; import { PLUGIN_ID, @@ -96,6 +97,7 @@ export interface FleetSetupDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; + spaces: SpacesPluginStart; telemetry?: TelemetryPluginSetup; } @@ -297,6 +299,9 @@ export class FleetPlugin .getScopedClient(request, { excludedWrappers: ['security'] }); }, }, + get spaceId() { + return deps.spaces.spacesService.getSpaceId(request); + }, }; } ); diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 57a9290d00a00..92504dfd7091d 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -22,6 +22,7 @@ import type { CopyAgentPolicyRequestSchema, DeleteAgentPolicyRequestSchema, GetFullAgentPolicyRequestSchema, + FleetRequestHandler, } from '../../types'; import { FLEET_SYSTEM_PACKAGE } from '../../../common'; import type { @@ -99,7 +100,7 @@ export const getOneAgentPolicyHandler: RequestHandler< } }; -export const createAgentPolicyHandler: RequestHandler< +export const createAgentPolicyHandler: FleetRequestHandler< undefined, TypeOf, TypeOf @@ -108,7 +109,7 @@ export const createAgentPolicyHandler: RequestHandler< const esClient = context.core.elasticsearch.client.asInternalUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; const withSysMonitoring = request.query.sys_monitoring ?? false; - + const spaceId = context.fleet.spaceId; try { // eslint-disable-next-line prefer-const let [agentPolicy, newSysPackagePolicy] = await Promise.all([ @@ -132,6 +133,7 @@ export const createAgentPolicyHandler: RequestHandler< newSysPackagePolicy.name = await incrementPackageName(soClient, FLEET_SYSTEM_PACKAGE); await packagePolicyService.create(soClient, esClient, newSysPackagePolicy, { + spaceId, user, bumpRevision: false, }); diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 4e43a19b3637e..4953cecbf211d 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -257,11 +257,13 @@ export const installPackageFromRegistryHandler: FleetRequestHandler< const esClient = context.core.elasticsearch.client.asInternalUser; const { pkgName, pkgVersion } = request.params; + const spaceId = context.fleet.spaceId; const res = await installPackage({ installSource: 'registry', savedObjectsClient, pkgkey: pkgVersion ? `${pkgName}-${pkgVersion}` : pkgName, esClient, + spaceId, force: request.body?.force, }); if (!res.error) { @@ -296,10 +298,12 @@ export const bulkInstallPackagesFromRegistryHandler: FleetRequestHandler< > = async (context, request, response) => { const savedObjectsClient = context.fleet.epm.internalSoClient; const esClient = context.core.elasticsearch.client.asInternalUser; + const spaceId = context.fleet.spaceId; const bulkInstalledResponses = await bulkInstallPackages({ savedObjectsClient, esClient, packagesToInstall: request.body.packages, + spaceId, }); const payload = bulkInstalledResponses.map(bulkInstallServiceResponseToHttpEntry); const body: BulkInstallPackagesResponse = { @@ -324,12 +328,13 @@ export const installPackageByUploadHandler: FleetRequestHandler< const esClient = context.core.elasticsearch.client.asInternalUser; const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); - + const spaceId = context.fleet.spaceId; const res = await installPackage({ installSource: 'upload', savedObjectsClient, esClient, archiveBuffer, + spaceId, contentType, }); if (!res.error) { diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index 6ea64f95c92ac..c4cef2a4d8d3b 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -16,13 +16,14 @@ import type { PackagePolicyServiceInterface, PostPackagePolicyCreateCallback, PutPackagePolicyUpdateCallback, + FleetRequestHandlerContext, } from '../..'; import type { CreatePackagePolicyRequestSchema, UpdatePackagePolicyRequestSchema, } from '../../types/rest_spec'; import type { FleetAuthzRouter } from '../security'; -import type { FleetRequestHandler, FleetRequestHandlerContext } from '../../types'; +import type { FleetRequestHandler } from '../../types'; import type { PackagePolicy } from '../../types'; import { registerRoutes } from './index'; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 2d27070980d57..830553aa24dab 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -19,6 +19,7 @@ import type { DeletePackagePoliciesRequestSchema, UpgradePackagePoliciesRequestSchema, DryRunPackagePoliciesRequestSchema, + FleetRequestHandler, } from '../../types'; import type { CreatePackagePolicyResponse, @@ -80,7 +81,7 @@ export const getOnePackagePolicyHandler: RequestHandler< } }; -export const createPackagePolicyHandler: RequestHandler< +export const createPackagePolicyHandler: FleetRequestHandler< undefined, undefined, TypeOf @@ -89,6 +90,7 @@ export const createPackagePolicyHandler: RequestHandler< const esClient = context.core.elasticsearch.client.asInternalUser; const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; const { force, ...newPolicy } = request.body; + const spaceId = context.fleet.spaceId; try { const newPackagePolicy = await packagePolicyService.enrichPolicyWithDefaultsFromPackage( soClient, @@ -106,6 +108,7 @@ export const createPackagePolicyHandler: RequestHandler< const packagePolicy = await packagePolicyService.create(soClient, esClient, newData, { user, force, + spaceId, }); const body: CreatePackagePolicyResponse = { item: packagePolicy }; return response.ok({ diff --git a/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts b/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts index d13c2ab5ab8e8..56cbbc9435a57 100644 --- a/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts +++ b/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts @@ -11,12 +11,13 @@ import type { TypeOf } from '@kbn/config-schema'; import type { PreconfiguredAgentPolicy } from '../../../common'; import { PRECONFIGURATION_API_ROUTES } from '../../constants'; +import type { FleetRequestHandler } from '../../types'; import { PutPreconfigurationSchema } from '../../types'; import { defaultIngestErrorHandler } from '../../errors'; import { ensurePreconfiguredPackagesAndPolicies, outputService } from '../../services'; import type { FleetAuthzRouter } from '../security'; -export const updatePreconfigurationHandler: RequestHandler< +export const updatePreconfigurationHandler: FleetRequestHandler< undefined, undefined, TypeOf @@ -24,7 +25,7 @@ export const updatePreconfigurationHandler: RequestHandler< const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asInternalUser; const defaultOutput = await outputService.ensureDefaultOutput(soClient); - + const spaceId = context.fleet.spaceId; const { agentPolicies, packages } = request.body; try { @@ -33,7 +34,8 @@ export const updatePreconfigurationHandler: RequestHandler< esClient, (agentPolicies as PreconfiguredAgentPolicy[]) ?? [], packages ?? [], - defaultOutput + defaultOutput, + spaceId ); return response.ok({ body }); } catch (error) { @@ -50,6 +52,6 @@ export const registerRoutes = (router: FleetAuthzRouter) => { fleet: { all: true }, }, }, - updatePreconfigurationHandler + updatePreconfigurationHandler as RequestHandler ); }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index 035659185955d..bacbff982daec 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -45,6 +45,7 @@ describe('FleetSetupHandler', () => { epm: { internalSoClient: savedObjectsClientMock.create(), }, + spaceId: 'default', }, }; response = httpServerMock.createResponseFactory(); diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 6058cfba12cad..96cb51af53b45 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -239,6 +239,7 @@ const getSavedObjectTypes = ( type: { type: 'keyword' }, }, }, + installed_kibana_space_id: { type: 'keyword' }, package_assets: { type: 'nested', properties: { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts index dbec18851cfc9..f1ac8382a9ba7 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts @@ -9,6 +9,8 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/s import { savedObjectsClientMock, elasticsearchServiceMock } from 'src/core/server/mocks'; import { loggerMock } from '@kbn/logging/mocks'; +import { DEFAULT_SPACE_ID } from '../../../../../spaces/common/constants'; + import { appContextService } from '../../app_context'; import { createAppContextStartContractMock } from '../../../mocks'; @@ -78,6 +80,7 @@ describe('_installPackage', () => { }, installType: 'install', installSource: 'registry', + spaceId: DEFAULT_SPACE_ID, }); // if we have a .catch this will fail nicely (test pass) 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 ac0c7e1729913..05ed45a0e0c7a 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 @@ -56,6 +56,7 @@ export async function _installPackage({ packageInfo, installType, installSource, + spaceId, }: { savedObjectsClient: SavedObjectsClientContract; savedObjectsImporter: Pick; @@ -66,6 +67,7 @@ export async function _installPackage({ packageInfo: InstallablePackage; installType: InstallType; installSource: InstallSource; + spaceId: string; }): Promise { const { name: pkgName, version: pkgVersion } = packageInfo; @@ -99,15 +101,12 @@ export async function _installPackage({ savedObjectsClient, packageInfo, installSource, + spaceId, }); } const kibanaAssets = await getKibanaAssets(paths); - if (installedPkg) - await deleteKibanaSavedObjectsAssets( - savedObjectsClient, - installedPkg.attributes.installed_kibana - ); + if (installedPkg) await deleteKibanaSavedObjectsAssets({ savedObjectsClient, installedPkg }); // save new kibana refs before installing the assets const installedKibanaAssetsRefs = await saveKibanaAssetsRefs( savedObjectsClient, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index 8a7fb9ae005d0..3178881b7ce09 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -20,12 +20,14 @@ interface BulkInstallPackagesParams { packagesToInstall: Array; esClient: ElasticsearchClient; force?: boolean; + spaceId: string; } export async function bulkInstallPackages({ savedObjectsClient, packagesToInstall, esClient, + spaceId, force, }: BulkInstallPackagesParams): Promise { const logger = appContextService.getLogger(); @@ -70,6 +72,7 @@ export async function bulkInstallPackages({ esClient, pkgkey: Registry.pkgToPkgKey(pkgKeyProps), installSource, + spaceId, force, }); if (installResult.error) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts index 6bc962165f1d2..341cc90dab9ea 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts @@ -18,6 +18,7 @@ const mockInstallation: SavedObject = { type: 'epm-packages', attributes: { id: 'test-pkg', + installed_kibana_space_id: 'default', installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], package_assets: [], @@ -37,6 +38,7 @@ const mockInstallationUpdateFail: SavedObject = { type: 'epm-packages', attributes: { id: 'test-pkg', + installed_kibana_space_id: 'default', installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], package_assets: [], 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 261a0d9a6d688..cb04b5f583c5a 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 @@ -9,6 +9,8 @@ import { savedObjectsClientMock } from 'src/core/server/mocks'; import type { ElasticsearchClient } from 'kibana/server'; +import { DEFAULT_SPACE_ID } from '../../../../../spaces/common/constants'; + import * as Registry from '../registry'; import { sendTelemetryEvents } from '../../upgrade_sender'; @@ -75,6 +77,7 @@ describe('install', () => { describe('registry', () => { it('should send telemetry on install failure, out of date', async () => { await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'apache-1.1.0', savedObjectsClient: savedObjectsClientMock.create(), @@ -96,6 +99,7 @@ describe('install', () => { it('should send telemetry on install failure, license error', async () => { jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(false); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'apache-1.3.0', savedObjectsClient: savedObjectsClientMock.create(), @@ -117,6 +121,7 @@ describe('install', () => { it('should send telemetry on install success', async () => { jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'apache-1.3.0', savedObjectsClient: savedObjectsClientMock.create(), @@ -140,6 +145,7 @@ describe('install', () => { .mockImplementationOnce(() => Promise.resolve({ attributes: { version: '1.2.0' } } as any)); jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'apache-1.3.0', savedObjectsClient: savedObjectsClientMock.create(), @@ -163,6 +169,7 @@ describe('install', () => { .mockImplementation(() => Promise.reject(new Error('error'))); jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'apache-1.3.0', savedObjectsClient: savedObjectsClientMock.create(), @@ -188,6 +195,7 @@ describe('install', () => { .spyOn(obj, 'getInstallationObject') .mockImplementationOnce(() => Promise.resolve({ attributes: { version: '1.2.0' } } as any)); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'upload', archiveBuffer: {} as Buffer, contentType: '', @@ -210,6 +218,7 @@ describe('install', () => { it('should send telemetry on install success', async () => { await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'upload', archiveBuffer: {} as Buffer, contentType: '', @@ -233,6 +242,7 @@ describe('install', () => { .spyOn(install, '_installPackage') .mockImplementation(() => Promise.reject(new Error('error'))); await installPackage({ + spaceId: DEFAULT_SPACE_ID, installSource: 'upload', archiveBuffer: {} as Buffer, contentType: '', 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 77fcc429b2084..a7e10ba4aa578 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -11,7 +11,7 @@ import type Boom from '@hapi/boom'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'src/core/server'; import { generateESIndexPatterns } from '../elasticsearch/template/template'; - +import { DEFAULT_SPACE_ID } from '../../../../../spaces/common/constants'; import type { BulkInstallPackageInfo, EpmPackageInstallStatus, @@ -85,8 +85,9 @@ export async function ensureInstalledPackage(options: { pkgName: string; esClient: ElasticsearchClient; pkgVersion?: string; + spaceId?: string; }): Promise { - const { savedObjectsClient, pkgName, esClient, pkgVersion } = options; + const { savedObjectsClient, pkgName, esClient, pkgVersion, spaceId = DEFAULT_SPACE_ID } = options; // If pkgVersion isn't specified, find the latest package version const pkgKeyProps = pkgVersion @@ -106,6 +107,7 @@ export async function ensureInstalledPackage(options: { installSource: 'registry', savedObjectsClient, pkgkey, + spaceId, esClient, force: true, // Always force outdated packages to be installed if a later version isn't installed }); @@ -142,6 +144,7 @@ export async function handleInstallPackageFailure({ pkgVersion, installedPkg, esClient, + spaceId, }: { savedObjectsClient: SavedObjectsClientContract; error: IngestManagerError | Boom.Boom | Error; @@ -149,6 +152,7 @@ export async function handleInstallPackageFailure({ pkgVersion: string; installedPkg: SavedObject | undefined; esClient: ElasticsearchClient; + spaceId: string; }) { if (error instanceof IngestManagerError) { return; @@ -183,6 +187,7 @@ export async function handleInstallPackageFailure({ savedObjectsClient, pkgkey: prevVersion, esClient, + spaceId, force: true, }); } @@ -202,6 +207,7 @@ interface InstallRegistryPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; esClient: ElasticsearchClient; + spaceId: string; force?: boolean; } @@ -229,6 +235,7 @@ async function installPackageFromRegistry({ savedObjectsClient, pkgkey, esClient, + spaceId, force = false, }: InstallRegistryPackageParams): Promise { const logger = appContextService.getLogger(); @@ -317,6 +324,7 @@ async function installPackageFromRegistry({ paths, packageInfo, installType, + spaceId, installSource: 'registry', }) .then(async (assets) => { @@ -339,6 +347,7 @@ async function installPackageFromRegistry({ pkgName, pkgVersion, installedPkg, + spaceId, esClient, }); sendEvent({ @@ -364,6 +373,7 @@ interface InstallUploadedArchiveParams { esClient: ElasticsearchClient; archiveBuffer: Buffer; contentType: string; + spaceId: string; } async function installPackageByUpload({ @@ -371,6 +381,7 @@ async function installPackageByUpload({ esClient, archiveBuffer, contentType, + spaceId, }: InstallUploadedArchiveParams): Promise { const logger = appContextService.getLogger(); // if an error happens during getInstallType, report that we don't know @@ -427,6 +438,7 @@ async function installPackageByUpload({ packageInfo, installType, installSource, + spaceId, }) .then((assets) => { sendEvent({ @@ -451,9 +463,10 @@ async function installPackageByUpload({ } } -export type InstallPackageParams = +export type InstallPackageParams = { spaceId: string } & ( | ({ installSource: Extract } & InstallRegistryPackageParams) - | ({ installSource: Extract } & InstallUploadedArchiveParams); + | ({ installSource: Extract } & InstallUploadedArchiveParams) +); export async function installPackage(args: InstallPackageParams) { if (!('installSource' in args)) { @@ -463,23 +476,25 @@ export async function installPackage(args: InstallPackageParams) { const { savedObjectsClient, esClient } = args; if (args.installSource === 'registry') { - const { pkgkey, force } = args; + const { pkgkey, force, spaceId } = args; logger.debug(`kicking off install of ${pkgkey} from registry`); const response = installPackageFromRegistry({ savedObjectsClient, pkgkey, esClient, + spaceId, force, }); return response; } else if (args.installSource === 'upload') { - const { archiveBuffer, contentType } = args; + const { archiveBuffer, contentType, spaceId } = args; logger.debug(`kicking off install of uploaded package`); const response = installPackageByUpload({ savedObjectsClient, esClient, archiveBuffer, contentType, + spaceId, }); return response; } @@ -515,6 +530,7 @@ export async function createInstallation(options: { savedObjectsClient: SavedObjectsClientContract; packageInfo: InstallablePackage; installSource: InstallSource; + spaceId: string; }) { const { savedObjectsClient, packageInfo, installSource } = options; const { name: pkgName, version: pkgVersion } = packageInfo; @@ -534,6 +550,7 @@ export async function createInstallation(options: { PACKAGES_SAVED_OBJECT_TYPE, { installed_kibana: [], + installed_kibana_space_id: options.spaceId, installed_es: [], package_assets: [], es_index_patterns: toSaveESIndexPatterns, @@ -627,6 +644,7 @@ export async function ensurePackagesCompletedInstall( savedObjectsClient, pkgkey, esClient, + spaceId: pkg.attributes.installed_kibana_space_id || DEFAULT_SPACE_ID, }) ); } 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 848d17f78c929..b3b99bfd05e03 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -6,9 +6,15 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; + import Boom from '@hapi/boom'; +import type { SavedObject } from 'src/core/server'; + +import { SavedObjectsClient } from '../../../../../../../src/core/server'; + import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; +import { DEFAULT_SPACE_ID } from '../../../../../spaces/common/constants'; import { ElasticsearchAssetType } from '../../../types'; import type { AssetReference, @@ -25,6 +31,7 @@ import { packagePolicyService, appContextService } from '../..'; import { deletePackageCache } from '../archive'; import { deleteIlms } from '../elasticsearch/datastream_ilm/remove'; import { removeArchiveEntries } from '../archive/storage'; +import { SavedObjectsUtils } from '../../../../../../../src/core/server'; import { getInstallation, kibanaSavedObjectTypes } from './index'; @@ -80,10 +87,15 @@ export async function removeInstallation(options: { async function deleteKibanaAssets( installedObjects: KibanaAssetReference[], - savedObjectsClient: SavedObjectsClientContract + spaceId: string = DEFAULT_SPACE_ID ) { + const savedObjectsClient = new SavedObjectsClient( + appContextService.getSavedObjects().createInternalRepository() + ); + const namespace = SavedObjectsUtils.namespaceStringToId(spaceId); const { resolved_objects: resolvedObjects } = await savedObjectsClient.bulkResolve( - installedObjects + installedObjects, + { namespace } ); const foundObjects = resolvedObjects.filter( @@ -94,7 +106,7 @@ async function deleteKibanaAssets( // we filter these out before calling delete const assetsToDelete = foundObjects.map(({ saved_object: { id, type } }) => ({ id, type })); const promises = assetsToDelete.map(async ({ id, type }) => { - return savedObjectsClient.delete(type, id); + return savedObjectsClient.delete(type, id, { namespace }); }); return Promise.all(promises); @@ -123,12 +135,15 @@ function deleteESAssets( } async function deleteAssets( - { installed_es: installedEs, installed_kibana: installedKibana }: Installation, + { + installed_es: installedEs, + installed_kibana: installedKibana, + installed_kibana_space_id: spaceId = DEFAULT_SPACE_ID, + }: Installation, savedObjectsClient: SavedObjectsClientContract, esClient: ElasticsearchClient ) { const logger = appContextService.getLogger(); - // must delete index templates first, or component templates which reference them cannot be deleted // must delete ingestPipelines first, or ml models referenced in them cannot be deleted. // separate the assets into Index Templates and other assets. @@ -155,7 +170,7 @@ async function deleteAssets( // then the other asset types await Promise.all([ ...deleteESAssets(otherAssets, esClient), - deleteKibanaAssets(installedKibana, savedObjectsClient), + deleteKibanaAssets(installedKibana, spaceId), ]); } catch (err) { // in the rollback case, partial installs are likely, so missing assets are not an error @@ -187,19 +202,24 @@ async function deleteComponentTemplate(esClient: ElasticsearchClient, name: stri } } -export async function deleteKibanaSavedObjectsAssets( - savedObjectsClient: SavedObjectsClientContract, - installedRefs: KibanaAssetReference[] -) { +export async function deleteKibanaSavedObjectsAssets({ + savedObjectsClient, + installedPkg, +}: { + savedObjectsClient: SavedObjectsClientContract; + installedPkg: SavedObject; +}) { + const { installed_kibana: installedRefs, installed_kibana_space_id: spaceId } = + installedPkg.attributes; if (!installedRefs.length) return; const logger = appContextService.getLogger(); const assetsToDelete = installedRefs .filter(({ type }) => kibanaSavedObjectTypes.includes(type)) - .map(({ id, type }) => ({ id, type })); + .map(({ id, type }) => ({ id, type } as KibanaAssetReference)); try { - await deleteKibanaAssets(assetsToDelete, savedObjectsClient); + await deleteKibanaAssets(assetsToDelete, spaceId); } catch (err) { // in the rollback case, partial installs are likely, so missing assets are not an error if (!savedObjectsClient.errors.isNotFoundError(err)) { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index e0bdda6e2449b..03b99a291d094 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -18,6 +18,8 @@ import type { import uuid from 'uuid'; import { safeLoad } from 'js-yaml'; +import { DEFAULT_SPACE_ID } from '../../../spaces/common/constants'; + import type { AuthenticatedUser } from '../../../security/server'; import { packageToPackagePolicy, @@ -92,6 +94,7 @@ class PackagePolicyService { esClient: ElasticsearchClient, packagePolicy: NewPackagePolicy, options?: { + spaceId?: string; id?: string; user?: AuthenticatedUser; bumpRevision?: boolean; @@ -134,6 +137,7 @@ class PackagePolicyService { const [, packageInfo] = await Promise.all([ ensureInstalledPackage({ esClient, + spaceId: options?.spaceId || DEFAULT_SPACE_ID, savedObjectsClient: soClient, pkgName: packagePolicy.package.name, pkgVersion: packagePolicy.package.version, diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 8324079e10da8..3bba721d0dffb 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -9,6 +9,7 @@ import uuid from 'uuid'; import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import { DEFAULT_SPACE_ID } from '../../../spaces/common/constants'; import type { InstallResult, @@ -225,7 +226,8 @@ describe('policy preconfiguration', () => { esClient, [], [], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(policies.length).toBe(0); @@ -242,7 +244,8 @@ describe('policy preconfiguration', () => { esClient, [], [{ name: 'test_package', version: '3.0.0' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(policies.length).toBe(0); @@ -271,7 +274,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [{ name: 'test_package', version: '3.0.0' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(policies.length).toEqual(1); @@ -322,7 +326,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [{ name: 'test_package', version: '3.0.0' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(mockedPackagePolicyService.create).not.toBeCalled(); @@ -371,7 +376,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [{ name: 'test_package', version: '3.0.0' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(mockedPackagePolicyService.create).toBeCalledTimes(1); @@ -398,7 +404,8 @@ describe('policy preconfiguration', () => { { name: 'test_package', version: '3.0.0' }, { name: 'test_package', version: '2.0.0' }, ], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ) ).rejects.toThrow( 'Duplicate packages specified in configuration: test_package-3.0.0, test_package-2.0.0' @@ -429,7 +436,8 @@ describe('policy preconfiguration', () => { esClient, policies, [{ name: 'test_package', version: '3.0.0' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ) ).rejects.toThrow( '[Test policy] could not be added. [test_package] could not be installed due to error: [Error: REGISTRY ERROR]' @@ -460,7 +468,8 @@ describe('policy preconfiguration', () => { esClient, policies, [{ name: 'CANNOT_MATCH', version: 'x.y.z' }], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ) ).rejects.toThrow( '[Test policy] could not be added. [test_package] is not installed, add [test_package] to [xpack.fleet.packages] or remove it from [Test package].' @@ -484,7 +493,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(policiesA.length).toEqual(1); @@ -509,7 +519,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(policiesB.length).toEqual(1); @@ -548,7 +559,8 @@ describe('policy preconfiguration', () => { }, ] as PreconfiguredAgentPolicy[], [], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(spyAgentPolicyServiceUpdate).toBeCalled(); expect(spyAgentPolicyServiceUpdate).toBeCalledWith( @@ -584,7 +596,8 @@ describe('policy preconfiguration', () => { esClient, [policy], [], - mockDefaultOutput + mockDefaultOutput, + DEFAULT_SPACE_ID ); expect(spyAgentPolicyServiceUpdate).not.toBeCalled(); expect(policies.length).toEqual(1); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index a41c7606287ee..8593f753de738 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -144,7 +144,8 @@ export async function ensurePreconfiguredPackagesAndPolicies( esClient: ElasticsearchClient, policies: PreconfiguredAgentPolicy[] = [], packages: PreconfiguredPackage[] = [], - defaultOutput: Output + defaultOutput: Output, + spaceId: string ): Promise { const logger = appContextService.getLogger(); @@ -179,6 +180,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( pkg.version === PRECONFIGURATION_LATEST_KEYWORD ? pkg.name : pkg ), force: true, // Always force outdated packages to be installed if a later version isn't installed + spaceId, }); const fulfilledPackages = []; diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 18c66e8267468..185be7bffdf54 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -12,6 +12,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s import { AUTO_UPDATE_PACKAGES } from '../../common'; import type { DefaultPackagesInstallationError, PreconfigurationError } from '../../common'; import { SO_SEARCH_LIMIT, DEFAULT_PACKAGES } from '../constants'; +import { DEFAULT_SPACE_ID } from '../../../spaces/common/constants'; import { appContextService } from './app_context'; import { agentPolicyService } from './agent_policy'; @@ -103,7 +104,8 @@ async function createSetupSideEffects( esClient, policies, packages, - defaultOutput + defaultOutput, + DEFAULT_SPACE_ID ); logger.debug('Cleaning up Fleet outputs'); @@ -160,6 +162,7 @@ export async function ensureFleetGlobalEsAssets( savedObjectsClient: soClient, pkgkey: pkgToPkgKey({ name: installation.name, version: installation.version }), esClient, + spaceId: DEFAULT_SPACE_ID, // Force install the package will update the index template and the datastream write indices force: true, }).catch((err) => { diff --git a/x-pack/plugins/fleet/server/types/request_context.ts b/x-pack/plugins/fleet/server/types/request_context.ts index aafa8765723d1..0f570f1877627 100644 --- a/x-pack/plugins/fleet/server/types/request_context.ts +++ b/x-pack/plugins/fleet/server/types/request_context.ts @@ -33,6 +33,7 @@ export interface FleetRequestHandlerContext extends RequestHandlerContext { */ readonly internalSoClient: SavedObjectsClientContract; }; + spaceId: string; }; } diff --git a/x-pack/plugins/fleet/storybook/context/fixtures/packages.ts b/x-pack/plugins/fleet/storybook/context/fixtures/packages.ts index 4c13b6b6bf8cb..a3e436b3f9718 100644 --- a/x-pack/plugins/fleet/storybook/context/fixtures/packages.ts +++ b/x-pack/plugins/fleet/storybook/context/fixtures/packages.ts @@ -50,6 +50,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'ga_installed', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, @@ -86,6 +87,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'ga_installed_update', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, @@ -147,6 +149,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'beta_installed', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, @@ -183,6 +186,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'beta_installed_update', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, @@ -253,6 +257,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'exp_installed', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, @@ -289,6 +294,7 @@ export const items: GetPackagesResponse['items'] = [ id: 'exp_installed_update', attributes: { installed_kibana: [], + installed_kibana_space_id: 'default', installed_es: [], package_assets: [], es_index_patterns: {}, diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap index 5eb4ae1808d7b..2e0014b998bdc 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap @@ -446,6 +446,7 @@ Object { "type": "search", }, ], + "installed_kibana_space_id": "default", "name": "apache", "package_assets": Array [ Object { diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js index 3428b4c1ded08..70c8748932f6e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/index.js +++ b/x-pack/test/fleet_api_integration/apis/epm/index.js @@ -19,6 +19,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./install_overrides')); loadTestFile(require.resolve('./install_prerelease')); loadTestFile(require.resolve('./install_remove_assets')); + loadTestFile(require.resolve('./install_remove_kbn_assets_in_space')); loadTestFile(require.resolve('./install_remove_multiple')); loadTestFile(require.resolve('./install_update')); loadTestFile(require.resolve('./bulk_upgrade')); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index 4c6976c463217..af807d4393daf 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -496,6 +496,7 @@ const expectAssetsInstalled = ({ package_assets: sortBy(res.attributes.package_assets, (o: AssetReference) => o.type), }; expect(sortedRes).eql({ + installed_kibana_space_id: 'default', installed_kibana: [ { id: 'sample_dashboard', diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_kbn_assets_in_space.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_kbn_assets_in_space.ts new file mode 100644 index 0000000000000..9e364f28f8b3e --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_kbn_assets_in_space.ts @@ -0,0 +1,170 @@ +/* + * 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 type { Client } from '@elastic/elasticsearch'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { setupFleetAndAgents } from '../agents/services'; + +const testSpaceId = 'fleet_test_space'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + const dockerServers = getService('dockerServers'); + const server = dockerServers.get('registry'); + const es: Client = getService('es'); + const pkgName = 'only_dashboard'; + const pkgVersion = '0.1.0'; + + const uninstallPackage = async (pkg: string, version: string) => { + await supertest.delete(`/api/fleet/epm/packages/${pkg}/${version}`).set('kbn-xsrf', 'xxxx'); + }; + + const installPackageInSpace = async (pkg: string, version: string, spaceId: string) => { + await supertest + .post(`/s/${spaceId}/api/fleet/epm/packages/${pkg}/${version}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + const createSpace = async (spaceId: string) => { + await supertest.post(`/api/spaces/space`).set('kbn-xsrf', 'xxxx').send({ + name: spaceId, + id: spaceId, + initials: 's', + color: '#D6BF57', + disabledFeatures: [], + imageUrl: '', + }); + }; + + const deleteSpace = async (spaceId: string) => { + await supertest.delete(`/api/spaces/space/${spaceId}`).set('kbn-xsrf', 'xxxx').send(); + }; + + describe('installs and uninstalls all assets (non default space)', async () => { + skipIfNoDockerRegistry(providerContext); + setupFleetAndAgents(providerContext); + before(async () => { + await createSpace(testSpaceId); + }); + + after(async () => { + await deleteSpace(testSpaceId); + }); + describe('installs all assets when installing a package for the first time in non default space', async () => { + before(async () => { + if (!server.enabled) return; + await installPackageInSpace(pkgName, pkgVersion, testSpaceId); + }); + after(async () => { + if (!server.enabled) return; + await uninstallPackage(pkgName, pkgVersion); + }); + + expectAssetsInstalled({ + pkgVersion, + pkgName, + es, + kibanaServer, + }); + }); + + describe('uninstalls all assets when uninstalling a package from a different space', async () => { + before(async () => { + if (!server.enabled) return; + await installPackageInSpace(pkgName, pkgVersion, testSpaceId); + await uninstallPackage(pkgName, pkgVersion); + }); + + it('should have uninstalled the kibana assets', async function () { + let resDashboard; + try { + resDashboard = await kibanaServer.savedObjects.get({ + type: 'dashboard', + id: 'test_dashboard', + space: testSpaceId, + }); + } catch (err) { + resDashboard = err; + } + expect(resDashboard.response.data.statusCode).equal(404); + + let resVis; + try { + resVis = await kibanaServer.savedObjects.get({ + type: 'visualization', + id: 'test_visualization', + space: testSpaceId, + }); + } catch (err) { + resVis = err; + } + expect(resVis.response.data.statusCode).equal(404); + let resSearch; + try { + resVis = await kibanaServer.savedObjects.get({ + type: 'search', + id: 'test_search', + space: testSpaceId, + }); + } catch (err) { + resSearch = err; + } + expect(resSearch.response.data.statusCode).equal(404); + }); + }); + }); +} + +const expectAssetsInstalled = ({ + pkgVersion, + pkgName, + es, + kibanaServer, +}: { + pkgVersion: string; + pkgName: string; + es: Client; + kibanaServer: any; +}) => { + it('should have installed the kibana assets', async function () { + // These are installed from Fleet along with every package + const resIndexPatternLogs = await kibanaServer.savedObjects.get({ + type: 'index-pattern', + id: 'logs-*', + }); + expect(resIndexPatternLogs.id).equal('logs-*'); + const resIndexPatternMetrics = await kibanaServer.savedObjects.get({ + type: 'index-pattern', + id: 'metrics-*', + }); + expect(resIndexPatternMetrics.id).equal('metrics-*'); + + // These are the assets from the package + const resDashboard = await kibanaServer.savedObjects.get({ + type: 'dashboard', + id: 'test_dashboard', + space: testSpaceId, + }); + expect(resDashboard.id).equal('test_dashboard'); + + const resVis = await kibanaServer.savedObjects.get({ + type: 'visualization', + id: 'test_visualization', + space: testSpaceId, + }); + expect(resVis.id).equal('test_visualization'); + const resSearch = await kibanaServer.savedObjects.get({ + type: 'search', + id: 'test_search', + space: testSpaceId, + }); + expect(resSearch.id).equal('test_search'); + }); +}; diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index faa95f3b65e60..1947396b8a2bd 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -328,6 +328,7 @@ export default function (providerContext: FtrProviderContext) { id: 'all_assets', }); expect(res.attributes).eql({ + installed_kibana_space_id: 'default', installed_kibana: [ { id: 'sample_dashboard', diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/docs/README.md new file mode 100644 index 0000000000000..2617f1fcabe11 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/docs/README.md @@ -0,0 +1,3 @@ +# Test package + +For testing that a package installs its elasticsearch assets when installed for the first time (not updating) and removing the package diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/img/logo_overrides_64_color.svg b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/img/logo_overrides_64_color.svg new file mode 100644 index 0000000000000..b03007a76ffcc --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/img/logo_overrides_64_color.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/dashboard/test_dashboard.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/dashboard/test_dashboard.json new file mode 100644 index 0000000000000..915d2fea2438c --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/dashboard/test_dashboard.json @@ -0,0 +1,22 @@ +{ + "attributes": { + "description": "Sample dashboard", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"highlightAll\":true,\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"version\":true}" + }, + "optionsJSON": "{\"darkTheme\":false}", + "panelsJSON": "[{\"embeddableConfig\":{},\"gridData\":{\"h\":12,\"i\":\"1\",\"w\":24,\"x\":0,\"y\":0},\"panelIndex\":\"1\",\"panelRefName\":\"panel_0\",\"version\":\"7.3.0\"},{\"embeddableConfig\":{\"columns\":[\"kafka.log.class\",\"kafka.log.trace.class\",\"kafka.log.trace.full\"],\"sort\":[\"@timestamp\",\"desc\"]},\"gridData\":{\"h\":12,\"i\":\"2\",\"w\":24,\"x\":24,\"y\":0},\"panelIndex\":\"2\",\"panelRefName\":\"panel_1\",\"version\":\"7.3.0\"},{\"embeddableConfig\":{\"columns\":[\"log.level\",\"kafka.log.component\",\"message\"],\"sort\":[\"@timestamp\",\"desc\"]},\"gridData\":{\"h\":20,\"i\":\"3\",\"w\":48,\"x\":0,\"y\":20},\"panelIndex\":\"3\",\"panelRefName\":\"panel_2\",\"version\":\"7.3.0\"},{\"embeddableConfig\":{},\"gridData\":{\"h\":8,\"i\":\"4\",\"w\":48,\"x\":0,\"y\":12},\"panelIndex\":\"4\",\"panelRefName\":\"panel_3\",\"version\":\"7.3.0\"}]", + "timeRestore": false, + "title": "[Logs Sample] Overview ECS", + "version": 1 + }, + "references": [ + { "id": "test_visualization", "name": "panel_0", "type": "visualization" }, + { "id": "test_search", "name": "panel_1", "type": "search" }, + { "id": "test_search", "name": "panel_2", "type": "search" }, + { "id": "test_visualization", "name": "panel_3", "type": "visualization" } + ], + "id": "test_dashboard", + "type": "dashboard" +} diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/search/test_search.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/search/test_search.json new file mode 100644 index 0000000000000..4e0d570fca763 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/search/test_search.json @@ -0,0 +1,24 @@ +{ + "attributes": { + "columns": [ + "log.level", + "kafka.log.component", + "message" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[{\"$state\":{\"store\":\"appState\"},\"meta\":{\"alias\":null,\"disabled\":false,\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\",\"key\":\"dataset.name\",\"negate\":false,\"params\":{\"query\":\"kafka.log\",\"type\":\"phrase\"},\"type\":\"phrase\",\"value\":\"log\"},\"query\":{\"match\":{\"dataset.name\":{\"query\":\"kafka.log\",\"type\":\"phrase\"}}}}],\"highlightAll\":true,\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\",\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"version\":true}" + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "title": "All logs [Logs Kafka] ECS", + "version": 1 + }, + "id": "test_search", + "type": "search" +} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/visualization/test_visualization.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/visualization/test_visualization.json new file mode 100644 index 0000000000000..7afd27374f3b1 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/kibana/visualization/test_visualization.json @@ -0,0 +1,11 @@ +{ + "attributes": { + "description": "sample visualization update", + "title": "sample vis title", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"extended_bounds\":{},\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1},\"schema\":\"segment\",\"type\":\"date_histogram\"},{\"enabled\":true,\"id\":\"3\",\"params\":{\"customLabel\":\"Log Level\",\"field\":\"log.level\",\"order\":\"desc\",\"orderBy\":\"1\",\"size\":5},\"schema\":\"group\",\"type\":\"terms\"}],\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per day\"},\"type\":\"category\"}],\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"legendPosition\":\"right\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"histogram\",\"valueAxis\":\"ValueAxis-1\"}],\"times\":[],\"type\":\"histogram\",\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"Count\"},\"type\":\"value\"}]},\"title\":\"Log levels over time [Logs Kafka] ECS\",\"type\":\"histogram\"}" + }, + "id": "test_visualization", + "type": "visualization" +} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml new file mode 100644 index 0000000000000..129ee4e2c133c --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml @@ -0,0 +1,20 @@ +format_version: 1.0.0 +name: only_dashboard +title: Only installs one dashboard +description: This package is used to test installing assets into a non-default space. +version: 0.1.0 +categories: [] +release: beta +type: integration +license: basic + +requirement: + elasticsearch: + versions: '>7.7.0' + kibana: + versions: '>7.7.0' + +icons: + - src: '/img/logo_overrides_64_color.svg' + size: '16x16' + type: 'image/svg+xml'