Add connector and actionTaskParams modelVersions (#184542)

Resolves: #177059

This PR adds model versions for `action` and `action_task_params` saved
objects.
I also re-organised the schema and model version file structure in
alerting, task_manager and actions plugins.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ersin Erdal 2024-06-18 16:31:16 +02:00 committed by GitHub
parent c34bb46c8a
commit 51bc0dc039
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 161 additions and 214 deletions

View file

@ -55,8 +55,8 @@ describe('checking migration metadata changes on all registered SO types', () =>
expect(hashMap).toMatchInlineSnapshot(`
Object {
"action": "cc93fe2c0c76e57c2568c63170e05daea897c136",
"action_task_params": "96e27e7f4e8273ffcd87060221e2b75e81912dd5",
"action": "0e6fc0b74c7312a8c11ff6b14437b93a997358b8",
"action_task_params": "b50cb5c8a493881474918e8d4985e61374ca4c30",
"ad_hoc_run_params": "d4e3c5c794151d0a4f5c71e886b2aa638da73ad2",
"alert": "3a67d3f1db80af36bd57aaea47ecfef87e43c58f",
"api_key_pending_invalidation": "1399e87ca37b3d3a65d269c924eda70726cfe886",

View file

@ -13,6 +13,7 @@ import type {
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
import { getOldestIdleActionTask } from '@kbn/task-manager-plugin/server';
import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
import { actionTaskParamsModelVersions } from './model_versions';
import { actionMappings, actionTaskParamsMappings, connectorTokenMappings } from './mappings';
import { getActionsMigrations } from './actions_migrations';
import { getActionTaskParamsMigrations } from './action_task_params_migrations';
@ -25,6 +26,7 @@ import {
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
CONNECTOR_TOKEN_SAVED_OBJECT_TYPE,
} from '../constants/saved_objects';
import { connectorModelVersions } from './model_versions';
export function setupSavedObjects(
savedObjects: SavedObjectsServiceSetup,
@ -60,6 +62,7 @@ export function setupSavedObjects(
};
},
},
modelVersions: connectorModelVersions,
});
// Encrypted attributes
@ -94,6 +97,7 @@ export function setupSavedObjects(
},
};
},
modelVersions: actionTaskParamsModelVersions,
});
encryptedSavedObjects.registerType({
type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,

View file

@ -0,0 +1,18 @@
/*
* 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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
import { actionTaskParamsSchemaV1 } from '../schemas/action_task_params';
export const actionTaskParamsModelVersions: SavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: actionTaskParamsSchemaV1,
},
},
};

View file

@ -0,0 +1,18 @@
/*
* 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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
import { rawConnectorSchemaV1 } from '../schemas/raw_connector';
export const connectorModelVersions: SavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: rawConnectorSchemaV1,
},
},
};

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { connectorModelVersions } from './connector_model_versions';
export { actionTaskParamsModelVersions } from './action_task_params_model_versions';

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { actionTaskParamsSchema as actionTaskParamsSchemaV1 } from './v1';

View file

@ -0,0 +1,29 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const actionTaskParamsSchema = schema.object({
actionId: schema.string(),
executionId: schema.maybe(schema.string()),
apiKey: schema.nullable(schema.string()),
params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }),
consumer: schema.maybe(schema.string()),
source: schema.maybe(schema.string()),
relatedSavedObjects: schema.maybe(
schema.arrayOf(
schema.object({
namespace: schema.maybe(schema.string({ minLength: 1 })),
id: schema.string({ minLength: 1 }),
type: schema.string({ minLength: 1 }),
// optional; for SO types like action/alert that have type id's
typeId: schema.maybe(schema.string({ minLength: 1 })),
}),
{ defaultValue: [] }
)
),
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { rawConnectorSchema as rawConnectorSchemaV1 } from './v1';

View file

@ -5,18 +5,18 @@
* 2.0.
*/
import { rawConnectorSchema } from './raw_connector_schema';
import { rawConnectorSchema } from './v1';
const action = {
actionTypeId: '12345',
name: 'test-action-name',
isMissingSecrets: true,
isMissingSecrets: false,
config: {
foo: 'bar',
},
secrets: {
secrets: JSON.stringify({
pass: 'foo',
},
}),
isPreconfigured: false,
isSystemAction: false,
};

View file

@ -11,8 +11,10 @@ export const rawConnectorSchema = schema.object({
actionTypeId: schema.string(),
name: schema.string(),
isMissingSecrets: schema.boolean(),
config: schema.recordOf(schema.string(), schema.any()),
secrets: schema.recordOf(schema.string(), schema.any()),
config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
// Encrypted fields are saved as string. Decrypted version of the 'secrets' field is an object
secrets: schema.string({ defaultValue: '{}' }),
isPreconfigured: schema.maybe(schema.boolean()),
isSystemAction: schema.maybe(schema.boolean()),
id: schema.maybe(schema.string()),

View file

@ -33,7 +33,6 @@ describe('createGetAlertIndicesAliasFn', () => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register({

View file

@ -79,7 +79,6 @@ import { registerAlertingUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
import {
setupSavedObjects,
getLatestRuleVersion,
RULE_SAVED_OBJECT_TYPE,
AD_HOC_RUN_SAVED_OBJECT_TYPE,
} from './saved_objects';
@ -333,7 +332,6 @@ export class AlertingPlugin {
alertsService: this.alertsService,
minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
inMemoryMetrics: this.inMemoryMetrics,
latestRuleVersion: getLatestRuleVersion(),
});
this.ruleTypeRegistry = ruleTypeRegistry;

View file

@ -18,7 +18,6 @@ const createRuleTypeRegistryMock = () => {
list: jest.fn(),
getAllTypes: jest.fn(),
ensureRuleTypeEnabled: jest.fn(),
getLatestRuleVersion: jest.fn(),
};
return mocked;
};

View file

@ -40,7 +40,6 @@ beforeEach(() => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
});
@ -913,16 +912,6 @@ describe('Create Lifecycle', () => {
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
});
});
describe('getLatestRuleVersion', () => {
test('should return the latest rule version', async () => {
const ruleTypeRegistry = new RuleTypeRegistry({
...ruleTypeRegistryParams,
latestRuleVersion: 5,
});
expect(ruleTypeRegistry.getLatestRuleVersion()).toBe(5);
});
});
});
function ruleTypeWithVariables<ActionGroupIds extends string>(

View file

@ -50,7 +50,6 @@ export interface ConstructorOptions {
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
inMemoryMetrics: InMemoryMetrics;
alertsService: AlertsService | null;
latestRuleVersion: number;
}
export interface RegistryRuleType
@ -160,7 +159,6 @@ export class RuleTypeRegistry {
private readonly licensing: LicensingPluginSetup;
private readonly inMemoryMetrics: InMemoryMetrics;
private readonly alertsService: AlertsService | null;
private readonly latestRuleVersion: number;
constructor({
config,
@ -172,7 +170,6 @@ export class RuleTypeRegistry {
minimumScheduleInterval,
inMemoryMetrics,
alertsService,
latestRuleVersion,
}: ConstructorOptions) {
this.config = config;
this.logger = logger;
@ -183,7 +180,6 @@ export class RuleTypeRegistry {
this.minimumScheduleInterval = minimumScheduleInterval;
this.inMemoryMetrics = inMemoryMetrics;
this.alertsService = alertsService;
this.latestRuleVersion = latestRuleVersion;
}
public has(id: string) {
@ -436,10 +432,6 @@ export class RuleTypeRegistry {
public getAllTypes(): string[] {
return [...this.ruleTypes.keys()];
}
public getLatestRuleVersion() {
return this.latestRuleVersion;
}
}
function normalizedActionVariables(actionVariables: RuleType['actionVariables']) {

View file

@ -1,51 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
SavedObjectsModelVersion,
SavedObjectsModelVersionMap,
} from '@kbn/core-saved-objects-server';
import { AdHocRunSO } from '../data/ad_hoc_run/types';
import { rawAdHocRunParamsSchemaV1 } from './schemas/raw_ad_hoc_run_params';
interface CustomSavedObjectsModelVersion extends SavedObjectsModelVersion {
isCompatibleWithPreviousVersion: (param: AdHocRunSO) => boolean;
}
export interface CustomSavedObjectsModelVersionMap extends SavedObjectsModelVersionMap {
[modelVersion: string]: CustomSavedObjectsModelVersion;
}
export const adHocRunParamsModelVersions: CustomSavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
forwardCompatibility: rawAdHocRunParamsSchemaV1.extends({}, { unknowns: 'ignore' }),
create: rawAdHocRunParamsSchemaV1,
},
isCompatibleWithPreviousVersion: () => true,
},
};
export const getLatestAdHocRunParamsVersion = () =>
Math.max(...Object.keys(adHocRunParamsModelVersions).map(Number));
export function getMinimumCompatibleVersion(
modelVersions: CustomSavedObjectsModelVersionMap,
version: number,
adHocRunParam: AdHocRunSO
): number {
if (version === 1) {
return 1;
}
if (modelVersions[version].isCompatibleWithPreviousVersion(adHocRunParam)) {
return getMinimumCompatibleVersion(modelVersions, version - 1, adHocRunParam);
}
return version;
}

View file

@ -24,13 +24,11 @@ import { getImportWarnings } from './get_import_warnings';
import { isRuleExportable } from './is_rule_exportable';
import { RuleTypeRegistry } from '../rule_type_registry';
export { partiallyUpdateRule } from './partially_update_rule';
export { getLatestRuleVersion, getMinimumCompatibleVersion } from './rule_model_versions';
import {
RULES_SETTINGS_SAVED_OBJECT_TYPE,
MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
} from '../../common';
import { ruleModelVersions } from './rule_model_versions';
import { adHocRunParamsModelVersions } from './ad_hoc_run_params_model_versions';
import { ruleModelVersions, adHocRunParamsModelVersions } from './model_versions';
export const RULE_SAVED_OBJECT_TYPE = 'alert';
export const AD_HOC_RUN_SAVED_OBJECT_TYPE = 'ad_hoc_run_params';

View file

@ -38,7 +38,6 @@ beforeEach(() => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
});

View file

@ -0,0 +1,19 @@
/*
* 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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
import { rawAdHocRunParamsSchemaV1 } from '../schemas/raw_ad_hoc_run_params';
export const adHocRunParamsModelVersions: SavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
forwardCompatibility: rawAdHocRunParamsSchemaV1.extends({}, { unknowns: 'ignore' }),
create: rawAdHocRunParamsSchemaV1,
},
},
};

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { adHocRunParamsModelVersions } from './ad_hoc_run_params_model_versions';
export { ruleModelVersions } from './rule_model_versions';

View file

@ -0,0 +1,18 @@
/*
* 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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
import { rawRuleSchemaV1 } from '../schemas/raw_rule';
export const ruleModelVersions: SavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: rawRuleSchemaV1,
},
},
};

View file

@ -1,77 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
CustomSavedObjectsModelVersionMap,
getLatestRuleVersion,
getMinimumCompatibleVersion,
} from './rule_model_versions';
import { schema } from '@kbn/config-schema';
import { RawRule } from '../types';
describe('rule model versions', () => {
const ruleModelVersions: CustomSavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => true,
},
'2': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => false,
},
'3': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test',
},
'4': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test',
},
};
const rawRule = { name: 'test' } as RawRule;
const mismatchingRawRule = { enabled: true } as RawRule;
describe('getMinimumCompatibleVersion', () => {
it('should return the minimum compatible version for the matching rawRule', () => {
expect(getMinimumCompatibleVersion(ruleModelVersions, 1, rawRule)).toBe(1);
expect(getMinimumCompatibleVersion(ruleModelVersions, 2, rawRule)).toBe(2);
expect(getMinimumCompatibleVersion(ruleModelVersions, 3, rawRule)).toBe(2);
expect(getMinimumCompatibleVersion(ruleModelVersions, 4, rawRule)).toBe(2);
});
it('should return the minimum compatible version for the mismatching rawRule', () => {
expect(getMinimumCompatibleVersion(ruleModelVersions, 3, mismatchingRawRule)).toBe(3);
expect(getMinimumCompatibleVersion(ruleModelVersions, 4, mismatchingRawRule)).toBe(4);
});
});
describe('getLatestRuleVersion', () => {
it('should return the latest rule model version', () => {
expect(getLatestRuleVersion()).toBe(1);
});
});
});

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
SavedObjectsModelVersion,
SavedObjectsModelVersionMap,
} from '@kbn/core-saved-objects-server';
import { RawRule } from '../types';
import { rawRuleSchemaV1 } from './schemas/raw_rule';
interface CustomSavedObjectsModelVersion extends SavedObjectsModelVersion {
isCompatibleWithPreviousVersion: (param: RawRule) => boolean;
}
export interface CustomSavedObjectsModelVersionMap extends SavedObjectsModelVersionMap {
[modelVersion: string]: CustomSavedObjectsModelVersion;
}
export const ruleModelVersions: CustomSavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: rawRuleSchemaV1,
},
isCompatibleWithPreviousVersion: (rawRule) => true,
},
};
export const getLatestRuleVersion = () => Math.max(...Object.keys(ruleModelVersions).map(Number));
export function getMinimumCompatibleVersion(
modelVersions: CustomSavedObjectsModelVersionMap,
version: number,
rawRule: RawRule
): number {
if (version === 1) {
return 1;
}
if (modelVersions[version].isCompatibleWithPreviousVersion(rawRule)) {
return getMinimumCompatibleVersion(modelVersions, version - 1, rawRule);
}
return version;
}

View file

@ -7,12 +7,12 @@
import type { SavedObjectsServiceSetup } from '@kbn/core/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { taskModelVersions } from './task_model_versions';
import { taskMappings } from './mappings';
import { getMigrations } from './migrations';
import { TaskManagerConfig } from '../config';
import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task';
import { TASK_MANAGER_INDEX } from '../constants';
import { taskModelVersions } from './model_versions';
export function setupSavedObjects(
savedObjects: SavedObjectsServiceSetup,

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { taskModelVersions } from './task_model_versions';

View file

@ -6,7 +6,7 @@
*/
import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
import { taskSchemaV1 } from './schemas/task';
import { taskSchemaV1 } from '../schemas/task';
export const taskModelVersions: SavedObjectsModelVersionMap = {
'1': {