[eem] allow definition to be only installed and not started (#190339)

When creating a definition we automatically start the transforms when
all the components are installed. This change introduces `installOnly:
boolean` query string that can be passed to routes that create
definitions (`POST /internal/entities/definition` and `PUT
/internal/entities/managed/enablement`) in order to only install the
components

### Testing
- `PUT kbn:/internal/entities/managed/enablement?installOnly=true`
- `GET kbn:/internal/entities/definition` returns builtin definitions
with `{ state: { installed: true, running: false } }`
- check the installed transforms are not started
This commit is contained in:
Kevin Lacabane 2024-08-13 11:37:22 +02:00 committed by GitHub
parent fe592d4f3b
commit 5290b35e5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 95 additions and 17 deletions

View file

@ -9,6 +9,7 @@ export * from './src/schema/entity_definition';
export * from './src/schema/entity';
export * from './src/schema/common';
export * from './src/schema/patterns';
export * from './src/rest_spec/create';
export * from './src/rest_spec/delete';
export * from './src/rest_spec/reset';
export * from './src/rest_spec/get';

View file

@ -0,0 +1,14 @@
/*
* 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 { z } from 'zod';
export const createEntityDefinitionQuerySchema = z.object({
installOnly: z.optional(z.coerce.boolean()).default(false),
});
export type CreateEntityDefinitionQuery = z.infer<typeof createEntityDefinitionQuerySchema>;

View file

@ -14,3 +14,5 @@ export const deleteEntityDefinitionParamsSchema = z.object({
export const deleteEntityDefinitionQuerySchema = z.object({
deleteData: z.optional(z.coerce.boolean().default(false)),
});
export type DeleteEntityDefinitionQuery = z.infer<typeof deleteEntityDefinitionQuerySchema>;

View file

@ -6,6 +6,7 @@
*/
import { HttpStart } from '@kbn/core/public';
import { CreateEntityDefinitionQuery, DeleteEntityDefinitionQuery } from '@kbn/entities-schema';
import { EntityManagerUnauthorizedError } from './errors';
import { IEntityClient } from '../types';
import {
@ -21,9 +22,13 @@ export class EntityClient implements IEntityClient {
return await this.http.get('/internal/entities/managed/enablement');
}
async enableManagedEntityDiscovery(): Promise<EnableManagedEntityResponse> {
async enableManagedEntityDiscovery(
query?: CreateEntityDefinitionQuery
): Promise<EnableManagedEntityResponse> {
try {
return await this.http.put('/internal/entities/managed/enablement');
return await this.http.put('/internal/entities/managed/enablement', {
query,
});
} catch (err) {
if (err.body?.statusCode === 403) {
throw new EntityManagerUnauthorizedError(err.body.message);
@ -32,9 +37,11 @@ export class EntityClient implements IEntityClient {
}
}
async disableManagedEntityDiscovery(): Promise<DisableManagedEntityResponse> {
async disableManagedEntityDiscovery(
query?: DeleteEntityDefinitionQuery
): Promise<DisableManagedEntityResponse> {
try {
return await this.http.delete('/internal/entities/managed/enablement');
return await this.http.delete('/internal/entities/managed/enablement', { query });
} catch (err) {
if (err.body?.statusCode === 403) {
throw new EntityManagerUnauthorizedError(err.body.message);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { Plugin as PluginClass } from '@kbn/core/public';
import { CreateEntityDefinitionQuery } from '@kbn/entities-schema';
import {
DisableManagedEntityResponse,
EnableManagedEntityResponse,
@ -26,6 +27,8 @@ export type EntityManagerPluginClass = PluginClass<
export interface IEntityClient {
isManagedEntityDiscoveryEnabled: () => Promise<ManagedEntityEnabledResponse>;
enableManagedEntityDiscovery: () => Promise<EnableManagedEntityResponse>;
enableManagedEntityDiscovery: (
query?: CreateEntityDefinitionQuery
) => Promise<EnableManagedEntityResponse>;
disableManagedEntityDiscovery: () => Promise<DisableManagedEntityResponse>;
}

View file

@ -160,8 +160,10 @@ export async function installBuiltInEntityDefinitions({
soClient,
logger,
builtInDefinitions,
installOnly,
}: Omit<InstallDefinitionParams, 'definition'> & {
builtInDefinitions: EntityDefinition[];
installOnly?: boolean;
}): Promise<EntityDefinition[]> {
if (builtInDefinitions.length === 0) return [];
@ -179,6 +181,7 @@ export async function installBuiltInEntityDefinitions({
esClient,
soClient,
logger,
installOnly,
});
}
@ -192,6 +195,7 @@ export async function installBuiltInEntityDefinitions({
esClient,
soClient,
logger,
installOnly,
});
}
@ -205,8 +209,12 @@ export async function installBuiltInEntityDefinitions({
return await Promise.all(installPromises);
}
async function installAndStartDefinition(params: InstallDefinitionParams) {
async function installAndStartDefinition(
params: InstallDefinitionParams & { installOnly?: boolean }
) {
const definition = await installEntityDefinition(params);
await startTransform(params.esClient, definition, params.logger);
if (!params.installOnly) {
await startTransform(params.esClient, definition, params.logger);
}
return definition;
}

View file

@ -6,6 +6,11 @@
*/
import { RequestHandlerContext } from '@kbn/core/server';
import {
CreateEntityDefinitionQuery,
createEntityDefinitionQuerySchema,
} from '@kbn/entities-schema';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import { SetupRouteOptions } from '../types';
import {
canEnableEntityDiscovery,
@ -26,10 +31,12 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({
server,
logger,
}: SetupRouteOptions<T>) {
router.put<unknown, unknown, unknown>(
router.put<unknown, CreateEntityDefinitionQuery, unknown>(
{
path: '/internal/entities/managed/enablement',
validate: false,
validate: {
query: buildRouteValidationWithZod(createEntityDefinitionQuerySchema),
},
},
async (context, req, res) => {
try {
@ -87,6 +94,7 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({
builtInDefinitions,
esClient,
soClient,
installOnly: req.query.installOnly,
});
return res.ok({ body: { success: true } });

View file

@ -6,7 +6,12 @@
*/
import { RequestHandlerContext } from '@kbn/core/server';
import { EntityDefinition, entityDefinitionSchema } from '@kbn/entities-schema';
import {
EntityDefinition,
entityDefinitionSchema,
createEntityDefinitionQuerySchema,
CreateEntityDefinitionQuery,
} from '@kbn/entities-schema';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import { SetupRouteOptions } from '../types';
import { EntityIdConflict } from '../../lib/entities/errors/entity_id_conflict_error';
@ -19,11 +24,12 @@ export function createEntityDefinitionRoute<T extends RequestHandlerContext>({
router,
server,
}: SetupRouteOptions<T>) {
router.post<unknown, unknown, EntityDefinition>(
router.post<unknown, CreateEntityDefinitionQuery, EntityDefinition>(
{
path: '/internal/entities/definition',
validate: {
body: buildRouteValidationWithZod(entityDefinitionSchema.strict()),
query: buildRouteValidationWithZod(createEntityDefinitionQuerySchema),
},
},
async (context, req, res) => {
@ -39,7 +45,9 @@ export function createEntityDefinitionRoute<T extends RequestHandlerContext>({
definition: req.body,
});
await startTransform(esClient, definition, logger);
if (!req.query.installOnly) {
await startTransform(esClient, definition, logger);
}
return res.ok({ body: definition });
} catch (e) {

View file

@ -6,7 +6,6 @@
*/
import expect from '@kbn/expect';
import { EntityDefinition } from '@kbn/entities-schema';
import {
entityDefinition as mockDefinition,
entityDefinitionWithBackfill as mockBackfillDefinition,
@ -26,17 +25,36 @@ export default function ({ getService }: FtrProviderContext) {
const { definitions } = await getInstalledDefinitions(supertest);
expect(definitions.length).to.eql(2);
expect(
definitions.find((definition: EntityDefinition) => definition.id === mockDefinition.id)
definitions.find(
(definition) =>
definition.id === mockDefinition.id &&
definition.state.installed === true &&
definition.state.running === true
)
);
expect(
definitions.find(
(definition: EntityDefinition) => definition.id === mockBackfillDefinition.id
(definition) =>
definition.id === mockBackfillDefinition.id &&
definition.state.installed === true &&
definition.state.running === true
)
);
await uninstallDefinition(supertest, mockDefinition.id);
await uninstallDefinition(supertest, mockBackfillDefinition.id);
});
it('does not start transforms when specified', async () => {
await installDefinition(supertest, mockDefinition, { installOnly: true });
const { definitions } = await getInstalledDefinitions(supertest);
expect(definitions.length).to.eql(1);
expect(definitions[0].state.installed).to.eql(true);
expect(definitions[0].state.running).to.eql(false);
await uninstallDefinition(supertest, mockDefinition.id);
});
});
});
}

View file

@ -7,13 +7,17 @@
import { Agent } from 'supertest';
import { EntityDefinition } from '@kbn/entities-schema';
import { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types';
export interface Auth {
username: string;
password: string;
}
export const getInstalledDefinitions = async (supertest: Agent, auth?: Auth) => {
export const getInstalledDefinitions = async (
supertest: Agent,
auth?: Auth
): Promise<{ definitions: EntityDefinitionWithState[] }> => {
let req = supertest.get('/internal/entities/definition').set('kbn-xsrf', 'xxx');
if (auth) {
req = req.auth(auth.username, auth.password);
@ -22,9 +26,14 @@ export const getInstalledDefinitions = async (supertest: Agent, auth?: Auth) =>
return response.body;
};
export const installDefinition = async (supertest: Agent, definition: EntityDefinition) => {
export const installDefinition = async (
supertest: Agent,
definition: EntityDefinition,
query: Record<string, any> = {}
) => {
return supertest
.post('/internal/entities/definition')
.query(query)
.set('kbn-xsrf', 'xxx')
.send(definition)
.expect(200);