mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Synthetics] fetch license info in service api client (#154770)
## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. Resolves https://github.com/elastic/kibana/issues/152723 Resolves https://github.com/elastic/synthetics-dev/issues/196 Fetches the license information and sends it as `license_level` to the synthetics service. Also stops syncing with the service if the license is expired.
This commit is contained in:
parent
7199327bca
commit
92c8699cc2
6 changed files with 320 additions and 49 deletions
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
|
||||
jest.mock('axios', () => jest.fn());
|
||||
|
||||
import { CoreStart } from '@kbn/core/server';
|
||||
import { coreMock } from '@kbn/core/server/mocks';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { ServiceAPIClient } from './service_api_client';
|
||||
import { UptimeServerSetup } from '../legacy_uptime/lib/adapters';
|
||||
|
@ -16,16 +15,37 @@ import { ServiceConfig } from '../../common/config';
|
|||
import axios from 'axios';
|
||||
import { LocationStatus, PublicLocations } from '../../common/runtime_types';
|
||||
|
||||
jest.mock('axios', () => jest.fn());
|
||||
jest.mock('@kbn/server-http-tools', () => ({
|
||||
...jest.requireActual('@kbn/server-http-tools'),
|
||||
SslConfig: jest.fn().mockImplementation(({ certificate, key }) => ({ certificate, key })),
|
||||
}));
|
||||
|
||||
const mockCoreStart = coreMock.createStart() as CoreStart;
|
||||
|
||||
mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest.fn().mockResolvedValue({
|
||||
license: {
|
||||
status: 'active',
|
||||
uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0',
|
||||
type: 'platinum',
|
||||
issue_date: '2022-11-29T00:00:00.000Z',
|
||||
issue_date_in_millis: 1669680000000,
|
||||
expiry_date: '2024-12-31T23:59:59.999Z',
|
||||
expiry_date_in_millis: 1735689599999,
|
||||
max_nodes: 100,
|
||||
max_resource_units: null,
|
||||
issued_to: 'Elastic - INTERNAL (development environments)',
|
||||
issuer: 'API',
|
||||
start_date_in_millis: 1669680000000,
|
||||
},
|
||||
});
|
||||
|
||||
describe('getHttpsAgent', () => {
|
||||
it('does not use certs if basic auth is set', () => {
|
||||
const apiClient = new ServiceAPIClient(
|
||||
jest.fn() as unknown as Logger,
|
||||
{ username: 'u', password: 'p' },
|
||||
{ isDev: true } as UptimeServerSetup
|
||||
{ isDev: true, coreStart: mockCoreStart } as UptimeServerSetup
|
||||
);
|
||||
const { options: result } = apiClient.getHttpsAgent('https://localhost:10001');
|
||||
expect(result).not.toHaveProperty('cert');
|
||||
|
@ -36,7 +56,7 @@ describe('getHttpsAgent', () => {
|
|||
const apiClient = new ServiceAPIClient(
|
||||
jest.fn() as unknown as Logger,
|
||||
{ tls: { certificate: 'crt', key: 'k' } } as ServiceConfig,
|
||||
{ isDev: true } as UptimeServerSetup
|
||||
{ isDev: true, coreStart: mockCoreStart } as UptimeServerSetup
|
||||
);
|
||||
|
||||
const { options: result } = apiClient.getHttpsAgent('https://example.com');
|
||||
|
@ -47,7 +67,7 @@ describe('getHttpsAgent', () => {
|
|||
const apiClient = new ServiceAPIClient(
|
||||
jest.fn() as unknown as Logger,
|
||||
{ tls: { certificate: 'crt', key: 'k' } } as ServiceConfig,
|
||||
{ isDev: false } as UptimeServerSetup
|
||||
{ isDev: false, coreStart: mockCoreStart } as UptimeServerSetup
|
||||
);
|
||||
|
||||
const { options: result } = apiClient.getHttpsAgent('https://localhost:10001');
|
||||
|
@ -58,7 +78,7 @@ describe('getHttpsAgent', () => {
|
|||
const apiClient = new ServiceAPIClient(
|
||||
jest.fn() as unknown as Logger,
|
||||
{ tls: { certificate: 'crt', key: 'k' } } as ServiceConfig,
|
||||
{ isDev: false } as UptimeServerSetup
|
||||
{ isDev: false, coreStart: mockCoreStart } as UptimeServerSetup
|
||||
);
|
||||
|
||||
const { options: result } = apiClient.getHttpsAgent('https://localhost:10001');
|
||||
|
@ -77,7 +97,7 @@ describe('checkAccountAccessStatus', () => {
|
|||
const apiClient = new ServiceAPIClient(
|
||||
jest.fn() as unknown as Logger,
|
||||
{ tls: { certificate: 'crt', key: 'k' } } as ServiceConfig,
|
||||
{ isDev: false, stackVersion: '8.4' } as UptimeServerSetup
|
||||
{ isDev: false, stackVersion: '8.4', coreStart: mockCoreStart } as UptimeServerSetup
|
||||
);
|
||||
|
||||
apiClient.locations = [
|
||||
|
@ -110,6 +130,7 @@ describe('checkAccountAccessStatus', () => {
|
|||
describe('callAPI', () => {
|
||||
beforeEach(() => {
|
||||
(axios as jest.MockedFunction<typeof axios>).mockReset();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
@ -134,6 +155,7 @@ describe('callAPI', () => {
|
|||
const apiClient = new ServiceAPIClient(logger, config, {
|
||||
isDev: true,
|
||||
stackVersion: '8.7.0',
|
||||
coreStart: mockCoreStart,
|
||||
} as UptimeServerSetup);
|
||||
|
||||
const spy = jest.spyOn(apiClient, 'callServiceEndpoint');
|
||||
|
@ -145,6 +167,7 @@ describe('callAPI', () => {
|
|||
await apiClient.callAPI('POST', {
|
||||
monitors: testMonitors,
|
||||
output,
|
||||
licenseLevel: 'trial',
|
||||
});
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
|
@ -159,6 +182,7 @@ describe('callAPI', () => {
|
|||
monitor.locations.some((loc: any) => loc.id === 'us_central')
|
||||
),
|
||||
output,
|
||||
licenseLevel: 'trial',
|
||||
},
|
||||
'POST',
|
||||
devUrl
|
||||
|
@ -173,6 +197,7 @@ describe('callAPI', () => {
|
|||
monitor.locations.some((loc: any) => loc.id === 'us_central_qa')
|
||||
),
|
||||
output,
|
||||
licenseLevel: 'trial',
|
||||
},
|
||||
'POST',
|
||||
'https://qa.service.elstc.co'
|
||||
|
@ -187,6 +212,7 @@ describe('callAPI', () => {
|
|||
monitor.locations.some((loc: any) => loc.id === 'us_central_staging')
|
||||
),
|
||||
output,
|
||||
licenseLevel: 'trial',
|
||||
},
|
||||
'POST',
|
||||
'https://qa.service.stg.co'
|
||||
|
@ -194,7 +220,13 @@ describe('callAPI', () => {
|
|||
|
||||
expect(axiosSpy).toHaveBeenCalledTimes(3);
|
||||
expect(axiosSpy).toHaveBeenNthCalledWith(1, {
|
||||
data: { monitors: request1, is_edit: undefined, output, stack_version: '8.7.0' },
|
||||
data: {
|
||||
monitors: request1,
|
||||
is_edit: undefined,
|
||||
output,
|
||||
stack_version: '8.7.0',
|
||||
license_level: 'trial',
|
||||
},
|
||||
headers: {
|
||||
Authorization: 'Basic ZGV2OjEyMzQ1',
|
||||
'x-kibana-version': '8.7.0',
|
||||
|
@ -207,7 +239,13 @@ describe('callAPI', () => {
|
|||
});
|
||||
|
||||
expect(axiosSpy).toHaveBeenNthCalledWith(2, {
|
||||
data: { monitors: request2, is_edit: undefined, output, stack_version: '8.7.0' },
|
||||
data: {
|
||||
monitors: request2,
|
||||
is_edit: undefined,
|
||||
output,
|
||||
stack_version: '8.7.0',
|
||||
license_level: 'trial',
|
||||
},
|
||||
headers: {
|
||||
Authorization: 'Basic ZGV2OjEyMzQ1',
|
||||
'x-kibana-version': '8.7.0',
|
||||
|
@ -220,7 +258,13 @@ describe('callAPI', () => {
|
|||
});
|
||||
|
||||
expect(axiosSpy).toHaveBeenNthCalledWith(3, {
|
||||
data: { monitors: request3, is_edit: undefined, output, stack_version: '8.7.0' },
|
||||
data: {
|
||||
monitors: request3,
|
||||
is_edit: undefined,
|
||||
output,
|
||||
stack_version: '8.7.0',
|
||||
license_level: 'trial',
|
||||
},
|
||||
headers: {
|
||||
Authorization: 'Basic ZGV2OjEyMzQ1',
|
||||
'x-kibana-version': '8.7.0',
|
||||
|
@ -273,6 +317,7 @@ describe('callAPI', () => {
|
|||
{
|
||||
isDev: true,
|
||||
stackVersion: '8.7.0',
|
||||
coreStart: mockCoreStart,
|
||||
} as UptimeServerSetup
|
||||
);
|
||||
|
||||
|
@ -283,10 +328,17 @@ describe('callAPI', () => {
|
|||
await apiClient.callAPI('POST', {
|
||||
monitors: testMonitors,
|
||||
output,
|
||||
licenseLevel: 'platinum',
|
||||
});
|
||||
|
||||
expect(axiosSpy).toHaveBeenNthCalledWith(1, {
|
||||
data: { monitors: request1, is_edit: undefined, output, stack_version: '8.7.0' },
|
||||
data: {
|
||||
monitors: request1,
|
||||
is_edit: undefined,
|
||||
output,
|
||||
stack_version: '8.7.0',
|
||||
license_level: 'platinum',
|
||||
},
|
||||
headers: {
|
||||
'x-kibana-version': '8.7.0',
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ export interface ServiceData {
|
|||
};
|
||||
runOnce?: boolean;
|
||||
isEdit?: boolean;
|
||||
licenseLevel: string;
|
||||
}
|
||||
|
||||
export class ServiceAPIClient {
|
||||
|
@ -111,7 +112,7 @@ export class ServiceAPIClient {
|
|||
const url = this.locations[Math.floor(Math.random() * this.locations.length)].url;
|
||||
|
||||
/* url is required for service locations, but omitted for private locations.
|
||||
/* this.locations is only service locations */
|
||||
/* this.locations is only service locations */
|
||||
const httpsAgent = this.getHttpsAgent(url);
|
||||
|
||||
if (httpsAgent) {
|
||||
|
@ -137,7 +138,7 @@ export class ServiceAPIClient {
|
|||
|
||||
async callAPI(
|
||||
method: 'POST' | 'PUT' | 'DELETE',
|
||||
{ monitors: allMonitors, output, runOnce, isEdit }: ServiceData
|
||||
{ monitors: allMonitors, output, runOnce, isEdit, licenseLevel }: ServiceData
|
||||
) {
|
||||
if (this.username === TEST_SERVICE_USERNAME) {
|
||||
// we don't want to call service while local integration tests are running
|
||||
|
@ -156,7 +157,7 @@ export class ServiceAPIClient {
|
|||
promises.push(
|
||||
rxjsFrom(
|
||||
this.callServiceEndpoint(
|
||||
{ monitors: locMonitors, isEdit, runOnce, output },
|
||||
{ monitors: locMonitors, isEdit, runOnce, output, licenseLevel },
|
||||
method,
|
||||
url
|
||||
)
|
||||
|
@ -196,7 +197,7 @@ export class ServiceAPIClient {
|
|||
}
|
||||
|
||||
async callServiceEndpoint(
|
||||
{ monitors, output, runOnce, isEdit }: ServiceData,
|
||||
{ monitors, output, runOnce, isEdit, licenseLevel }: ServiceData,
|
||||
method: 'POST' | 'PUT' | 'DELETE',
|
||||
url: string
|
||||
) {
|
||||
|
@ -214,6 +215,7 @@ export class ServiceAPIClient {
|
|||
output,
|
||||
stack_version: this.stackVersion,
|
||||
is_edit: isEdit,
|
||||
license_level: licenseLevel,
|
||||
},
|
||||
headers: this.authorization
|
||||
? {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { KibanaRequest, SavedObjectsClientContract, CoreStart } from '@kbn/core/server';
|
||||
import { coreMock } from '@kbn/core/server/mocks';
|
||||
import { SyntheticsMonitorClient } from './synthetics_monitor_client';
|
||||
import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters';
|
||||
import { SyntheticsService } from '../synthetics_service';
|
||||
|
@ -18,6 +19,25 @@ import {
|
|||
} from '../../../common/runtime_types';
|
||||
import { mockEncryptedSO } from '../utils/mocks';
|
||||
|
||||
const mockCoreStart = coreMock.createStart() as CoreStart;
|
||||
|
||||
mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest.fn().mockResolvedValue({
|
||||
license: {
|
||||
status: 'active',
|
||||
uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0',
|
||||
type: 'platinum',
|
||||
issue_date: '2022-11-29T00:00:00.000Z',
|
||||
issue_date_in_millis: 1669680000000,
|
||||
expiry_date: '2024-12-31T23:59:59.999Z',
|
||||
expiry_date_in_millis: 1735689599999,
|
||||
max_nodes: 100,
|
||||
max_resource_units: null,
|
||||
issued_to: 'Elastic - INTERNAL (development environments)',
|
||||
issuer: 'API',
|
||||
start_date_in_millis: 1669680000000,
|
||||
},
|
||||
});
|
||||
|
||||
describe('SyntheticsMonitorClient', () => {
|
||||
const mockEsClient = {
|
||||
search: jest.fn(),
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('axios', () => jest.fn());
|
||||
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import { coreMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { CoreStart } from '@kbn/core/server';
|
||||
import { SyntheticsService } from './synthetics_service';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { UptimeServerSetup } from '../legacy_uptime/lib/adapters';
|
||||
|
@ -16,8 +16,50 @@ import times from 'lodash/times';
|
|||
import { LocationStatus, HeartbeatConfig } from '../../common/runtime_types';
|
||||
import { mockEncryptedSO } from './utils/mocks';
|
||||
|
||||
jest.mock('axios', () => jest.fn());
|
||||
|
||||
const taskManagerSetup = taskManagerMock.createSetup();
|
||||
|
||||
const mockCoreStart = coreMock.createStart() as CoreStart;
|
||||
|
||||
mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest.fn().mockResolvedValue({
|
||||
license: {
|
||||
status: 'active',
|
||||
uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0',
|
||||
type: 'platinum',
|
||||
issue_date: '2022-11-29T00:00:00.000Z',
|
||||
issue_date_in_millis: 1669680000000,
|
||||
expiry_date: '2024-12-31T23:59:59.999Z',
|
||||
expiry_date_in_millis: 1735689599999,
|
||||
max_nodes: 100,
|
||||
max_resource_units: null,
|
||||
issued_to: 'Elastic - INTERNAL (development environments)',
|
||||
issuer: 'API',
|
||||
start_date_in_millis: 1669680000000,
|
||||
},
|
||||
});
|
||||
|
||||
const getFakePayload = (locations: HeartbeatConfig['locations']) => {
|
||||
return {
|
||||
type: 'http',
|
||||
enabled: true,
|
||||
schedule: {
|
||||
number: '3',
|
||||
unit: 'm',
|
||||
},
|
||||
name: 'my mon',
|
||||
locations,
|
||||
urls: 'http://google.com',
|
||||
max_redirects: '0',
|
||||
password: '',
|
||||
proxy_url: '',
|
||||
id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d',
|
||||
fields: { config_id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d' },
|
||||
fields_under_root: true,
|
||||
secrets: '{}',
|
||||
};
|
||||
};
|
||||
|
||||
describe('SyntheticsService', () => {
|
||||
const mockEsClient = {
|
||||
search: jest.fn(),
|
||||
|
@ -38,13 +80,12 @@ describe('SyntheticsService', () => {
|
|||
manifestUrl: 'http://localhost:8080/api/manifest',
|
||||
},
|
||||
},
|
||||
coreStart: mockCoreStart,
|
||||
encryptedSavedObjects: mockEncryptedSO(),
|
||||
savedObjectsClient: savedObjectsClientMock.create()!,
|
||||
} as unknown as UptimeServerSetup;
|
||||
|
||||
const getMockedService = (locationsNum: number = 1) => {
|
||||
serverMock.config = { service: { devUrl: 'http://localhost' } };
|
||||
const service = new SyntheticsService(serverMock);
|
||||
|
||||
const locations = times(locationsNum).map((n) => {
|
||||
return {
|
||||
id: `loc-${n}`,
|
||||
|
@ -58,6 +99,30 @@ describe('SyntheticsService', () => {
|
|||
status: LocationStatus.GA,
|
||||
};
|
||||
});
|
||||
serverMock.config = { service: { devUrl: 'http://localhost' } };
|
||||
if (serverMock.savedObjectsClient) {
|
||||
serverMock.savedObjectsClient.find = jest.fn().mockResolvedValue({
|
||||
saved_objects: [
|
||||
getFakePayload([
|
||||
{
|
||||
id: `loc-1`,
|
||||
label: `Location 1`,
|
||||
url: `https://example.com/1`,
|
||||
geo: {
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
},
|
||||
isServiceManaged: true,
|
||||
status: LocationStatus.GA,
|
||||
},
|
||||
]),
|
||||
],
|
||||
total: 1,
|
||||
per_page: 20,
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
const service = new SyntheticsService(serverMock);
|
||||
|
||||
service.apiClient.locations = locations;
|
||||
|
||||
|
@ -66,28 +131,9 @@ describe('SyntheticsService', () => {
|
|||
return { service, locations };
|
||||
};
|
||||
|
||||
const getFakePayload = (locations: HeartbeatConfig['locations']) => {
|
||||
return {
|
||||
type: 'http',
|
||||
enabled: true,
|
||||
schedule: {
|
||||
number: '3',
|
||||
unit: 'm',
|
||||
},
|
||||
name: 'my mon',
|
||||
locations,
|
||||
urls: 'http://google.com',
|
||||
max_redirects: '0',
|
||||
password: '',
|
||||
proxy_url: '',
|
||||
id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d',
|
||||
fields: { config_id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d' },
|
||||
fields_under_root: true,
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(axios as jest.MockedFunction<typeof axios>).mockReset();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
@ -174,6 +220,101 @@ describe('SyntheticsService', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('includes the license level flag on edit requests', async () => {
|
||||
const { service, locations } = getMockedService();
|
||||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
const payload = getFakePayload([locations[0]]);
|
||||
|
||||
await service.editConfig({ monitor: payload } as any);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ license_level: 'platinum' }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('includes the license level flag on add config requests', async () => {
|
||||
const { service, locations } = getMockedService();
|
||||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
const payload = getFakePayload([locations[0]]);
|
||||
|
||||
await service.addConfig({ monitor: payload } as any);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ license_level: 'platinum' }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('includes the license level flag on push configs requests', async () => {
|
||||
const { service, locations } = getMockedService();
|
||||
|
||||
serverMock.encryptedSavedObjects = mockEncryptedSO({
|
||||
attributes: getFakePayload([locations[0]]),
|
||||
}) as any;
|
||||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
await service.pushConfigs();
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ license_level: 'platinum' }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[true, 'Cannot sync monitors with the Synthetics service. License is expired.'],
|
||||
[
|
||||
false,
|
||||
'Cannot sync monitors with the Synthetics service. Unable to determine license level.',
|
||||
],
|
||||
])(
|
||||
'does not call api when license is expired or unavailable',
|
||||
async (isExpired, errorMessage) => {
|
||||
const { service, locations } = getMockedService();
|
||||
|
||||
mockCoreStart.elasticsearch.client.asInternalUser.license.get = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
license: isExpired
|
||||
? {
|
||||
status: 'expired',
|
||||
uid: 'c5788419-1c6f-424a-9217-da7a0a9151a0',
|
||||
type: 'platinum',
|
||||
issue_date: '2022-11-29T00:00:00.000Z',
|
||||
issue_date_in_millis: 1669680000000,
|
||||
expiry_date: '2022-12-31T23:59:59.999Z',
|
||||
expiry_date_in_millis: 1735689599999,
|
||||
max_nodes: 100,
|
||||
max_resource_units: null,
|
||||
issued_to: 'Elastic - INTERNAL (development environments)',
|
||||
issuer: 'API',
|
||||
start_date_in_millis: 1669680000000,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
serverMock.encryptedSavedObjects = mockEncryptedSO({
|
||||
attributes: getFakePayload([locations[0]]),
|
||||
}) as any;
|
||||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
await expect(service.pushConfigs()).rejects.toThrow(errorMessage);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('getSyntheticsParams', () => {
|
||||
|
@ -182,6 +323,11 @@ describe('SyntheticsService', () => {
|
|||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
serverMock.encryptedSavedObjects = mockEncryptedSO({
|
||||
attributes: { key: 'username', value: 'elastic' },
|
||||
namespaces: ['*'],
|
||||
}) as any;
|
||||
|
||||
const params = await service.getSyntheticsParams();
|
||||
|
||||
expect(params).toEqual({
|
||||
|
@ -190,6 +336,7 @@ describe('SyntheticsService', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the params for specific space', async () => {
|
||||
const { service } = getMockedService();
|
||||
|
||||
|
@ -207,9 +354,10 @@ describe('SyntheticsService', () => {
|
|||
it('returns the space limited params', async () => {
|
||||
const { service } = getMockedService();
|
||||
|
||||
serverMock.encryptedSavedObjects = mockEncryptedSO([
|
||||
{ attributes: { key: 'username', value: 'elastic' }, namespaces: ['default'] },
|
||||
]) as any;
|
||||
serverMock.encryptedSavedObjects = mockEncryptedSO({
|
||||
attributes: { key: 'username', value: 'elastic' },
|
||||
namespaces: ['default'],
|
||||
}) as any;
|
||||
|
||||
const params = await service.getSyntheticsParams({ spaceId: 'default' });
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { Logger, SavedObject } from '@kbn/core/server';
|
||||
import { Logger, SavedObject, ElasticsearchClient } from '@kbn/core/server';
|
||||
import {
|
||||
ConcreteTaskInstance,
|
||||
TaskInstance,
|
||||
|
@ -56,6 +56,7 @@ const SYNTHETICS_SERVICE_SYNC_INTERVAL_DEFAULT = '5m';
|
|||
|
||||
export class SyntheticsService {
|
||||
private logger: Logger;
|
||||
private esClient?: ElasticsearchClient;
|
||||
private readonly server: UptimeServerSetup;
|
||||
public apiClient: ServiceAPIClient;
|
||||
|
||||
|
@ -242,6 +243,42 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
private async getLicense() {
|
||||
this.esClient = this.getESClient();
|
||||
let license;
|
||||
if (this.esClient === undefined || this.esClient === null) {
|
||||
throw Error(
|
||||
'Cannot sync monitors with the Synthetics service. Elasticsearch client is unavailable: cannot retrieve license information'
|
||||
);
|
||||
}
|
||||
try {
|
||||
license = (await this.esClient.license.get())?.license;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot sync monitors with the Synthetics service. Unable to determine license level: ${e}`
|
||||
);
|
||||
}
|
||||
|
||||
if (license?.status === 'expired') {
|
||||
throw new Error('Cannot sync monitors with the Synthetics service. License is expired.');
|
||||
}
|
||||
|
||||
if (!license?.type) {
|
||||
throw new Error(
|
||||
'Cannot sync monitors with the Synthetics service. Unable to determine license level.'
|
||||
);
|
||||
}
|
||||
|
||||
return license;
|
||||
}
|
||||
|
||||
private getESClient() {
|
||||
if (!this.server.coreStart) {
|
||||
return;
|
||||
}
|
||||
return this.server.coreStart?.elasticsearch.client.asInternalUser;
|
||||
}
|
||||
|
||||
async getOutput() {
|
||||
const { apiKey, isValid } = await getAPIKeyForSyntheticsService({ server: this.server });
|
||||
if (!isValid) {
|
||||
|
@ -261,6 +298,7 @@ export class SyntheticsService {
|
|||
async addConfig(config: ConfigData | ConfigData[]) {
|
||||
try {
|
||||
const monitors = this.formatConfigs(Array.isArray(config) ? config : [config]);
|
||||
const license = await this.getLicense();
|
||||
|
||||
const output = await this.getOutput();
|
||||
if (output) {
|
||||
|
@ -269,6 +307,7 @@ export class SyntheticsService {
|
|||
this.syncErrors = await this.apiClient.post({
|
||||
monitors,
|
||||
output,
|
||||
licenseLevel: license.type,
|
||||
});
|
||||
}
|
||||
return this.syncErrors;
|
||||
|
@ -279,6 +318,7 @@ export class SyntheticsService {
|
|||
|
||||
async editConfig(monitorConfig: ConfigData | ConfigData[], isEdit = true) {
|
||||
try {
|
||||
const license = await this.getLicense();
|
||||
const monitors = this.formatConfigs(
|
||||
Array.isArray(monitorConfig) ? monitorConfig : [monitorConfig]
|
||||
);
|
||||
|
@ -289,6 +329,7 @@ export class SyntheticsService {
|
|||
monitors,
|
||||
output,
|
||||
isEdit,
|
||||
licenseLevel: license.type,
|
||||
};
|
||||
|
||||
this.syncErrors = await this.apiClient.put(data);
|
||||
|
@ -300,6 +341,7 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
async pushConfigs() {
|
||||
const license = await this.getLicense();
|
||||
const service = this;
|
||||
const subject = new Subject<MonitorFields[]>();
|
||||
|
||||
|
@ -326,6 +368,7 @@ export class SyntheticsService {
|
|||
service.syncErrors = await this.apiClient.put({
|
||||
monitors,
|
||||
output,
|
||||
licenseLevel: license.type,
|
||||
});
|
||||
} catch (e) {
|
||||
sendErrorTelemetryEvents(service.logger, service.server.telemetry, {
|
||||
|
@ -344,6 +387,7 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
async runOnceConfigs(configs: ConfigData) {
|
||||
const license = await this.getLicense();
|
||||
const monitors = this.formatConfigs(configs);
|
||||
if (monitors.length === 0) {
|
||||
return;
|
||||
|
@ -358,6 +402,7 @@ export class SyntheticsService {
|
|||
return await this.apiClient.runOnce({
|
||||
monitors,
|
||||
output,
|
||||
licenseLevel: license.type,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
|
@ -366,6 +411,7 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
async deleteConfigs(configs: ConfigData[]) {
|
||||
const license = await this.getLicense();
|
||||
const hasPublicLocations = configs.some((config) =>
|
||||
config.monitor.locations.some(({ isServiceManaged }) => isServiceManaged)
|
||||
);
|
||||
|
@ -379,12 +425,14 @@ export class SyntheticsService {
|
|||
const data = {
|
||||
output,
|
||||
monitors: this.formatConfigs(configs),
|
||||
licenseLevel: license.type,
|
||||
};
|
||||
return await this.apiClient.delete(data);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllConfigs() {
|
||||
const license = await this.getLicense();
|
||||
const subject = new Subject<MonitorFields[]>();
|
||||
|
||||
subject.subscribe(async (monitors) => {
|
||||
|
@ -401,6 +449,7 @@ export class SyntheticsService {
|
|||
const data = {
|
||||
output,
|
||||
monitors,
|
||||
licenseLevel: license.type,
|
||||
};
|
||||
return await this.apiClient.delete(data);
|
||||
}
|
||||
|
|
|
@ -8,16 +8,16 @@
|
|||
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
|
||||
export const mockEncryptedSO = (
|
||||
data: any = [{ attributes: { key: 'username', value: 'elastic' }, namespaces: ['*'] }]
|
||||
data: any = { attributes: { key: 'username', value: 'elastic' }, namespaces: ['*'] }
|
||||
) => ({
|
||||
getClient: jest.fn().mockReturnValue({
|
||||
getDecryptedAsInternalUser: jest.fn(),
|
||||
getDecryptedAsInternalUser: jest.fn().mockResolvedValue(data),
|
||||
createPointInTimeFinderDecryptedAsInternalUser: jest.fn().mockImplementation(() => ({
|
||||
close: jest.fn(),
|
||||
find: jest.fn().mockReturnValue({
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
saved_objects: data,
|
||||
saved_objects: [data],
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue