Skip to content

Commit

Permalink
[Uptimes] Push configs to service (#120069) (#120458)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
shahzad31 and kibanamachine authored Dec 6, 2021
1 parent f8cb87b commit fbebe24
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 51 deletions.
5 changes: 3 additions & 2 deletions x-pack/plugins/uptime/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ export interface MonitorIdParam {

export type SyntheticsMonitorSavedObject = SimpleSavedObject<{
name: string;
runOnce: boolean;
runOnce?: boolean;
urls?: string[];
tags?: string[];
locations: string[];
schedule: string;
type: 'http' | 'tcp' | 'icmp' | 'browser';
source?: {
Expand All @@ -59,4 +60,4 @@ export interface ManifestLocation {
status: string;
}

export type ServiceLocations = Array<{ id: string; label: string; geo: LocationGeo }>;
export type ServiceLocations = Array<{ id: string; label: string; geo: LocationGeo; url: string }>;
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { SecurityPluginStart } from '../../../../../security/server';
import { CloudSetup } from '../../../../../cloud/server';
import { FleetStartContract } from '../../../../../fleet/server';
import { UptimeConfig } from '../../../../common/config';
import { SyntheticsService } from '../../synthetics_service/synthetics_service';

export type UMElasticsearchQueryFn<P, R = any> = (
params: {
Expand All @@ -47,6 +48,7 @@ export interface UptimeServerSetup {
security: SecurityPluginStart;
savedObjectsClient: SavedObjectsClientContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
syntheticsService: SyntheticsService;
}

export interface UptimeCorePluginsSetup {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('getAPIKeyTest', function () {
cluster: ['monitor', 'read_ilm', 'read_pipeline'],
index: [
{
names: ['synthetics-*'],
names: ['synthetics-*', 'heartbeat-*'],
privileges: ['view_index_metadata', 'create_doc', 'auto_configure'],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ export const getAPIKeyForSyntheticsService = async ({
includedHiddenTypes: [syntheticsServiceApiKey.name],
});

const apiKey = await getSyntheticsServiceAPIKey(encryptedClient);
if (apiKey) {
return apiKey;
try {
const apiKey = await getSyntheticsServiceAPIKey(encryptedClient);
if (apiKey) {
return apiKey;
}
} catch (err) {
// TODO: figure out how to handle decryption errors
}

return await generateAndSaveAPIKey({ request, security, savedObjectsClient });
};

Expand Down Expand Up @@ -61,7 +66,7 @@ export const generateAndSaveAPIKey = async ({
cluster: ['monitor', 'read_ilm', 'read_pipeline'],
index: [
{
names: ['synthetics-*'],
names: ['synthetics-*', 'heartbeat-*'],
privileges: ['view_index_metadata', 'create_doc', 'auto_configure'],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,7 @@ describe('getServiceLocations', function () {
});
it('should return parsed locations', async () => {
const locations = await getServiceLocations({
config: {
unsafe: {
service: {
manifestUrl: 'http://local.dev',
},
},
},
manifestUrl: 'http://local.dev',
});

expect(locations).toEqual([
Expand All @@ -44,6 +38,7 @@ describe('getServiceLocations', function () {
},
id: 'us_central',
label: 'US Central',
url: 'https://local.dev',
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@
*/

import axios from 'axios';
import { UptimeConfig } from '../../../common/config';
import { ManifestLocation, ServiceLocations } from '../../../common/types';

export async function getServiceLocations({ config }: { config: UptimeConfig }) {
const manifestURL = config.unsafe.service.manifestUrl;
export async function getServiceLocations({ manifestUrl }: { manifestUrl: string }) {
const locations: ServiceLocations = [];
try {
const { data } = await axios.get<Record<string, ManifestLocation>>(manifestURL);
const { data } = await axios.get<Record<string, ManifestLocation>>(manifestUrl);

Object.entries(data.locations).forEach(([locationId, location]) => {
locations.push({
id: locationId,
label: location.geo.name,
geo: location.geo.location,
url: location.url,
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import axios from 'axios';
import { forkJoin, from as rxjsFrom, Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { ServiceLocations, SyntheticsMonitorSavedObject } from '../../../common/types';
import { getServiceLocations } from './get_service_locations';
import { Logger } from '../../../../../../src/core/server';

const TEST_SERVICE_USERNAME = 'localKibanaIntegrationTestsUser';

export type MonitorConfigs = Array<
SyntheticsMonitorSavedObject['attributes'] & {
id: string;
source?: {
inline: {
script: string;
};
};
}
>;

export interface ServiceData {
monitors: MonitorConfigs;
output: {
hosts: string[];
api_key: string;
};
}

export class ServiceAPIClient {
private readonly username: string;
private readonly authorization: string;
private locations: ServiceLocations;
private logger: Logger;

constructor(manifestUrl: string, username: string, password: string, logger: Logger) {
this.username = username;
this.authorization = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
this.logger = logger;
this.locations = [];

getServiceLocations({ manifestUrl }).then((result) => {
this.locations = result;
});
}

async post(data: ServiceData) {
return this.callAPI('POST', data);
}

async put(data: ServiceData) {
return this.callAPI('POST', data);
}

async delete(data: ServiceData) {
return this.callAPI('DELETE', data);
}

async callAPI(method: 'POST' | 'PUT' | 'DELETE', { monitors: allMonitors, output }: ServiceData) {
if (this.username === TEST_SERVICE_USERNAME) {
// we don't want to call service while local integration tests are running
return;
}

const callServiceEndpoint = (monitors: ServiceData['monitors'], url: string) => {
return axios({
method,
url: url + '/monitors',
data: { monitors, output },
headers: {
Authorization: this.authorization,
},
});
};

const pushErrors: Array<{ locationId: string; error: Error }> = [];

const promises: Array<Observable<unknown>> = [];

this.locations.forEach(({ id, url }) => {
const locMonitors = allMonitors.filter(
({ locations }) => !locations || locations?.includes(id)
);
if (locMonitors.length > 0) {
promises.push(
rxjsFrom(callServiceEndpoint(locMonitors, url)).pipe(
tap((result) => {
this.logger.debug(result.data);
}),
catchError((err) => {
pushErrors.push({ locationId: id, error: err });
this.logger.error(err);
// we don't want to throw an unhandled exception here
return of(true);
})
)
);
}
});

await forkJoin(promises).toPromise();

return pushErrors;
}
}
Loading

0 comments on commit fbebe24

Please sign in to comment.