Add x-pack plugin for new platform server licensing information (#43623) (#46920)

* Add x-pack plugin for new platform server licensing information

* Update x-pack translations

* Implement core plugin interface for licensing plugin:

* Rename references to service to plugin

* Use CoreStart in licensing start method

* Fix outstanding reference error

* Fix type check errors

* Address outstanding review comments

* Fix type error in tests

* Address review comments, move polling logic to standalone

* Split up test files

* Fix bad reference in test

* Use relative reference to poller util

* Add more plugin tests to address review comments

* Fix different manners of config generation in licensing plugin

* Update test fixtures

* Fix path to test fixtures
This commit is contained in:
Mikhail Shustov 2019-09-30 17:16:02 +02:00 committed by GitHub
parent d8392c4771
commit c05b383b63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1098 additions and 6 deletions

View file

@ -0,0 +1,70 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Poller } from './poller';
const delay = (duration: number) => new Promise(r => setTimeout(r, duration));
describe('Poller', () => {
let handler: jest.Mock<any, any>;
let poller: Poller<string>;
beforeEach(() => {
handler = jest.fn().mockImplementation((iteration: number) => `polling-${iteration}`);
poller = new Poller<string>(100, 'polling', handler);
});
afterEach(() => {
poller.unsubscribe();
});
it('returns an observable of subject', async () => {
await delay(300);
expect(poller.subject$.getValue()).toBe('polling-2');
});
it('executes a function on an interval', async () => {
await delay(300);
expect(handler).toBeCalledTimes(3);
});
it('no longer polls after unsubscribing', async () => {
await delay(300);
poller.unsubscribe();
await delay(300);
expect(handler).toBeCalledTimes(3);
});
it('does not add next value if returns undefined', async () => {
const values: any[] = [];
const polling = new Poller<string>(100, 'polling', iteration => {
if (iteration % 2 === 0) {
return `polling-${iteration}`;
}
});
polling.subject$.subscribe(value => {
values.push(value);
});
await delay(300);
polling.unsubscribe();
expect(values).toEqual(['polling', 'polling-0', 'polling-2']);
});
});

55
src/core/utils/poller.ts Normal file
View file

@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BehaviorSubject, timer } from 'rxjs';
/**
* Create an Observable BehaviorSubject to invoke a function on an interval
* which returns the next value for the observable.
* @public
*/
export class Poller<T> {
/**
* The observable to observe for changes to the poller value.
*/
public readonly subject$ = new BehaviorSubject<T>(this.initialValue);
private poller$ = timer(0, this.frequency);
private subscription = this.poller$.subscribe(async iteration => {
const next = await this.handler(iteration);
if (next !== undefined) {
this.subject$.next(next);
}
return iteration;
});
constructor(
private frequency: number,
private initialValue: T,
private handler: (iteration: number) => Promise<T | undefined> | T | undefined
) {}
/**
* Permanently end the polling operation.
*/
unsubscribe() {
return this.subscription.unsubscribe();
}
}

View file

@ -0,0 +1,8 @@
{
"id": "licensing",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["x-pack", "licensing"],
"server": true,
"ui": false
}

View file

@ -0,0 +1,115 @@
/*
* 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 { take, skip } from 'rxjs/operators';
import { merge } from 'lodash';
import { ClusterClient } from 'src/core/server';
import { coreMock } from '../../../../../src/core/server/mocks';
import { Plugin } from '../plugin';
import { schema } from '../schema';
export async function licenseMerge(xpackInfo = {}) {
return merge(
{
license: {
uid: '00000000-0000-0000-0000-000000000000',
type: 'basic',
mode: 'basic',
status: 'active',
},
features: {
ccr: {
available: false,
enabled: true,
},
data_frame: {
available: true,
enabled: true,
},
graph: {
available: false,
enabled: true,
},
ilm: {
available: true,
enabled: true,
},
logstash: {
available: false,
enabled: true,
},
ml: {
available: false,
enabled: true,
},
monitoring: {
available: true,
enabled: true,
},
rollup: {
available: true,
enabled: true,
},
security: {
available: true,
enabled: true,
},
sql: {
available: true,
enabled: true,
},
vectors: {
available: true,
enabled: true,
},
voting_only: {
available: true,
enabled: true,
},
watcher: {
available: false,
enabled: true,
},
},
},
xpackInfo
);
}
export async function setupOnly(pluginInitializerContext: any = {}) {
const coreSetup = coreMock.createSetup();
const clusterClient = ((await coreSetup.elasticsearch.dataClient$
.pipe(take(1))
.toPromise()) as unknown) as jest.Mocked<PublicMethodsOf<ClusterClient>>;
const plugin = new Plugin(
coreMock.createPluginInitializerContext({
config: schema.validate(pluginInitializerContext.config || {}),
})
);
return { coreSetup, plugin, clusterClient };
}
export async function setup(xpackInfo = {}, pluginInitializerContext: any = {}) {
const { coreSetup, clusterClient, plugin } = await setupOnly(pluginInitializerContext);
clusterClient.callAsInternalUser.mockResolvedValueOnce(licenseMerge(xpackInfo));
const { license$ } = await plugin.setup(coreSetup);
const license = await license$
.pipe(
skip(1),
take(1)
)
.toPromise();
return {
plugin,
license$,
license,
clusterClient,
};
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export const SERVICE_NAME = 'licensing';
export const DEFAULT_POLLING_FREQUENCY = 30001; // 30 seconds
export enum LICENSE_STATUS {
Unavailable = 'UNAVAILABLE',
Invalid = 'INVALID',
Expired = 'EXPIRED',
Valid = 'VALID',
}
export enum LICENSE_TYPE {
basic = 10,
standard = 20,
gold = 30,
platinum = 40,
trial = 50,
}

View file

@ -0,0 +1,13 @@
/*
* 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 { PluginInitializerContext } from 'src/core/server';
import { schema } from './schema';
import { Plugin } from './plugin';
export * from './types';
export const config = { schema };
export const plugin = (context: PluginInitializerContext) => new Plugin(context);

View file

@ -0,0 +1,180 @@
/*
* 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 { ILicense } from './types';
import { Plugin } from './plugin';
import { LICENSE_STATUS } from './constants';
import { LicenseFeature } from './license_feature';
import { setup } from './__fixtures__/setup';
describe('license', () => {
let plugin: Plugin;
let license: ILicense;
afterEach(async () => {
await plugin.stop();
});
test('uid returns a UID field', async () => {
({ plugin, license } = await setup());
expect(license.uid).toBe('00000000-0000-0000-0000-000000000000');
});
test('isActive returns true if status is active', async () => {
({ plugin, license } = await setup());
expect(license.isActive).toBe(true);
});
test('isActive returns false if status is not active', async () => {
({ plugin, license } = await setup({
license: {
status: 'aCtIvE', // needs to match exactly
},
}));
expect(license.isActive).toBe(false);
});
test('expiryDateInMillis returns expiry_date_in_millis', async () => {
const expiry = Date.now();
({ plugin, license } = await setup({
license: {
expiry_date_in_millis: expiry,
},
}));
expect(license.expiryDateInMillis).toBe(expiry);
});
test('isOneOf returns true if the type includes one of the license types', async () => {
({ plugin, license } = await setup({
license: {
type: 'platinum',
},
}));
expect(license.isOneOf('platinum')).toBe(true);
expect(license.isOneOf(['platinum'])).toBe(true);
expect(license.isOneOf(['gold', 'platinum'])).toBe(true);
expect(license.isOneOf(['platinum', 'gold'])).toBe(true);
expect(license.isOneOf(['basic', 'gold'])).toBe(false);
expect(license.isOneOf(['basic'])).toBe(false);
});
test('type returns the license type', async () => {
({ plugin, license } = await setup());
expect(license.type).toBe('basic');
});
test('returns feature API with getFeature', async () => {
({ plugin, license } = await setup());
const security = license.getFeature('security');
const fake = license.getFeature('fake');
expect(security).toBeInstanceOf(LicenseFeature);
expect(fake).toBeInstanceOf(LicenseFeature);
});
describe('isActive', () => {
test('should return Valid if active and check matches', async () => {
({ plugin, license } = await setup({
license: {
type: 'gold',
},
}));
expect(license.check('test', 'basic').check).toBe(LICENSE_STATUS.Valid);
expect(license.check('test', 'gold').check).toBe(LICENSE_STATUS.Valid);
});
test('should return Invalid if active and check does not match', async () => {
({ plugin, license } = await setup());
const { check } = license.check('test', 'gold');
expect(check).toBe(LICENSE_STATUS.Invalid);
});
test('should return Unavailable if missing license', async () => {
({ plugin, license } = await setup({ license: null }));
const { check } = license.check('test', 'gold');
expect(check).toBe(LICENSE_STATUS.Unavailable);
});
test('should return Expired if not active', async () => {
({ plugin, license } = await setup({
license: {
status: 'not-active',
},
}));
const { check } = license.check('test', 'basic');
expect(check).toBe(LICENSE_STATUS.Expired);
});
});
describe('basic', () => {
test('isBasic is true if active and basic', async () => {
({ plugin, license } = await setup());
expect(license.isBasic).toBe(true);
});
test('isBasic is false if active and not basic', async () => {
({ plugin, license } = await setup({
license: {
type: 'gold',
},
}));
expect(license.isBasic).toBe(false);
});
test('isBasic is false if not active and basic', async () => {
({ plugin, license } = await setup({
license: {
status: 'not-active',
},
}));
expect(license.isBasic).toBe(false);
});
test('isNotBasic is false if not active', async () => {
({ plugin, license } = await setup({
license: {
status: 'not-active',
},
}));
expect(license.isNotBasic).toBe(false);
});
test('isNotBasic is true if active and not basic', async () => {
({ plugin, license } = await setup({
license: {
type: 'gold',
},
}));
expect(license.isNotBasic).toBe(true);
});
test('isNotBasic is false if active and basic', async () => {
({ plugin, license } = await setup());
expect(license.isNotBasic).toBe(false);
});
});
});

View file

@ -0,0 +1,178 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { createHash } from 'crypto';
import { LicenseFeature } from './license_feature';
import { LICENSE_STATUS, LICENSE_TYPE } from './constants';
import { LicenseType, ILicense } from './types';
function toLicenseType(minimumLicenseRequired: LICENSE_TYPE | string) {
if (typeof minimumLicenseRequired !== 'string') {
return minimumLicenseRequired;
}
if (!(minimumLicenseRequired in LICENSE_TYPE)) {
throw new Error(`${minimumLicenseRequired} is not a valid license type`);
}
return LICENSE_TYPE[minimumLicenseRequired as LicenseType];
}
export class License implements ILicense {
private readonly hasLicense: boolean;
private readonly license: any;
private readonly features: any;
private _signature!: string;
private objectified!: any;
private readonly featuresMap: Map<string, LicenseFeature>;
constructor(
license: any,
features: any,
private error: Error | null,
private clusterSource: string
) {
this.hasLicense = Boolean(license);
this.license = license || {};
this.features = features;
this.featuresMap = new Map<string, LicenseFeature>();
}
public get uid() {
return this.license.uid;
}
public get status() {
return this.license.status;
}
public get isActive() {
return this.status === 'active';
}
public get expiryDateInMillis() {
return this.license.expiry_date_in_millis;
}
public get type() {
return this.license.type;
}
public get isAvailable() {
return this.hasLicense;
}
public get isBasic() {
return this.isActive && this.type === 'basic';
}
public get isNotBasic() {
return this.isActive && this.type !== 'basic';
}
public get reasonUnavailable() {
if (!this.isAvailable) {
return `[${this.clusterSource}] Elasticsearch cluster did not respond with license information.`;
}
if (this.error instanceof Error && (this.error as any).status === 400) {
return `X-Pack plugin is not installed on the [${this.clusterSource}] Elasticsearch cluster.`;
}
return this.error;
}
public get signature() {
if (this._signature !== undefined) {
return this._signature;
}
this._signature = createHash('md5')
.update(JSON.stringify(this.toObject()))
.digest('hex');
return this._signature;
}
isOneOf(candidateLicenses: string | string[]) {
if (!Array.isArray(candidateLicenses)) {
candidateLicenses = [candidateLicenses];
}
return candidateLicenses.includes(this.type);
}
meetsMinimumOf(minimum: LICENSE_TYPE) {
return LICENSE_TYPE[this.type as LicenseType] >= minimum;
}
check(pluginName: string, minimumLicenseRequired: LICENSE_TYPE | string) {
const minimum = toLicenseType(minimumLicenseRequired);
if (!this.isAvailable) {
return {
check: LICENSE_STATUS.Unavailable,
message: i18n.translate('xpack.licensing.check.errorUnavailableMessage', {
defaultMessage:
'You cannot use {pluginName} because license information is not available at this time.',
values: { pluginName },
}),
};
}
const { type: licenseType } = this.license;
if (!this.meetsMinimumOf(minimum)) {
return {
check: LICENSE_STATUS.Invalid,
message: i18n.translate('xpack.licensing.check.errorUnsupportedMessage', {
defaultMessage:
'Your {licenseType} license does not support {pluginName}. Please upgrade your license.',
values: { licenseType, pluginName },
}),
};
}
if (!this.isActive) {
return {
check: LICENSE_STATUS.Expired,
message: i18n.translate('xpack.licensing.check.errorExpiredMessage', {
defaultMessage:
'You cannot use {pluginName} because your {licenseType} license has expired.',
values: { licenseType, pluginName },
}),
};
}
return { check: LICENSE_STATUS.Valid };
}
toObject() {
if (this.objectified) {
return this.objectified;
}
this.objectified = {
license: {
type: this.type,
isActive: this.isActive,
expiryDateInMillis: this.expiryDateInMillis,
},
features: [...this.featuresMap].map(([, feature]) => feature.toObject()),
};
return this.objectified;
}
getFeature(name: string) {
if (!this.featuresMap.has(name)) {
this.featuresMap.set(name, new LicenseFeature(name, this.features[name], this));
}
return this.featuresMap.get(name);
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 { ILicense } from './types';
import { Plugin } from './plugin';
import { setup } from './__fixtures__/setup';
describe('licensing feature', () => {
let plugin: Plugin;
let license: ILicense;
afterEach(async () => {
await plugin.stop();
});
test('isAvailable', async () => {
({ plugin, license } = await setup());
const security = license.getFeature('security');
expect(security!.isAvailable).toBe(true);
});
test('isEnabled', async () => {
({ plugin, license } = await setup());
const security = license.getFeature('security');
expect(security!.isEnabled).toBe(true);
});
test('name', async () => {
({ plugin, license } = await setup());
const security = license.getFeature('security');
expect(security!.name).toBe('security');
});
});

View file

@ -0,0 +1,34 @@
/*
* 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 { License } from './license';
import { LicenseFeatureSerializer } from './types';
export class LicenseFeature {
private serializable: LicenseFeatureSerializer = license => ({
name: this.name,
isAvailable: this.isAvailable,
isEnabled: this.isEnabled,
});
constructor(public name: string, private feature: any = {}, private license: License) {}
public get isAvailable() {
return !!this.feature.available;
}
public get isEnabled() {
return !!this.feature.enabled;
}
public onObject(serializable: LicenseFeatureSerializer) {
this.serializable = serializable;
}
public toObject() {
return this.serializable(this.license);
}
}

View file

@ -0,0 +1,23 @@
/*
* 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 { PluginInitializerContext } from 'src/core/server';
import { LicensingConfigType } from './types';
export class LicensingConfig {
public isEnabled: boolean;
public clusterSource: string;
public pollingFrequency: number;
/**
* @internal
*/
constructor(rawConfig: LicensingConfigType, env: PluginInitializerContext['env']) {
this.isEnabled = rawConfig.isEnabled;
this.clusterSource = rawConfig.clusterSource;
this.pollingFrequency = rawConfig.pollingFrequency;
}
}

View file

@ -0,0 +1,83 @@
/*
* 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 { take, skip } from 'rxjs/operators';
import { ILicense } from './types';
import { Plugin } from './plugin';
import { License } from './license';
import { setup, setupOnly, licenseMerge } from './__fixtures__/setup';
describe('licensing plugin', () => {
let plugin: Plugin;
let license: ILicense;
afterEach(async () => {
await plugin.stop();
});
test('returns instance of licensing setup', async () => {
({ plugin, license } = await setup());
expect(license).toBeInstanceOf(License);
});
test('still returns instance of licensing setup when request fails', async () => {
const { clusterClient, coreSetup, plugin: _plugin } = await setupOnly();
plugin = _plugin;
clusterClient.callAsInternalUser.mockRejectedValue(new Error('test'));
const { license$ } = await plugin.setup(coreSetup);
const finalLicense = await license$
.pipe(
skip(1),
take(1)
)
.toPromise();
expect(finalLicense).toBeInstanceOf(License);
});
test('observable receives updated licenses', async () => {
const { clusterClient, coreSetup, plugin: _plugin } = await setupOnly({
config: {
pollingFrequency: 100,
},
});
const types = ['basic', 'gold', 'platinum'];
let iterations = 0;
plugin = _plugin;
clusterClient.callAsInternalUser.mockImplementation(() => {
return Promise.resolve(
licenseMerge({
license: {
type: types[iterations++],
},
})
);
});
const { license$ } = await plugin.setup(coreSetup);
const licenseTypes: any[] = [];
await new Promise(resolve => {
const subscription = license$.subscribe(next => {
if (!next.type) {
return;
}
if (iterations > 3) {
subscription.unsubscribe();
resolve();
} else {
licenseTypes.push(next.type);
}
});
});
expect(licenseTypes).toEqual(['basic', 'gold', 'platinum']);
});
});

View file

@ -0,0 +1,136 @@
/*
* 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 { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import moment from 'moment';
import {
CoreSetup,
CoreStart,
Logger,
Plugin as CorePlugin,
PluginInitializerContext,
} from 'src/core/server';
import { Poller } from '../../../../src/core/utils/poller';
import { LicensingConfigType, LicensingPluginSetup, ILicense } from './types';
import { LicensingConfig } from './licensing_config';
import { License } from './license';
export class Plugin implements CorePlugin<LicensingPluginSetup> {
private readonly logger: Logger;
private readonly config$: Observable<LicensingConfig>;
private poller!: Poller<ILicense>;
constructor(private readonly context: PluginInitializerContext) {
this.logger = this.context.logger.get();
this.config$ = this.context.config
.create<LicensingConfigType | { config: LicensingConfigType }>()
.pipe(
map(config =>
'config' in config
? new LicensingConfig(config.config, this.context.env)
: new LicensingConfig(config, this.context.env)
)
);
}
private hasLicenseInfoChanged(newLicense: any) {
const currentLicense = this.poller.subject$.getValue();
if ((currentLicense && !newLicense) || (newLicense && !currentLicense)) {
return true;
}
return (
newLicense.type !== currentLicense.type ||
newLicense.status !== currentLicense.status ||
newLicense.expiry_date_in_millis !== currentLicense.expiryDateInMillis
);
}
private async fetchInfo(core: CoreSetup, clusterSource: string, pollingFrequency: number) {
this.logger.debug(
`Calling [${clusterSource}] Elasticsearch _xpack API. Polling frequency: ${pollingFrequency}`
);
const cluster = await core.elasticsearch.dataClient$.pipe(first()).toPromise();
try {
const response = await cluster.callAsInternalUser('transport.request', {
method: 'GET',
path: '/_xpack',
});
const rawLicense = response && response.license;
const features = (response && response.features) || {};
const licenseInfoChanged = this.hasLicenseInfoChanged(rawLicense);
if (!licenseInfoChanged) {
return { license: false, error: null, features: null };
}
const currentLicense = this.poller.subject$.getValue();
const licenseInfo = [
'type' in rawLicense && `type: ${rawLicense.type}`,
'status' in rawLicense && `status: ${rawLicense.status}`,
'expiry_date_in_millis' in rawLicense &&
`expiry date: ${moment(rawLicense.expiry_date_in_millis, 'x').format()}`,
]
.filter(Boolean)
.join(' | ');
this.logger.info(
`Imported ${currentLicense ? 'changed ' : ''}license information` +
` from Elasticsearch for the [${clusterSource}] cluster: ${licenseInfo}`
);
return { license: rawLicense, error: null, features };
} catch (err) {
this.logger.warn(
`License information could not be obtained from Elasticsearch` +
` for the [${clusterSource}] cluster. ${err}`
);
return { license: null, error: err, features: {} };
}
}
private create({ clusterSource, pollingFrequency }: LicensingConfig, core: CoreSetup) {
this.poller = new Poller<ILicense>(
pollingFrequency,
new License(null, {}, null, clusterSource),
async () => {
const { license, features, error } = await this.fetchInfo(
core,
clusterSource,
pollingFrequency
);
if (license !== false) {
return new License(license, features, error, clusterSource);
}
}
);
return this.poller;
}
public async setup(core: CoreSetup) {
const config = await this.config$.pipe(first()).toPromise();
const poller = this.create(config, core);
return {
license$: poller.subject$.asObservable(),
};
}
public async start(core: CoreStart) {}
public stop() {
if (this.poller) {
this.poller.unsubscribe();
}
}
}

View file

@ -0,0 +1,14 @@
/*
* 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 { schema as Schema } from '@kbn/config-schema';
import { DEFAULT_POLLING_FREQUENCY } from './constants';
export const schema = Schema.object({
isEnabled: Schema.boolean({ defaultValue: true }),
clusterSource: Schema.string({ defaultValue: 'data' }),
pollingFrequency: Schema.number({ defaultValue: DEFAULT_POLLING_FREQUENCY }),
});

View file

@ -0,0 +1,120 @@
/*
* 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 { Observable } from 'rxjs';
import { TypeOf } from '@kbn/config-schema';
import { schema } from './schema';
import { LICENSE_TYPE, LICENSE_STATUS } from './constants';
import { LicenseFeature } from './license_feature';
/**
* @public
* Results from checking if a particular license type meets the minimum
* requirements of the license type.
*/
export interface ILicenseCheck {
/**
* The status of checking the results of a license type meeting the license minimum.
*/
check: LICENSE_STATUS;
/**
* A message containing the reason for a license type not being valid.
*/
message?: string;
}
/** @public */
export interface ILicense {
/**
* UID for license.
*/
uid?: string;
/**
* The validity status of the license.
*/
status?: string;
/**
* Determine if the status of the license is active.
*/
isActive: boolean;
/**
* Unix epoch of the expiration date of the license.
*/
expiryDateInMillis?: number;
/**
* The license type, being usually one of basic, standard, gold, platinum, or trial.
*/
type?: string;
/**
* Determine if the license container has information.
*/
isAvailable: boolean;
/**
* Determine if the type of the license is basic, and also active.
*/
isBasic: boolean;
/**
* Determine if the type of the license is not basic, and also active.
*/
isNotBasic: boolean;
/**
* If the license is not available, provides a string or Error containing the reason.
*/
reasonUnavailable: string | Error | null;
/**
* The MD5 hash of the serialized license.
*/
signature: string;
/**
* Determine if the provided license types match against the license type.
* @param candidateLicenses license types to intersect against the license.
*/
isOneOf(candidateLicenses: string | string[]): boolean;
/**
* Determine if the provided license type is sufficient for the current license.
* @param minimum a license type to determine for sufficiency
*/
meetsMinimumOf(minimum: LICENSE_TYPE): boolean;
/**
* For a given plugin and license type, receive information about the status of the license.
* @param pluginName the name of the plugin
* @param minimumLicenseRequired the minimum valid license for operating the given plugin
*/
check(pluginName: string, minimumLicenseRequired: LICENSE_TYPE | string): ILicenseCheck;
/**
* Receive a serialized plain object of the license.
*/
toObject(): any;
/**
* A specific API for interacting with the specific features of the license.
* @param name the name of the feature to interact with
*/
getFeature(name: string): LicenseFeature | undefined;
}
/** @public */
export interface LicensingPluginSetup {
license$: Observable<ILicense>;
}
/** @public */
export type LicensingConfigType = TypeOf<typeof schema>;
/** @public */
export type LicenseType = keyof typeof LICENSE_TYPE;
/** @public */
export type LicenseFeatureSerializer = (licensing: ILicense) => any;

View file

@ -5491,6 +5491,9 @@
"xpack.licenseMgmt.uploadLicense.uploadButtonLabel": "アップロード",
"xpack.licenseMgmt.uploadLicense.uploadingButtonLabel": "アップロード中…",
"xpack.licenseMgmt.uploadLicense.uploadLicenseTitle": "ライセンスのアップロード",
"xpack.licensing.check.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません",
"xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。",
"xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。",
"xpack.logstash.addRoleAlert.grantAdditionalPrivilegesDescription": "Kibana の管理で、Kibana ユーザーに {role} ロールを割り当ててください。",
"xpack.logstash.addRoleAlert.grantAdditionalPrivilegesTitle": "追加権限の授与。",
"xpack.logstash.alertCallOut.howToSeeAdditionalPipelinesDescription": "追加パイプラインを表示させる方法",
@ -9779,9 +9782,6 @@
"xpack.security.users.breadcrumb": "ユーザー",
"xpack.security.users.createBreadcrumb": "作成",
"xpack.security.management.editRole.featureTable.excludedFromBasePrivilegsTooltip": "アクセスを許可するには、「カスタム」特権を使用します。{featureName} は基本権限の一部ではありません。",
"xpack.server.checkLicense.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません",
"xpack.server.checkLicense.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。",
"xpack.server.checkLicense.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。",
"xpack.siem.add_to_kql.filterForValueHoverAction": "値でフィルターします",
"xpack.siem.andOrBadge.and": "AND",
"xpack.siem.andOrBadge.or": "OR",

View file

@ -5494,6 +5494,9 @@
"xpack.licenseMgmt.uploadLicense.uploadButtonLabel": "上传",
"xpack.licenseMgmt.uploadLicense.uploadingButtonLabel": "正在上传……",
"xpack.licenseMgmt.uploadLicense.uploadLicenseTitle": "上传您的许可",
"xpack.licensing.check.errorExpiredMessage": "您不能使用 {pluginName},因为您的 {licenseType} 许可证已过期",
"xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。",
"xpack.licensing.check.errorUnsupportedMessage": "您的 {licenseType} 许可证不支持 {pluginName}。请升级您的许可。",
"xpack.logstash.addRoleAlert.grantAdditionalPrivilegesDescription": "在 Kibana“管理”中将 {role} 角色分配给您的 Kibana 用户。",
"xpack.logstash.addRoleAlert.grantAdditionalPrivilegesTitle": "授予其他权限。",
"xpack.logstash.alertCallOut.howToSeeAdditionalPipelinesDescription": "我如何可以看到其他管道?",
@ -9781,9 +9784,6 @@
"xpack.security.users.breadcrumb": "用户",
"xpack.security.users.createBreadcrumb": "创建",
"xpack.security.management.editRole.featureTable.excludedFromBasePrivilegsTooltip": "使用“定制”权限来授予权限。{featureName} 不属于基础权限。",
"xpack.server.checkLicense.errorExpiredMessage": "您不能使用 {pluginName},因为您的 {licenseType} 许可证已过期",
"xpack.server.checkLicense.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。",
"xpack.server.checkLicense.errorUnsupportedMessage": "您的 {licenseType} 许可证不支持 {pluginName}。请升级您的许可。",
"xpack.siem.add_to_kql.filterForValueHoverAction": "筛留值",
"xpack.siem.andOrBadge.and": "AND",
"xpack.siem.andOrBadge.or": "OR",