[8.0] [Fleet] Add installed_kibana_space_id to epm-packages saved objects (#120517) (#121553)

* [Fleet] Add `installed_kibana_space_id` to `epm-packages` saved objects (#120517)

# Conflicts:
#	x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
#	x-pack/plugins/fleet/server/routes/preconfiguration/index.ts

* fix handler types

Co-authored-by: Mark Hopkin <mark.hopkin@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Josh Dover 2021-12-20 12:13:47 +01:00 committed by GitHub
parent d8cb6f164b
commit 1ef87700b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 430 additions and 60 deletions

View file

@ -36,6 +36,7 @@ interface SavedObjectResponse<Attributes extends Record<string, any>> {
interface GetOptions {
type: string;
id: string;
space?: string;
}
interface IndexOptions<Attributes> {
@ -110,11 +111,13 @@ export class KbnClientSavedObjects {
* Get an object
*/
public async get<Attributes extends Record<string, any>>(options: GetOptions) {
this.log.debug('Gettings saved object: %j', options);
this.log.debug('Getting saved object: %j', options);
const { data } = await this.requester.request<SavedObjectResponse<Attributes>>({
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',
});

View file

@ -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;
}

View file

@ -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",

View file

@ -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.

View file

@ -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"]

View file

@ -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',

View file

@ -80,6 +80,7 @@ export const createFleetRequestHandlerContextMock = (): jest.Mocked<
epm: {
internalSoClient: savedObjectsClientMock.create(),
},
spaceId: 'default',
};
};

View file

@ -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);
},
};
}
);

View file

@ -22,6 +22,7 @@ import type {
CopyAgentPolicyRequestSchema,
DeleteAgentPolicyRequestSchema,
GetFullAgentPolicyRequestSchema,
FleetRequestHandler,
} from '../../types';
import type { AgentPolicy, NewPackagePolicy } from '../../types';
import { FLEET_SYSTEM_PACKAGE } from '../../../common';
@ -100,7 +101,7 @@ export const getOneAgentPolicyHandler: RequestHandler<
}
};
export const createAgentPolicyHandler: RequestHandler<
export const createAgentPolicyHandler: FleetRequestHandler<
undefined,
TypeOf<typeof CreateAgentPolicyRequestSchema.query>,
TypeOf<typeof CreateAgentPolicyRequestSchema.body>
@ -109,7 +110,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<
@ -136,6 +137,7 @@ export const createAgentPolicyHandler: RequestHandler<
newSysPackagePolicy.name = await incrementPackageName(soClient, FLEET_SYSTEM_PACKAGE);
await packagePolicyService.create(soClient, esClient, newSysPackagePolicy, {
spaceId,
user,
bumpRevision: false,
});

View file

