mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Ingest Manager] Ingest setup upgrade (#78081)
* Adding bulk upgrade api
* Addressing comments
* Removing todo
* Changing body field
* Adding helper for getting the bulk install route
* Adding request spec
* Pulling in Johns changes
* Removing test for same package upgraded multiple times
* Adding upgrade to setup route
* Adding setup integration test
* Clean up error handling
* Beginning to add tests
* Failing jest mock tests
* Break up tests & modules for easier testing.
Deal with issue described in https://github.com/facebook/jest/issues/1075#issuecomment-221771095
epm/packages/install has functions a, b, c which are independent but a can also call b and c
function a() {
b();
c();
}
The linked FB issue describes the cause and rationale (Jest works on "module" boundary) but TL;DR: it's easier if you split up your files
Some related links I found during this journey
* https://medium.com/@qjli/how-to-mock-specific-module-function-in-jest-715e39a391f4
* https://stackoverflow.com/questions/52650367/jestjs-how-to-test-function-being-called-inside-another-function
* 50855968 (50855968)
* Add test confirming update error result will throw
* Keep orig error. Add status code in http handler
* Leave error as-is
* Removing accidental code changes. File rename.
* Missed a function when moving to a new file
* Add missing type imports
* Lift .map lambda into named outer function
* Adding additional test
* Fixing type error
Co-authored-by: John Schulz <john.schulz@elastic.co>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
26f2bd204d
commit
e75b36a68d
11 changed files with 426 additions and 191 deletions
|
@ -71,7 +71,7 @@ export interface InstallPackageResponse {
|
|||
response: AssetReference[];
|
||||
}
|
||||
|
||||
export interface IBulkInstallPackageError {
|
||||
export interface IBulkInstallPackageHTTPError {
|
||||
name: string;
|
||||
statusCode: number;
|
||||
error: string | Error;
|
||||
|
@ -86,7 +86,7 @@ export interface BulkInstallPackageInfo {
|
|||
}
|
||||
|
||||
export interface BulkInstallPackagesResponse {
|
||||
response: Array<BulkInstallPackageInfo | IBulkInstallPackageError>;
|
||||
response: Array<BulkInstallPackageInfo | IBulkInstallPackageHTTPError>;
|
||||
}
|
||||
|
||||
export interface BulkInstallPackagesRequest {
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
GetCategoriesResponse,
|
||||
GetPackagesResponse,
|
||||
GetLimitedPackagesResponse,
|
||||
BulkInstallPackageInfo,
|
||||
BulkInstallPackagesResponse,
|
||||
IBulkInstallPackageHTTPError,
|
||||
} from '../../../common';
|
||||
import {
|
||||
GetCategoriesRequestSchema,
|
||||
|
@ -26,21 +28,21 @@ import {
|
|||
BulkUpgradePackagesFromRegistryRequestSchema,
|
||||
} from '../../types';
|
||||
import {
|
||||
BulkInstallResponse,
|
||||
bulkInstallPackages,
|
||||
getCategories,
|
||||
getPackages,
|
||||
getFile,
|
||||
getPackageInfo,
|
||||
handleInstallPackageFailure,
|
||||
installPackage,
|
||||
isBulkInstallError,
|
||||
removeInstallation,
|
||||
getLimitedPackages,
|
||||
getInstallationObject,
|
||||
} from '../../services/epm/packages';
|
||||
import { defaultIngestErrorHandler } from '../../errors';
|
||||
import { defaultIngestErrorHandler, ingestErrorToResponseOptions } from '../../errors';
|
||||
import { splitPkgKey } from '../../services/epm/registry';
|
||||
import {
|
||||
handleInstallPackageFailure,
|
||||
bulkInstallPackages,
|
||||
} from '../../services/epm/packages/install';
|
||||
|
||||
export const getCategoriesHandler: RequestHandler<
|
||||
undefined,
|
||||
|
@ -171,6 +173,21 @@ export const installPackageFromRegistryHandler: RequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
const bulkInstallServiceResponseToHttpEntry = (
|
||||
result: BulkInstallResponse
|
||||
): BulkInstallPackageInfo | IBulkInstallPackageHTTPError => {
|
||||
if (isBulkInstallError(result)) {
|
||||
const { statusCode, body } = ingestErrorToResponseOptions(result.error);
|
||||
return {
|
||||
name: result.name,
|
||||
statusCode,
|
||||
error: body.message,
|
||||
};
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export const bulkInstallPackagesFromRegistryHandler: RequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
|
@ -178,13 +195,14 @@ export const bulkInstallPackagesFromRegistryHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const res = await bulkInstallPackages({
|
||||
const bulkInstalledResponses = await bulkInstallPackages({
|
||||
savedObjectsClient,
|
||||
callCluster,
|
||||
packagesToUpgrade: request.body.packages,
|
||||
});
|
||||
const payload = bulkInstalledResponses.map(bulkInstallServiceResponseToHttpEntry);
|
||||
const body: BulkInstallPackagesResponse = {
|
||||
response: res,
|
||||
response: payload,
|
||||
};
|
||||
return response.ok({ body });
|
||||
};
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { CallESAsCurrentUser } from '../../../types';
|
||||
import * as Registry from '../registry';
|
||||
import { getInstallationObject } from './index';
|
||||
import { BulkInstallResponse, IBulkInstallPackageError, upgradePackage } from './install';
|
||||
|
||||
interface BulkInstallPackagesParams {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
packagesToUpgrade: string[];
|
||||
callCluster: CallESAsCurrentUser;
|
||||
}
|
||||
|
||||
export async function bulkInstallPackages({
|
||||
savedObjectsClient,
|
||||
packagesToUpgrade,
|
||||
callCluster,
|
||||
}: BulkInstallPackagesParams): Promise<BulkInstallResponse[]> {
|
||||
const installedAndLatestPromises = packagesToUpgrade.map((pkgToUpgrade) =>
|
||||
Promise.all([
|
||||
getInstallationObject({ savedObjectsClient, pkgName: pkgToUpgrade }),
|
||||
Registry.fetchFindLatestPackage(pkgToUpgrade),
|
||||
])
|
||||
);
|
||||
const installedAndLatestResults = await Promise.allSettled(installedAndLatestPromises);
|
||||
const installResponsePromises = installedAndLatestResults.map(async (result, index) => {
|
||||
const pkgToUpgrade = packagesToUpgrade[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
const [installedPkg, latestPkg] = result.value;
|
||||
return upgradePackage({
|
||||
savedObjectsClient,
|
||||
callCluster,
|
||||
installedPkg,
|
||||
latestPkg,
|
||||
pkgToUpgrade,
|
||||
});
|
||||
} else {
|
||||
return { name: pkgToUpgrade, error: result.reason };
|
||||
}
|
||||
});
|
||||
const installResults = await Promise.allSettled(installResponsePromises);
|
||||
const installResponses = installResults.map((result, index) => {
|
||||
const pkgToUpgrade = packagesToUpgrade[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
} else {
|
||||
return { name: pkgToUpgrade, error: result.reason };
|
||||
}
|
||||
});
|
||||
|
||||
return installResponses;
|
||||
}
|
||||
|
||||
export function isBulkInstallError(test: any): test is IBulkInstallPackageError {
|
||||
return 'error' in test && test.error instanceof Error;
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types';
|
||||
import { SavedObject, SavedObjectsClientContract } from 'src/core/server';
|
||||
|
||||
jest.mock('./install');
|
||||
jest.mock('./bulk_install_packages');
|
||||
jest.mock('./get');
|
||||
|
||||
import { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages';
|
||||
const { ensureInstalledDefaultPackages } = jest.requireActual('./install');
|
||||
const { isBulkInstallError: actualIsBulkInstallError } = jest.requireActual(
|
||||
'./bulk_install_packages'
|
||||
);
|
||||
import { getInstallation } from './get';
|
||||
import { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { appContextService } from '../../app_context';
|
||||
import { createAppContextStartContractMock } from '../../../mocks';
|
||||
|
||||
// if we add this assertion, TS will type check the return value
|
||||
// and the editor will also know about .mockImplementation, .mock.calls, etc
|
||||
const mockedBulkInstallPackages = bulkInstallPackages as jest.MockedFunction<
|
||||
typeof bulkInstallPackages
|
||||
>;
|
||||
const mockedIsBulkInstallError = isBulkInstallError as jest.MockedFunction<
|
||||
typeof isBulkInstallError
|
||||
>;
|
||||
const mockedGetInstallation = getInstallation as jest.MockedFunction<typeof getInstallation>;
|
||||
|
||||
// I was unable to get the actual implementation set in the `jest.mock()` call at the top to work
|
||||
// so this will set the `isBulkInstallError` function back to the actual implementation
|
||||
mockedIsBulkInstallError.mockImplementation(actualIsBulkInstallError);
|
||||
|
||||
const mockInstallation: SavedObject<Installation> = {
|
||||
id: 'test-pkg',
|
||||
references: [],
|
||||
type: 'epm-packages',
|
||||
attributes: {
|
||||
id: 'test-pkg',
|
||||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }],
|
||||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
|
||||
es_index_patterns: { pattern: 'pattern-name' },
|
||||
name: 'test package',
|
||||
version: '1.0.0',
|
||||
install_status: 'installed',
|
||||
install_version: '1.0.0',
|
||||
install_started_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
describe('ensureInstalledDefaultPackages', () => {
|
||||
let soClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
beforeEach(async () => {
|
||||
soClient = savedObjectsClientMock.create();
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
});
|
||||
afterEach(async () => {
|
||||
appContextService.stop();
|
||||
});
|
||||
it('should return an array of Installation objects when successful', async () => {
|
||||
mockedGetInstallation.mockImplementation(async () => {
|
||||
return mockInstallation.attributes;
|
||||
});
|
||||
mockedBulkInstallPackages.mockImplementationOnce(async function () {
|
||||
return [
|
||||
{
|
||||
name: mockInstallation.attributes.name,
|
||||
assets: [],
|
||||
newVersion: '',
|
||||
oldVersion: '',
|
||||
statusCode: 200,
|
||||
},
|
||||
];
|
||||
});
|
||||
const resp = await ensureInstalledDefaultPackages(soClient, jest.fn());
|
||||
expect(resp).toEqual([mockInstallation.attributes]);
|
||||
});
|
||||
it('should throw the first Error it finds', async () => {
|
||||
class SomeCustomError extends Error {}
|
||||
mockedGetInstallation.mockImplementation(async () => {
|
||||
return mockInstallation.attributes;
|
||||
});
|
||||
mockedBulkInstallPackages.mockImplementationOnce(async function () {
|
||||
return [
|
||||
{
|
||||
name: 'success one',
|
||||
assets: [],
|
||||
newVersion: '',
|
||||
oldVersion: '',
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: 'success two',
|
||||
assets: [],
|
||||
newVersion: '',
|
||||
oldVersion: '',
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: 'failure one',
|
||||
error: new SomeCustomError('abc 123'),
|
||||
},
|
||||
{
|
||||
name: 'success three',
|
||||
assets: [],
|
||||
newVersion: '',
|
||||
oldVersion: '',
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: 'failure two',
|
||||
error: new Error('zzz'),
|
||||
},
|
||||
];
|
||||
});
|
||||
const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn());
|
||||
expect.assertions(2);
|
||||
expect(installPromise).rejects.toThrow(SomeCustomError);
|
||||
expect(installPromise).rejects.toThrow('abc 123');
|
||||
});
|
||||
it('should throw an error when get installation returns undefined', async () => {
|
||||
mockedGetInstallation.mockImplementation(async () => {
|
||||
return undefined;
|
||||
});
|
||||
mockedBulkInstallPackages.mockImplementationOnce(async function () {
|
||||
return [
|
||||
{
|
||||
name: 'undefined package',
|
||||
assets: [],
|
||||
newVersion: '',
|
||||
oldVersion: '',
|
||||
statusCode: 200,
|
||||
},
|
||||
];
|
||||
});
|
||||
const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn());
|
||||
expect.assertions(1);
|
||||
expect(installPromise).rejects.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { SavedObject } from 'src/core/server';
|
||||
import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types';
|
||||
import { getInstallType } from './install';
|
||||
|
||||
const mockInstallation: SavedObject<Installation> = {
|
||||
id: 'test-pkg',
|
||||
references: [],
|
||||
type: 'epm-packages',
|
||||
attributes: {
|
||||
id: 'test-pkg',
|
||||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }],
|
||||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
|
||||
es_index_patterns: { pattern: 'pattern-name' },
|
||||
name: 'test packagek',
|
||||
version: '1.0.0',
|
||||
install_status: 'installed',
|
||||
install_version: '1.0.0',
|
||||
install_started_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
const mockInstallationUpdateFail: SavedObject<Installation> = {
|
||||
id: 'test-pkg',
|
||||
references: [],
|
||||
type: 'epm-packages',
|
||||
attributes: {
|
||||
id: 'test-pkg',
|
||||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }],
|
||||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
|
||||
es_index_patterns: { pattern: 'pattern-name' },
|
||||
name: 'test packagek',
|
||||
version: '1.0.0',
|
||||
install_status: 'installing',
|
||||
install_version: '1.0.1',
|
||||
install_started_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
describe('getInstallType', () => {
|
||||
it('should return correct type when installing and no other version is currently installed', () => {
|
||||
const installTypeInstall = getInstallType({ pkgVersion: '1.0.0', installedPkg: undefined });
|
||||
expect(installTypeInstall).toBe('install');
|
||||
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'update').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'reinstall').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'reupdate').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'rollback').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when installing the same version', () => {
|
||||
const installTypeReinstall = getInstallType({
|
||||
pkgVersion: '1.0.0',
|
||||
installedPkg: mockInstallation,
|
||||
});
|
||||
expect(installTypeReinstall).toBe('reinstall');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeReinstall === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when moving from one version to another', () => {
|
||||
const installTypeUpdate = getInstallType({
|
||||
pkgVersion: '1.0.1',
|
||||
installedPkg: mockInstallation,
|
||||
});
|
||||
expect(installTypeUpdate).toBe('update');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeUpdate === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when update fails and trys again', () => {
|
||||
const installTypeReupdate = getInstallType({
|
||||
pkgVersion: '1.0.1',
|
||||
installedPkg: mockInstallationUpdateFail,
|
||||
});
|
||||
expect(installTypeReupdate).toBe('reupdate');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeReupdate === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when attempting to rollback from a failed update', () => {
|
||||
const installTypeRollback = getInstallType({
|
||||
pkgVersion: '1.0.0',
|
||||
installedPkg: mockInstallationUpdateFail,
|
||||
});
|
||||
expect(installTypeRollback).toBe('rollback');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeRollback === 'install').toBe(false);
|
||||
});
|
||||
});
|
|
@ -12,6 +12,8 @@ import {
|
|||
InstallationStatus,
|
||||
KibanaAssetType,
|
||||
} from '../../../types';
|
||||
|
||||
export { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages';
|
||||
export {
|
||||
getCategories,
|
||||
getFile,
|
||||
|
@ -23,7 +25,13 @@ export {
|
|||
SearchParams,
|
||||
} from './get';
|
||||
|
||||
export { installPackage, ensureInstalledPackage } from './install';
|
||||
export {
|
||||
BulkInstallResponse,
|
||||
handleInstallPackageFailure,
|
||||
installPackage,
|
||||
IBulkInstallPackageError,
|
||||
ensureInstalledPackage,
|
||||
} from './install';
|
||||
export { removeInstallation } from './remove';
|
||||
|
||||
type RequiredPackage = 'system' | 'endpoint';
|
||||
|
|
|
@ -1,103 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types';
|
||||
import { SavedObject } from 'src/core/server';
|
||||
import { getInstallType } from './install';
|
||||
|
||||
const mockInstallation: SavedObject<Installation> = {
|
||||
id: 'test-pkg',
|
||||
references: [],
|
||||
type: 'epm-packages',
|
||||
attributes: {
|
||||
id: 'test-pkg',
|
||||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }],
|
||||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
|
||||
es_index_patterns: { pattern: 'pattern-name' },
|
||||
name: 'test packagek',
|
||||
version: '1.0.0',
|
||||
install_status: 'installed',
|
||||
install_version: '1.0.0',
|
||||
install_started_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
const mockInstallationUpdateFail: SavedObject<Installation> = {
|
||||
id: 'test-pkg',
|
||||
references: [],
|
||||
type: 'epm-packages',
|
||||
attributes: {
|
||||
id: 'test-pkg',
|
||||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }],
|
||||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }],
|
||||
es_index_patterns: { pattern: 'pattern-name' },
|
||||
name: 'test packagek',
|
||||
version: '1.0.0',
|
||||
install_status: 'installing',
|
||||
install_version: '1.0.1',
|
||||
install_started_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
describe('install', () => {
|
||||
describe('getInstallType', () => {
|
||||
it('should return correct type when installing and no other version is currently installed', () => {
|
||||
const installTypeInstall = getInstallType({ pkgVersion: '1.0.0', installedPkg: undefined });
|
||||
expect(installTypeInstall).toBe('install');
|
||||
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'update').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'reinstall').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'reupdate').toBe(false);
|
||||
// @ts-expect-error can only be 'install' if no installedPkg given
|
||||
expect(installTypeInstall === 'rollback').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when installing the same version', () => {
|
||||
const installTypeReinstall = getInstallType({
|
||||
pkgVersion: '1.0.0',
|
||||
installedPkg: mockInstallation,
|
||||
});
|
||||
expect(installTypeReinstall).toBe('reinstall');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeReinstall === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when moving from one version to another', () => {
|
||||
const installTypeUpdate = getInstallType({
|
||||
pkgVersion: '1.0.1',
|
||||
installedPkg: mockInstallation,
|
||||
});
|
||||
expect(installTypeUpdate).toBe('update');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeUpdate === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when update fails and trys again', () => {
|
||||
const installTypeReupdate = getInstallType({
|
||||
pkgVersion: '1.0.1',
|
||||
installedPkg: mockInstallationUpdateFail,
|
||||
});
|
||||
expect(installTypeReupdate).toBe('reupdate');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeReupdate === 'install').toBe(false);
|
||||
});
|
||||
|
||||
it('should return correct type when attempting to rollback from a failed update', () => {
|
||||
const installTypeRollback = getInstallType({
|
||||
pkgVersion: '1.0.0',
|
||||
installedPkg: mockInstallationUpdateFail,
|
||||
});
|
||||
expect(installTypeRollback).toBe('rollback');
|
||||
|
||||
// @ts-expect-error cannot be 'install' if given installedPkg
|
||||
expect(installTypeRollback === 'install').toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,7 +8,7 @@ import { SavedObject, SavedObjectsClientContract } from 'src/core/server';
|
|||
import semver from 'semver';
|
||||
import Boom from 'boom';
|
||||
import { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { BulkInstallPackageInfo, IBulkInstallPackageError } from '../../../../common';
|
||||
import { BulkInstallPackageInfo } from '../../../../common';
|
||||
import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants';
|
||||
import {
|
||||
AssetReference,
|
||||
|
@ -23,7 +23,13 @@ import {
|
|||
} from '../../../types';
|
||||
import { installIndexPatterns } from '../kibana/index_pattern/install';
|
||||
import * as Registry from '../registry';
|
||||
import { getInstallation, getInstallationObject, isRequiredPackage } from './index';
|
||||
import {
|
||||
getInstallation,
|
||||
getInstallationObject,
|
||||
isRequiredPackage,
|
||||
bulkInstallPackages,
|
||||
isBulkInstallError,
|
||||
} from './index';
|
||||
import { installTemplates } from '../elasticsearch/template/install';
|
||||
import { generateESIndexPatterns } from '../elasticsearch/template/template';
|
||||
import { installPipelines, deletePreviousPipelines } from '../elasticsearch/ingest_pipeline/';
|
||||
|
@ -36,11 +42,7 @@ import {
|
|||
} from '../kibana/assets/install';
|
||||
import { updateCurrentWriteIndices } from '../elasticsearch/template/template';
|
||||
import { deleteKibanaSavedObjectsAssets, removeInstallation } from './remove';
|
||||
import {
|
||||
IngestManagerError,
|
||||
PackageOutdatedError,
|
||||
ingestErrorToResponseOptions,
|
||||
} from '../../../errors';
|
||||
import { IngestManagerError, PackageOutdatedError } from '../../../errors';
|
||||
import { getPackageSavedObjects } from './get';
|
||||
import { installTransformForDataset } from '../elasticsearch/transform/install';
|
||||
import { appContextService } from '../../app_context';
|
||||
|
@ -68,17 +70,27 @@ export async function ensureInstalledDefaultPackages(
|
|||
callCluster: CallESAsCurrentUser
|
||||
): Promise<Installation[]> {
|
||||
const installations = [];
|
||||
for (const pkgName in DefaultPackages) {
|
||||
if (!DefaultPackages.hasOwnProperty(pkgName)) continue;
|
||||
const installation = ensureInstalledPackage({
|
||||
savedObjectsClient,
|
||||
pkgName,
|
||||
callCluster,
|
||||
});
|
||||
installations.push(installation);
|
||||
const bulkResponse = await bulkInstallPackages({
|
||||
savedObjectsClient,
|
||||
packagesToUpgrade: Object.values(DefaultPackages),
|
||||
callCluster,
|
||||
});
|
||||
|
||||
for (const resp of bulkResponse) {
|
||||
if (isBulkInstallError(resp)) {
|
||||
throw resp.error;
|
||||
} else {
|
||||
installations.push(getInstallation({ savedObjectsClient, pkgName: resp.name }));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(installations);
|
||||
const retrievedInstallations = await Promise.all(installations);
|
||||
return retrievedInstallations.map((installation, index) => {
|
||||
if (!installation) {
|
||||
throw new Error(`could not get installation ${bulkResponse[index].name}`);
|
||||
}
|
||||
return installation;
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensureInstalledPackage(options: {
|
||||
|
@ -154,21 +166,11 @@ export async function handleInstallPackageFailure({
|
|||
}
|
||||
}
|
||||
|
||||
type BulkInstallResponse = BulkInstallPackageInfo | IBulkInstallPackageError;
|
||||
function bulkInstallErrorToOptions({
|
||||
pkgToUpgrade,
|
||||
error,
|
||||
}: {
|
||||
pkgToUpgrade: string;
|
||||
export interface IBulkInstallPackageError {
|
||||
name: string;
|
||||
error: Error;
|
||||
}): IBulkInstallPackageError {
|
||||
const { statusCode, body } = ingestErrorToResponseOptions(error);
|
||||
return {
|
||||
name: pkgToUpgrade,
|
||||
statusCode,
|
||||
error: body.message,
|
||||
};
|
||||
}
|
||||
export type BulkInstallResponse = BulkInstallPackageInfo | IBulkInstallPackageError;
|
||||
|
||||
interface UpgradePackageParams {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
|
@ -177,7 +179,7 @@ interface UpgradePackageParams {
|
|||
latestPkg: UnwrapPromise<ReturnType<typeof Registry.fetchFindLatestPackage>>;
|
||||
pkgToUpgrade: string;
|
||||
}
|
||||
async function upgradePackage({
|
||||
export async function upgradePackage({
|
||||
savedObjectsClient,
|
||||
callCluster,
|
||||
installedPkg,
|
||||
|
@ -207,7 +209,7 @@ async function upgradePackage({
|
|||
installedPkg,
|
||||
callCluster,
|
||||
});
|
||||
return bulkInstallErrorToOptions({ pkgToUpgrade, error: installFailed });
|
||||
return { name: pkgToUpgrade, error: installFailed };
|
||||
}
|
||||
} else {
|
||||
// package was already at the latest version
|
||||
|
@ -223,51 +225,6 @@ async function upgradePackage({
|
|||
}
|
||||
}
|
||||
|
||||
interface BulkInstallPackagesParams {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
packagesToUpgrade: string[];
|
||||
callCluster: CallESAsCurrentUser;
|
||||
}
|
||||
export async function bulkInstallPackages({
|
||||
savedObjectsClient,
|
||||
packagesToUpgrade,
|
||||
callCluster,
|
||||
}: BulkInstallPackagesParams): Promise<BulkInstallResponse[]> {
|
||||
const installedAndLatestPromises = packagesToUpgrade.map((pkgToUpgrade) =>
|
||||
Promise.all([
|
||||
getInstallationObject({ savedObjectsClient, pkgName: pkgToUpgrade }),
|
||||
Registry.fetchFindLatestPackage(pkgToUpgrade),
|
||||
])
|
||||
);
|
||||
const installedAndLatestResults = await Promise.allSettled(installedAndLatestPromises);
|
||||
const installResponsePromises = installedAndLatestResults.map(async (result, index) => {
|
||||
const pkgToUpgrade = packagesToUpgrade[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
const [installedPkg, latestPkg] = result.value;
|
||||
return upgradePackage({
|
||||
savedObjectsClient,
|
||||
callCluster,
|
||||
installedPkg,
|
||||
latestPkg,
|
||||
pkgToUpgrade,
|
||||
});
|
||||
} else {
|
||||
return bulkInstallErrorToOptions({ pkgToUpgrade, error: result.reason });
|
||||
}
|
||||
});
|
||||
const installResults = await Promise.allSettled(installResponsePromises);
|
||||
const installResponses = installResults.map((result, index) => {
|
||||
const pkgToUpgrade = packagesToUpgrade[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
} else {
|
||||
return bulkInstallErrorToOptions({ pkgToUpgrade, error: result.reason });
|
||||
}
|
||||
});
|
||||
|
||||
return installResponses;
|
||||
}
|
||||
|
||||
interface InstallPackageParams {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
pkgkey: string;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { skipIfNoDockerRegistry } from '../../helpers';
|
|||
import {
|
||||
BulkInstallPackageInfo,
|
||||
BulkInstallPackagesResponse,
|
||||
IBulkInstallPackageError,
|
||||
IBulkInstallPackageHTTPError,
|
||||
} from '../../../../plugins/ingest_manager/common';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
|
@ -68,7 +68,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
expect(entry.oldVersion).equal('0.1.0');
|
||||
expect(entry.newVersion).equal('0.3.0');
|
||||
|
||||
const err = body.response[1] as IBulkInstallPackageError;
|
||||
const err = body.response[1] as IBulkInstallPackageHTTPError;
|
||||
expect(err.statusCode).equal(404);
|
||||
expect(body.response[1].name).equal('blahblah');
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
export default function loadTests({ loadTestFile }) {
|
||||
describe('EPM Endpoints', () => {
|
||||
loadTestFile(require.resolve('./list'));
|
||||
loadTestFile(require.resolve('./setup'));
|
||||
loadTestFile(require.resolve('./get'));
|
||||
loadTestFile(require.resolve('./file'));
|
||||
//loadTestFile(require.resolve('./template'));
|
||||
|
|
48
x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts
Normal file
48
x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { GetInfoResponse, Installed } from '../../../../plugins/ingest_manager/common';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
const { getService } = providerContext;
|
||||
const supertest = getService('supertest');
|
||||
const log = getService('log');
|
||||
|
||||
describe('setup api', async () => {
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
describe('setup performs upgrades', async () => {
|
||||
const oldEndpointVersion = '0.13.0';
|
||||
beforeEach(async () => {
|
||||
await supertest
|
||||
.post(`/api/ingest_manager/epm/packages/endpoint-${oldEndpointVersion}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({ force: true })
|
||||
.expect(200);
|
||||
});
|
||||
it('upgrades the endpoint package from 0.13.0 to the latest version available', async function () {
|
||||
let { body }: { body: GetInfoResponse } = await supertest
|
||||
.get(`/api/ingest_manager/epm/packages/endpoint-${oldEndpointVersion}`)
|
||||
.expect(200);
|
||||
const latestEndpointVersion = body.response.latestVersion;
|
||||
log.info(`Endpoint package latest version: ${latestEndpointVersion}`);
|
||||
// make sure we're actually doing an upgrade
|
||||
expect(latestEndpointVersion).not.eql(oldEndpointVersion);
|
||||
await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxxx').expect(200);
|
||||
|
||||
({ body } = await supertest
|
||||
.get(`/api/ingest_manager/epm/packages/endpoint-${latestEndpointVersion}`)
|
||||
.expect(200));
|
||||
expect(body.response).to.have.property('savedObject');
|
||||
expect((body.response as Installed).savedObject.attributes.install_version).to.eql(
|
||||
latestEndpointVersion
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue