mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[uiSettings] always use the latest config document to create the new one (#159649)
## Summary Fix https://github.com/elastic/kibana/issues/159646 Fix the config creation-from-previous-one logic by always using the latest config for the new version's creation ## Release Note Fix a bug that could cause old Kibana deployments to loose their uiSettings after an upgrade --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
9ea864cc05
commit
83abc6e3c0
8 changed files with 100 additions and 17 deletions
|
@ -9,6 +9,7 @@
|
||||||
import { Subject, Observable, firstValueFrom, of } from 'rxjs';
|
import { Subject, Observable, firstValueFrom, of } from 'rxjs';
|
||||||
import { filter, take, switchMap } from 'rxjs/operators';
|
import { filter, take, switchMap } from 'rxjs/operators';
|
||||||
import type { Logger } from '@kbn/logging';
|
import type { Logger } from '@kbn/logging';
|
||||||
|
import { stripVersionQualifier } from '@kbn/std';
|
||||||
import type { ServiceStatus } from '@kbn/core-status-common';
|
import type { ServiceStatus } from '@kbn/core-status-common';
|
||||||
import type { CoreContext, CoreService } from '@kbn/core-base-server-internal';
|
import type { CoreContext, CoreService } from '@kbn/core-base-server-internal';
|
||||||
import type { DocLinksServiceStart } from '@kbn/core-doc-links-server';
|
import type { DocLinksServiceStart } from '@kbn/core-doc-links-server';
|
||||||
|
@ -106,9 +107,7 @@ export class SavedObjectsService
|
||||||
|
|
||||||
constructor(private readonly coreContext: CoreContext) {
|
constructor(private readonly coreContext: CoreContext) {
|
||||||
this.logger = coreContext.logger.get('savedobjects-service');
|
this.logger = coreContext.logger.get('savedobjects-service');
|
||||||
this.kibanaVersion = SavedObjectsService.stripVersionQualifier(
|
this.kibanaVersion = stripVersionQualifier(this.coreContext.env.packageInfo.version);
|
||||||
this.coreContext.env.packageInfo.version
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setup(setupDeps: SavedObjectsSetupDeps): Promise<InternalSavedObjectsServiceSetup> {
|
public async setup(setupDeps: SavedObjectsSetupDeps): Promise<InternalSavedObjectsServiceSetup> {
|
||||||
|
@ -384,12 +383,4 @@ export class SavedObjectsService
|
||||||
nodeRoles: nodeInfo.roles,
|
nodeRoles: nodeInfo.roles,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha)
|
|
||||||
* to regular semver (x.y.z).
|
|
||||||
*/
|
|
||||||
private static stripVersionQualifier(version: string) {
|
|
||||||
return version.split('-')[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
"@kbn/utils",
|
"@kbn/utils",
|
||||||
"@kbn/core-http-router-server-internal",
|
"@kbn/core-http-router-server-internal",
|
||||||
"@kbn/logging-mocks",
|
"@kbn/logging-mocks",
|
||||||
|
"@kbn/std",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*",
|
"target/**/*",
|
||||||
|
|
|
@ -46,6 +46,59 @@ describe('getUpgradeableConfig', () => {
|
||||||
expect(result).toEqual(savedConfig);
|
expect(result).toEqual(savedConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the latest config when multiple are found', async () => {
|
||||||
|
const savedObjectsClient = savedObjectsClientMock.create();
|
||||||
|
savedObjectsClient.find.mockResolvedValue({
|
||||||
|
saved_objects: [
|
||||||
|
{ id: '7.2.0', attributes: 'foo' },
|
||||||
|
{ id: '7.3.0', attributes: 'foo' },
|
||||||
|
],
|
||||||
|
} as SavedObjectsFindResponse);
|
||||||
|
|
||||||
|
const result = await getUpgradeableConfig({
|
||||||
|
savedObjectsClient,
|
||||||
|
version: '7.5.0',
|
||||||
|
type: 'config',
|
||||||
|
});
|
||||||
|
expect(result!.id).toBe('7.3.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the latest config when multiple are found with rc qualifier', async () => {
|
||||||
|
const savedObjectsClient = savedObjectsClientMock.create();
|
||||||
|
savedObjectsClient.find.mockResolvedValue({
|
||||||
|
saved_objects: [
|
||||||
|
{ id: '7.2.0', attributes: 'foo' },
|
||||||
|
{ id: '7.3.0', attributes: 'foo' },
|
||||||
|
{ id: '7.5.0-rc1', attributes: 'foo' },
|
||||||
|
],
|
||||||
|
} as SavedObjectsFindResponse);
|
||||||
|
|
||||||
|
const result = await getUpgradeableConfig({
|
||||||
|
savedObjectsClient,
|
||||||
|
version: '7.5.0',
|
||||||
|
type: 'config',
|
||||||
|
});
|
||||||
|
expect(result!.id).toBe('7.5.0-rc1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores documents with malformed ids', async () => {
|
||||||
|
const savedObjectsClient = savedObjectsClientMock.create();
|
||||||
|
savedObjectsClient.find.mockResolvedValue({
|
||||||
|
saved_objects: [
|
||||||
|
{ id: 'not-a-semver', attributes: 'foo' },
|
||||||
|
{ id: '7.2.0', attributes: 'foo' },
|
||||||
|
{ id: '7.3.0', attributes: 'foo' },
|
||||||
|
],
|
||||||
|
} as SavedObjectsFindResponse);
|
||||||
|
|
||||||
|
const result = await getUpgradeableConfig({
|
||||||
|
savedObjectsClient,
|
||||||
|
version: '7.5.0',
|
||||||
|
type: 'config',
|
||||||
|
});
|
||||||
|
expect(result!.id).toBe('7.3.0');
|
||||||
|
});
|
||||||
|
|
||||||
it('finds saved config with RC version === Kibana version', async () => {
|
it('finds saved config with RC version === Kibana version', async () => {
|
||||||
const savedConfig = { id: '7.5.0-rc1', attributes: 'foo' };
|
const savedConfig = { id: '7.5.0-rc1', attributes: 'foo' };
|
||||||
const savedObjectsClient = savedObjectsClientMock.create();
|
const savedObjectsClient = savedObjectsClientMock.create();
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
import Semver from 'semver';
|
||||||
|
import type {
|
||||||
|
SavedObjectsClientContract,
|
||||||
|
SavedObjectsFindResult,
|
||||||
|
} from '@kbn/core-saved-objects-api-server';
|
||||||
import type { ConfigAttributes } from '../saved_objects';
|
import type { ConfigAttributes } from '../saved_objects';
|
||||||
import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
|
import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
|
||||||
|
|
||||||
|
@ -47,11 +51,26 @@ export async function getUpgradeableConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
// try to find a config that we can upgrade
|
// try to find a config that we can upgrade
|
||||||
const findResult = savedConfigs.find((savedConfig) =>
|
const matchingResults = savedConfigs.filter((savedConfig) =>
|
||||||
isConfigVersionUpgradeable(savedConfig.id, version)
|
isConfigVersionUpgradeable(savedConfig.id, version)
|
||||||
);
|
);
|
||||||
if (findResult) {
|
const mostRecentConfig = getMostRecentConfig(matchingResults);
|
||||||
return { id: findResult.id, attributes: findResult.attributes };
|
if (mostRecentConfig) {
|
||||||
|
return { id: mostRecentConfig.id, attributes: mostRecentConfig.attributes };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMostRecentConfig = (
|
||||||
|
results: Array<SavedObjectsFindResult<UpgradeableConfigAttributes>>
|
||||||
|
): SavedObjectsFindResult<UpgradeableConfigAttributes> | undefined => {
|
||||||
|
return results.reduce<SavedObjectsFindResult<UpgradeableConfigAttributes> | undefined>(
|
||||||
|
(mostRecent, current) => {
|
||||||
|
if (!mostRecent) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return Semver.gt(mostRecent.id, current.id) ? mostRecent : current;
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { firstValueFrom, Observable } from 'rxjs';
|
||||||
import { mapToObject } from '@kbn/std';
|
import { mapToObject } from '@kbn/std';
|
||||||
|
|
||||||
import type { Logger } from '@kbn/logging';
|
import type { Logger } from '@kbn/logging';
|
||||||
|
import { stripVersionQualifier } from '@kbn/std';
|
||||||
import type { CoreContext, CoreService } from '@kbn/core-base-server-internal';
|
import type { CoreContext, CoreService } from '@kbn/core-base-server-internal';
|
||||||
import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal';
|
import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal';
|
||||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||||
|
@ -32,6 +33,7 @@ export interface SetupDeps {
|
||||||
http: InternalHttpServiceSetup;
|
http: InternalHttpServiceSetup;
|
||||||
savedObjects: InternalSavedObjectsServiceSetup;
|
savedObjects: InternalSavedObjectsServiceSetup;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientType<T> = T extends 'global'
|
type ClientType<T> = T extends 'global'
|
||||||
? UiSettingsGlobalClient
|
? UiSettingsGlobalClient
|
||||||
: T extends 'namespace'
|
: T extends 'namespace'
|
||||||
|
@ -109,7 +111,7 @@ export class UiSettingsService
|
||||||
const isNamespaceScope = scope === 'namespace';
|
const isNamespaceScope = scope === 'namespace';
|
||||||
const options = {
|
const options = {
|
||||||
type: (isNamespaceScope ? 'config' : 'config-global') as 'config' | 'config-global',
|
type: (isNamespaceScope ? 'config' : 'config-global') as 'config' | 'config-global',
|
||||||
id: version,
|
id: stripVersionQualifier(version),
|
||||||
buildNum,
|
buildNum,
|
||||||
savedObjectsClient,
|
savedObjectsClient,
|
||||||
defaults: isNamespaceScope
|
defaults: isNamespaceScope
|
||||||
|
|
|
@ -29,3 +29,4 @@ export {
|
||||||
} from './src/iteration';
|
} from './src/iteration';
|
||||||
export { ensureDeepObject } from './src/ensure_deep_object';
|
export { ensureDeepObject } from './src/ensure_deep_object';
|
||||||
export { Semaphore } from './src/semaphore';
|
export { Semaphore } from './src/semaphore';
|
||||||
|
export { stripVersionQualifier } from './src/strip_version_qualifier';
|
||||||
|
|
15
packages/kbn-std/src/strip_version_qualifier.ts
Normal file
15
packages/kbn-std/src/strip_version_qualifier.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha)
|
||||||
|
* to regular semver (x.y.z).
|
||||||
|
*/
|
||||||
|
export function stripVersionQualifier(version: string): string {
|
||||||
|
return version.split('-')[0];
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { stripVersionQualifier } from '@kbn/std';
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
export default function enterSpaceFunctionalTests({
|
export default function enterSpaceFunctionalTests({
|
||||||
|
@ -32,7 +33,7 @@ export default function enterSpaceFunctionalTests({
|
||||||
{ space: 'another-space' }
|
{ space: 'another-space' }
|
||||||
);
|
);
|
||||||
const config = await kibanaServer.savedObjects.get({
|
const config = await kibanaServer.savedObjects.get({
|
||||||
id: await kibanaServer.version.get(),
|
id: stripVersionQualifier(await kibanaServer.version.get()),
|
||||||
type: 'config',
|
type: 'config',
|
||||||
});
|
});
|
||||||
await kibanaServer.savedObjects.update({
|
await kibanaServer.savedObjects.update({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue