From 3d37afba4e711c6ed094782b134fc23b4b124767 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 19 Oct 2021 16:48:15 -0500 Subject: [PATCH] [Logs/Metrics UI] Add deprecated field configuration to Deprecations API (#115103) (#115633) * [Logs/Metrics UI] Add deprecated field configuration to Deprecations API * Add correction steps * Add unit test for source config deprecations * Apply suggestions from code review Co-authored-by: Chris Cowan * Lint fix Co-authored-by: Chris Cowan # Conflicts: # x-pack/plugins/infra/server/plugin.ts --- .../plugins/infra/server/deprecations.test.ts | 86 +++++++ x-pack/plugins/infra/server/deprecations.ts | 211 ++++++++++++++++++ x-pack/plugins/infra/server/plugin.ts | 8 +- 3 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/infra/server/deprecations.test.ts create mode 100644 x-pack/plugins/infra/server/deprecations.ts diff --git a/x-pack/plugins/infra/server/deprecations.test.ts b/x-pack/plugins/infra/server/deprecations.test.ts new file mode 100644 index 0000000000000..318f1e50d6662 --- /dev/null +++ b/x-pack/plugins/infra/server/deprecations.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { getInfraDeprecationsFactory } from './deprecations'; + +describe('Infra plugin deprecations', () => { + describe('Source configuration deprecations', () => { + test('returns no deprecations when all fields are set to the default values', async () => { + const sources = { + getAllSourceConfigurations: () => [ + { + configuration: { + name: 'Default', + fields: { + timestamp: '@timestamp', + tiebreaker: '_doc', + container: 'container.id', + host: 'host.name', + pod: 'kubernetes.pod.uid', + }, + }, + }, + { + configuration: { + name: 'Alternate', + fields: { + timestamp: '@timestamp', + tiebreaker: '_doc', + container: 'container.id', + host: 'host.name', + pod: 'kubernetes.pod.uid', + }, + }, + }, + ], + }; + const getDeprecations = getInfraDeprecationsFactory(sources as any); + const deprecations = await getDeprecations({} as any); + expect(deprecations.length).toBe(0); + }); + }); + test('returns expected deprecations when some fields are not set to default values in one or more source configurations', async () => { + const sources = { + getAllSourceConfigurations: () => [ + { + configuration: { + name: 'Default', + fields: { + timestamp: 'not-@timestamp', + tiebreaker: '_doc', + container: 'not-container.id', + host: 'host.name', + pod: 'not-kubernetes.pod.uid', + }, + }, + }, + { + configuration: { + name: 'Alternate', + fields: { + timestamp: 'not-@timestamp', + tiebreaker: 'not-_doc', + container: 'container.id', + host: 'not-host.name', + pod: 'kubernetes.pod.uid', + }, + }, + }, + ], + }; + const getDeprecations = getInfraDeprecationsFactory(sources as any); + const deprecations = await getDeprecations({} as any); + expect(deprecations.length).toBe(5); + expect( + deprecations.map((d) => + d.title.replace(/Source configuration field "(.*)" is deprecated./, '$1') + ) + ).toEqual( + expect.arrayContaining(['timestamp', 'tiebreaker', 'container ID', 'host name', 'pod ID']) + ); + }); +}); diff --git a/x-pack/plugins/infra/server/deprecations.ts b/x-pack/plugins/infra/server/deprecations.ts new file mode 100644 index 0000000000000..27c2b235f769b --- /dev/null +++ b/x-pack/plugins/infra/server/deprecations.ts @@ -0,0 +1,211 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; +import { + ConfigDeprecationProvider, + ConfigDeprecation, + DeprecationsDetails, + GetDeprecationsContext, +} from 'src/core/server'; +import { InfraSources } from './lib/sources'; + +const deprecatedFieldMessage = (fieldName: string, defaultValue: string, configNames: string[]) => + i18n.translate('xpack.infra.deprecations.deprecatedFieldDescription', { + defaultMessage: + 'Configuring the "{fieldName}" field has been deprecated and will be removed in 8.0.0. This plugin is designed to work with ECS, and expects this field to have a value of `{defaultValue}`. It has a different value configured in Source {configCount, plural, one {Configuration} other {Configurations}}: {configNames}', + values: { + fieldName, + defaultValue, + configNames: configNames.join(', '), + configCount: configNames.length, + }, + }); + +const DEFAULT_VALUES = { + timestamp: '@timestamp', + tiebreaker: '_doc', + container: 'container.id', + host: 'host.name', + pod: 'kubernetes.pod.uid', +}; + +const FIELD_DEPRECATION_FACTORIES: Record DeprecationsDetails> = + { + timestamp: (configNames) => ({ + level: 'critical', + title: i18n.translate('xpack.infra.deprecations.timestampFieldTitle', { + defaultMessage: 'Source configuration field "timestamp" is deprecated.', + }), + message: deprecatedFieldMessage( + i18n.translate('xpack.infra.deprecations.timestampFieldName', { + defaultMessage: 'timestamp', + }), + DEFAULT_VALUES.timestamp, + configNames + ), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.timestampAdjustIndexing', { + defaultMessage: 'Adjust your indexing to use "{field}" as a timestamp.', + values: { field: '@timestamp' }, + }), + ], + }, + }), + tiebreaker: (configNames) => ({ + level: 'critical', + title: i18n.translate('xpack.infra.deprecations.tiebreakerFieldTitle', { + defaultMessage: 'Source configuration field "tiebreaker" is deprecated.', + }), + message: deprecatedFieldMessage( + i18n.translate('xpack.infra.deprecations.tiebreakerFieldName', { + defaultMessage: 'tiebreaker', + }), + DEFAULT_VALUES.tiebreaker, + configNames + ), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.tiebreakerAdjustIndexing', { + defaultMessage: 'Adjust your indexing to use "{field}" as a tiebreaker.', + values: { field: '_doc' }, + }), + ], + }, + }), + host: (configNames) => ({ + level: 'critical', + title: i18n.translate('xpack.infra.deprecations.hostnameFieldTitle', { + defaultMessage: 'Source configuration field "host name" is deprecated.', + }), + message: deprecatedFieldMessage( + i18n.translate('xpack.infra.deprecations.hostnameFieldName', { + defaultMessage: 'host name', + }), + DEFAULT_VALUES.host, + configNames + ), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.hostAdjustIndexing', { + defaultMessage: 'Adjust your indexing to identify hosts using "{field}"', + values: { field: 'host.name' }, + }), + ], + }, + }), + pod: (configNames) => ({ + level: 'critical', + title: i18n.translate('xpack.infra.deprecations.podIdFieldTitle', { + defaultMessage: 'Source configuration field "pod ID" is deprecated.', + }), + message: deprecatedFieldMessage( + i18n.translate('xpack.infra.deprecations.podIdFieldName', { defaultMessage: 'pod ID' }), + DEFAULT_VALUES.pod, + configNames + ), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.podAdjustIndexing', { + defaultMessage: 'Adjust your indexing to identify Kubernetes pods using "{field}"', + values: { field: 'kubernetes.pod.uid' }, + }), + ], + }, + }), + container: (configNames) => ({ + level: 'critical', + title: i18n.translate('xpack.infra.deprecations.containerIdFieldTitle', { + defaultMessage: 'Source configuration field "container ID" is deprecated.', + }), + message: deprecatedFieldMessage( + i18n.translate('xpack.infra.deprecations.containerIdFieldName', { + defaultMessage: 'container ID', + }), + DEFAULT_VALUES.container, + configNames + ), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.containerAdjustIndexing', { + defaultMessage: 'Adjust your indexing to identify Docker containers using "{field}"', + values: { field: 'container.id' }, + }), + ], + }, + }), + }; + +export const configDeprecations: ConfigDeprecationProvider = () => [ + ...Object.keys(FIELD_DEPRECATION_FACTORIES).map( + (key): ConfigDeprecation => + (completeConfig, rootPath, addDeprecation) => { + const configuredValue = get(completeConfig, `xpack.infra.sources.default.fields.${key}`); + if (typeof configuredValue === 'undefined') { + return completeConfig; + } + addDeprecation({ + title: i18n.translate('xpack.infra.deprecations.deprecatedFieldConfigTitle', { + defaultMessage: '"{fieldKey}" is deprecated.', + values: { + fieldKey: key, + }, + }), + message: i18n.translate('xpack.infra.deprecations.deprecatedFieldConfigDescription', { + defaultMessage: + 'Configuring "xpack.infra.sources.default.fields.{fieldKey}" has been deprecated and will be removed in 8.0.0.', + values: { + fieldKey: key, + }, + }), + level: 'warning', + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.infra.deprecations.removeConfigField', { + defaultMessage: + 'Remove "xpack.infra.sources.default.fields.{fieldKey}" from your Kibana configuration.', + values: { fieldKey: key }, + }), + ], + }, + } as Parameters[0]); + + return completeConfig; + } + ), +]; + +export const getInfraDeprecationsFactory = + (sources: InfraSources) => + async ({ savedObjectsClient }: GetDeprecationsContext) => { + const deprecatedFieldsToSourceConfigMap: Map = new Map(); + const sourceConfigurations = await sources.getAllSourceConfigurations(savedObjectsClient); + + for (const { configuration } of sourceConfigurations) { + const { name, fields } = configuration; + for (const [key, defaultValue] of Object.entries(DEFAULT_VALUES)) { + const configuredValue = Reflect.get(fields, key); + if (configuredValue !== defaultValue) { + const affectedConfigNames = deprecatedFieldsToSourceConfigMap.get(key) ?? []; + affectedConfigNames.push(name); + deprecatedFieldsToSourceConfigMap.set(key, affectedConfigNames); + } + } + } + + const deprecations: DeprecationsDetails[] = []; + if (deprecatedFieldsToSourceConfigMap.size > 0) { + for (const [fieldName, affectedConfigNames] of deprecatedFieldsToSourceConfigMap.entries()) { + const deprecationFactory = Reflect.get(FIELD_DEPRECATION_FACTORIES, fieldName); + deprecations.push(deprecationFactory(affectedConfigNames)); + } + } + + return deprecations; + }; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index b77b81cf41ee1..34ac45222bb3f 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -40,6 +40,7 @@ import { UsageCollector } from './usage/usage_collector'; import { createGetLogQueryFields } from './services/log_queries/get_log_query_fields'; import { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; import { RulesService } from './services/rules'; +import { configDeprecations, getInfraDeprecationsFactory } from './deprecations'; export const config: PluginConfigDescriptor = { schema: schema.object({ @@ -68,7 +69,7 @@ export const config: PluginConfigDescriptor = { }) ), }), - deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], + deprecations: configDeprecations, }; export type InfraConfig = TypeOf; @@ -193,6 +194,11 @@ export class InfraServerPlugin implements Plugin { const logEntriesService = new LogEntriesService(); logEntriesService.setup(core, { ...plugins, sources }); + // register deprecated source configuration fields + core.deprecations.registerDeprecations({ + getDeprecations: getInfraDeprecationsFactory(sources), + }); + return { defineInternalSourceConfiguration(sourceId, sourceProperties) { sources.defineInternalSourceConfiguration(sourceId, sourceProperties);