[Fleet] Report installing status while package install is in progress (#111875) (#114296)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-10-07 13:20:58 -04:00 committed by GitHub
parent f01ad6c371
commit 8e9c74b5d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 13 deletions

View file

@ -53,5 +53,7 @@ export const monitoringTypes = Object.values(dataTypes);
export const installationStatuses = {
Installed: 'installed',
Installing: 'installing',
InstallFailed: 'install_failed',
NotInstalled: 'not_installed',
} as const;

View file

@ -181,6 +181,8 @@
"type": "string",
"enum": [
"installed",
"installing",
"install_failed",
"not_installed"
]
},

View file

@ -119,6 +119,8 @@ paths:
type: string
enum:
- installed
- installing
- install_failed
- not_installed
savedObject:
type: string

View file

@ -17,6 +17,8 @@ get:
type: string
enum:
- installed
- installing
- install_failed
- not_installed
savedObject:
type: string

View file

@ -45,7 +45,7 @@ export interface DefaultPackagesInstallationError {
export type InstallType = 'reinstall' | 'reupdate' | 'rollback' | 'update' | 'install' | 'unknown';
export type InstallSource = 'registry' | 'upload';
export type EpmPackageInstallStatus = 'installed' | 'installing';
export type EpmPackageInstallStatus = 'installed' | 'installing' | 'install_failed';
export type DetailViewPanelName = 'overview' | 'policies' | 'assets' | 'settings' | 'custom';
export type ServiceName = 'kibana' | 'elasticsearch';
@ -399,17 +399,26 @@ export interface PackageUsageStats {
agent_policy_count: number;
}
export type Installable<T> = Installed<T> | NotInstalled<T>;
export type Installable<T> = Installed<T> | Installing<T> | NotInstalled<T> | InstallFailed<T>;
export type Installed<T = {}> = T & {
status: InstallationStatus['Installed'];
savedObject: SavedObject<Installation>;
};
export type Installing<T = {}> = T & {
status: InstallationStatus['Installing'];
savedObject: SavedObject<Installation>;
};
export type NotInstalled<T = {}> = T & {
status: InstallationStatus['NotInstalled'];
};
export type InstallFailed<T = {}> = T & {
status: InstallationStatus['InstallFailed'];
};
export type AssetReference = KibanaAssetReference | EsAssetReference;
export type KibanaAssetReference = Pick<SavedObjectReference, 'id'> & {

View file

@ -5,23 +5,30 @@
* 2.0.
*/
jest.mock('../registry');
import type { SavedObjectsClientContract, SavedObjectsFindResult } from 'kibana/server';
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server';
import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../common';
import type { PackagePolicySOAttributes } from '../../../../common';
import { PACKAGES_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../common';
import type { PackagePolicySOAttributes, RegistryPackage } from '../../../../common';
import { getPackageUsageStats } from './get';
import * as Registry from '../registry';
import { createAppContextStartContractMock } from '../../../mocks';
import { appContextService } from '../../app_context';
import { getPackageInfo, getPackageUsageStats } from './get';
const MockRegistry = Registry as jest.Mocked<typeof Registry>;
describe('When using EPM `get` services', () => {
let soClient: jest.Mocked<SavedObjectsClientContract>;
beforeEach(() => {
soClient = savedObjectsClientMock.create();
});
describe('and invoking getPackageUsageStats()', () => {
let soClient: jest.Mocked<SavedObjectsClientContract>;
beforeEach(() => {
soClient = savedObjectsClientMock.create();
const savedObjects: Array<SavedObjectsFindResult<PackagePolicySOAttributes>> = [
{
type: 'ingest-package-policies',
@ -172,4 +179,105 @@ describe('When using EPM `get` services', () => {
});
});
});
describe('getPackageInfo', () => {
beforeEach(() => {
const mockContract = createAppContextStartContractMock();
appContextService.start(mockContract);
MockRegistry.fetchFindLatestPackage.mockResolvedValue({
name: 'my-package',
version: '1.0.0',
} as RegistryPackage);
MockRegistry.getRegistryPackage.mockResolvedValue({
paths: [],
packageInfo: {
name: 'my-package',
version: '1.0.0',
} as RegistryPackage,
});
});
describe('installation status', () => {
it('should be not_installed when no package SO exists', async () => {
const soClient = savedObjectsClientMock.create();
soClient.get.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError());
expect(
await getPackageInfo({
savedObjectsClient: soClient,
pkgName: 'my-package',
pkgVersion: '1.0.0',
})
).toMatchObject({
status: 'not_installed',
});
});
it('should be installing when package SO install_status is installing', async () => {
const soClient = savedObjectsClientMock.create();
soClient.get.mockResolvedValue({
id: 'my-package',
type: PACKAGES_SAVED_OBJECT_TYPE,
references: [],
attributes: {
install_status: 'installing',
},
});
expect(
await getPackageInfo({
savedObjectsClient: soClient,
pkgName: 'my-package',
pkgVersion: '1.0.0',
})
).toMatchObject({
status: 'installing',
});
});
it('should be installed when package SO install_status is installed', async () => {
const soClient = savedObjectsClientMock.create();
soClient.get.mockResolvedValue({
id: 'my-package',
type: PACKAGES_SAVED_OBJECT_TYPE,
references: [],
attributes: {
install_status: 'installed',
},
});
expect(
await getPackageInfo({
savedObjectsClient: soClient,
pkgName: 'my-package',
pkgVersion: '1.0.0',
})
).toMatchObject({
status: 'installed',
});
});
it('should be install_failed when package SO install_status is install_failed', async () => {
const soClient = savedObjectsClientMock.create();
soClient.get.mockResolvedValue({
id: 'my-package',
type: PACKAGES_SAVED_OBJECT_TYPE,
references: [],
attributes: {
install_status: 'install_failed',
},
});
expect(
await getPackageInfo({
savedObjectsClient: soClient,
pkgName: 'my-package',
pkgVersion: '1.0.0',
})
).toMatchObject({
status: 'install_failed',
});
});
});
});
});

View file

@ -53,7 +53,7 @@ export function createInstallableFrom<T>(
return savedObject
? {
...from,
status: installationStatuses.Installed,
status: savedObject.attributes.install_status,
savedObject,
}
: {

View file

@ -12,7 +12,12 @@ import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } fro
import { generateESIndexPatterns } from '../elasticsearch/template/template';
import type { BulkInstallPackageInfo, InstallablePackage, InstallSource } from '../../../../common';
import type {
BulkInstallPackageInfo,
EpmPackageInstallStatus,
InstallablePackage,
InstallSource,
} from '../../../../common';
import {
IngestManagerError,
PackageOperationNotSupportedError,
@ -158,6 +163,8 @@ export async function handleInstallPackageFailure({
await removeInstallation({ savedObjectsClient, pkgkey, esClient });
}
await updateInstallStatus({ savedObjectsClient, pkgName, status: 'install_failed' });
if (installType === 'update') {
if (!installedPkg) {
logger.error(
@ -432,6 +439,21 @@ export const updateVersion = async (
version: pkgVersion,
});
};
export const updateInstallStatus = async ({
savedObjectsClient,
pkgName,
status,
}: {
savedObjectsClient: SavedObjectsClientContract;
pkgName: string;
status: EpmPackageInstallStatus;
}) => {
return savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, {
install_status: status,
});
};
export async function createInstallation(options: {
savedObjectsClient: SavedObjectsClientContract;
packageInfo: InstallablePackage;