@ -255,11 +255,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) {
@ -294,10 +296,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 = {
@ -322,12 +326,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) {

View file

@ -7,8 +7,9 @@
import { httpServerMock, httpServiceMock } from 'src/core/server/mocks';
import type { KibanaRequest } from 'kibana/server';
import type { IRouter, RequestHandler, RouteConfig } from 'kibana/server';
import type { RouteConfig } from 'kibana/server';
import type { FleetRouter } from '../../types/request_context';
import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants';
import { appContextService, packagePolicyService } from '../../services';
import { createAppContextStartContractMock, xpackMocks } from '../../mocks';
@ -21,8 +22,7 @@ import type {
CreatePackagePolicyRequestSchema,
UpdatePackagePolicyRequestSchema,
} from '../../types/rest_spec';
import type { PackagePolicy } from '../../types';
import type { PackagePolicy, FleetRequestHandler } from '../../types';
import { registerRoutes } from './index';
@ -93,8 +93,8 @@ jest.mock('../../services/epm/packages', () => {
});
describe('When calling package policy', () => {
let routerMock: jest.Mocked<IRouter>;
let routeHandler: RequestHandler<any, any, any>;
let routerMock: jest.Mocked<FleetRouter>;
let routeHandler: FleetRequestHandler<any, any, any>;
let routeConfig: RouteConfig<any, any, any, any>;
let context: ReturnType<typeof xpackMocks.createRequestHandlerContext>;
let response: ReturnType<typeof httpServerMock.createResponseFactory>;

View file

@ -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<typeof CreatePackagePolicyRequestSchema.body>
@ -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({

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import type { IRouter } from 'src/core/server';
import { PLUGIN_ID, PACKAGE_POLICY_API_ROUTES } from '../../constants';
import type { FleetRouter } from '../../types/request_context';
import {
GetPackagePoliciesRequestSchema,
GetOnePackagePolicyRequestSchema,
@ -28,7 +27,7 @@ import {
dryRunUpgradePackagePolicyHandler,
} from './handlers';
export const registerRoutes = (router: IRouter) => {
export const registerRoutes = (router: FleetRouter) => {
// List
router.get(
{

View file

@ -11,11 +11,12 @@ import type { TypeOf } from '@kbn/config-schema';
import type { PreconfiguredAgentPolicy } from '../../../common';
import { PLUGIN_ID, PRECONFIGURATION_API_ROUTES } from '../../constants';
import type { FleetRequestHandler } from '../../types';
import { PutPreconfigurationSchema } from '../../types';
import { defaultIngestErrorHandler } from '../../errors';
import { ensurePreconfiguredPackagesAndPolicies, outputService } from '../../services';
export const updatePreconfigurationHandler: RequestHandler<
export const updatePreconfigurationHandler: FleetRequestHandler<
undefined,
undefined,
TypeOf<typeof PutPreconfigurationSchema.body>
@ -23,7 +24,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 {
@ -32,7 +33,8 @@ export const updatePreconfigurationHandler: RequestHandler<
esClient,
(agentPolicies as PreconfiguredAgentPolicy[]) ?? [],
packages ?? [],
defaultOutput
defaultOutput,
spaceId
);
return response.ok({ body });
} catch (error) {
@ -47,6 +49,6 @@ export const registerRoutes = (router: IRouter) => {
validate: PutPreconfigurationSchema,
options: { tags: [`access:${PLUGIN_ID}-all`] },
},
updatePreconfigurationHandler
updatePreconfigurationHandler as RequestHandler
);
};

View file

@ -45,6 +45,7 @@ describe('FleetSetupHandler', () => {
epm: {
internalSoClient: savedObjectsClientMock.create(),
},
spaceId: 'default',
},
};
response = httpServerMock.createResponseFactory();

View file

@ -239,6 +239,7 @@ const getSavedObjectTypes = (
type: { type: 'keyword' },
},
},
installed_kibana_space_id: { type: 'keyword' },
package_assets: {
type: 'nested',
properties: {

View file

@ -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)

View file

@ -56,6 +56,7 @@ export async function _installPackage({
packageInfo,
installType,
installSource,
spaceId,
}: {
savedObjectsClient: SavedObjectsClientContract;
savedObjectsImporter: Pick<SavedObjectsImporter, 'import' | 'resolveImportErrors'>;
@ -66,6 +67,7 @@ export async function _installPackage({
packageInfo: InstallablePackage;
installType: InstallType;
installSource: InstallSource;
spaceId: string;
}): Promise<AssetReference[]> {
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,

View file

@ -20,12 +20,14 @@ interface BulkInstallPackagesParams {
packagesToInstall: Array<string | { name: string; version: string }>;
esClient: ElasticsearchClient;
force?: boolean;
spaceId: string;
}
export async function bulkInstallPackages({
savedObjectsClient,
packagesToInstall,
esClient,
spaceId,
force,
}: BulkInstallPackagesParams): Promise<BulkInstallResponse[]> {
const logger = appContextService.getLogger();
@ -70,6 +72,7 @@ export async function bulkInstallPackages({
esClient,
pkgkey: Registry.pkgToPkgKey(pkgKeyProps),
installSource,
spaceId,
force,
});
if (installResult.error) {

View file

@ -18,6 +18,7 @@ const mockInstallation: SavedObject<Installation> = {
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<Installation> = {
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: [],

View file

@ -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: '',

View file

@ -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<Installation> {
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<Installation> | 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<InstallResult> {
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<InstallResult> {
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<InstallSource, 'registry'> } & InstallRegistryPackageParams)
| ({ installSource: Extract<InstallSource, 'upload'> } & InstallUploadedArchiveParams);
| ({ installSource: Extract<InstallSource, 'upload'> } & 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,
})
);
}

View file

@ -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<Installation>;
}) {
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)) {

View file

@ -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,

View file

@ -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);

View file

@ -144,7 +144,8 @@ export async function ensurePreconfiguredPackagesAndPolicies(
esClient: ElasticsearchClient,
policies: PreconfiguredAgentPolicy[] = [],
packages: PreconfiguredPackage[] = [],
defaultOutput: Output
defaultOutput: Output,
spaceId: string
): Promise<PreconfigurationResult> {
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 = [];

View file

@ -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) => {

View file

@ -33,6 +33,7 @@ export interface FleetRequestHandlerContext extends RequestHandlerContext {
*/
readonly internalSoClient: SavedObjectsClientContract;
};
spaceId: string;
};
}

View file

@ -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: {},

View file

@ -446,6 +446,7 @@ Object {
"type": "search",
},
],
"installed_kibana_space_id": "default",
"name": "apache",
"package_assets": Array [
Object {

View file

@ -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'));

View file

@ -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',

View file

@ -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');
});
};

View file

@ -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',

View file

@ -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

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<g fill="none" fill-rule="evenodd">
<path fill="#F04E98" d="M29,32.0001 L15.935,9.4321 C13.48,5.1941 7,6.9351 7,11.8321 L7,52.1681 C7,57.0651 13.48,58.8061 15.935,54.5671 L29,32.0001 Z"/>
<path fill="#FA744E" d="M34.7773,32.0001 L33.3273,34.5051 L20.2613,57.0731 C19.8473,57.7871 19.3533,58.4271 18.8023,59.0001 L34.9273,59.0001 C38.7073,59.0001 42.2213,57.0601 44.2363,53.8611 L58.0003,32.0001 L34.7773,32.0001 Z"/>
<path fill="#343741" d="M44.2363,10.1392 C42.2213,6.9402 38.7073,5.0002 34.9273,5.0002 L18.8023,5.0002 C19.3533,5.5732 19.8473,6.2122 20.2613,6.9272 L33.3273,29.4942 L34.7773,32.0002 L58.0003,32.0002 L44.2363,10.1392 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 751 B

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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'