[EMS] Update EMS Client with serverless version and header (#163098)

Part of #157094
Fixes #161790

## Summary

* Upgrade to `@elastic/ems-client@8.5.0`
* Updates the instantiation of the EMS Client to pass the version and
HTTP header on Serverless builds.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

## Testing

The serverless update to EMS Tile Service is ready in Production.
Running Kibana in serverless mode, you can check for requests to
`https://tiles.maps.elastic.dev/latest/manifest` and the associated
`elastic-api-version` header being accepted.

EMS File Service already accepts the `latest/manifest` endpoint in our
production environment.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jorge Sanz 2023-10-17 13:46:14 +02:00 committed by GitHub
parent d7b9a2ab71
commit 6caf0327ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 12 deletions

View file

@ -101,7 +101,7 @@
"@elastic/charts": "60.0.0",
"@elastic/datemath": "5.0.3",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1",
"@elastic/ems-client": "8.4.0",
"@elastic/ems-client": "8.5.0",
"@elastic/eui": "89.0.0",
"@elastic/filesaver": "1.1.2",
"@elastic/node-crypto": "1.2.1",

View file

@ -84,7 +84,7 @@ export const PER_PACKAGE_ALLOWED_LICENSES = {
export const LICENSE_OVERRIDES = {
'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
'@elastic/ems-client@8.4.0': ['Elastic License 2.0'],
'@elastic/ems-client@8.5.0': ['Elastic License 2.0'],
'@elastic/eui@89.0.0': ['SSPL-1.0 OR Elastic License 2.0'],
'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODCBy license https://github.com/mattcg/language-subtag-registry
'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary

View file

@ -17,3 +17,5 @@ export const DEFAULT_EMS_ROADMAP_DESATURATED_ID = 'road_map_desaturated';
export const DEFAULT_EMS_DARKMAP_ID = 'dark_map';
export const EMS_APP_NAME = 'kibana'; // app-name submitted as the `app`-param to EMS
export const DEFAULT_EMS_REST_VERSION = '2023-10-31';

View file

@ -14,6 +14,7 @@ export {
DEFAULT_EMS_ROADMAP_ID,
DEFAULT_EMS_ROADMAP_DESATURATED_ID,
DEFAULT_EMS_DARKMAP_ID,
DEFAULT_EMS_REST_VERSION,
EMS_APP_NAME,
} from './ems_defaults';

View file

@ -0,0 +1,116 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { EMSSettings } from '../../common/ems_settings';
import {
DEFAULT_EMS_FILE_API_URL,
DEFAULT_EMS_FONT_LIBRARY_URL,
DEFAULT_EMS_LANDING_PAGE_URL,
DEFAULT_EMS_TILE_API_URL,
DEFAULT_EMS_REST_VERSION,
} from '../../common/ems_defaults';
import { createEMSClient } from './create_ems_client';
import type { EMSConfig } from '../../common/ems_settings';
import { BuildFlavor } from '@kbn/config/src/types';
import { LATEST_API_URL_PATH } from '@elastic/ems-client';
const IS_ENTERPRISE_PLUS = () => true;
describe('createEMSClient', () => {
const mockConfig: EMSConfig = {
includeElasticMapsService: true,
emsUrl: '',
emsFileApiUrl: DEFAULT_EMS_FILE_API_URL,
emsTileApiUrl: DEFAULT_EMS_TILE_API_URL,
emsLandingPageUrl: DEFAULT_EMS_LANDING_PAGE_URL,
emsFontLibraryUrl: DEFAULT_EMS_FONT_LIBRARY_URL,
};
const emsSettings = new EMSSettings(mockConfig, IS_ENTERPRISE_PLUS);
describe('settings for traditional (SemVer)', () => {
const kbnVersion = '8.7.6';
const minorKbnVersion = 'v8.7';
const build: BuildFlavor = 'traditional';
const emsClient = createEMSClient(emsSettings, kbnVersion, build);
test('should point to the /vX.Y folder on traditional SemVer (X.Y.Z)', () => {
expect(emsClient.getLandingPageUrl()).toBe(
DEFAULT_EMS_LANDING_PAGE_URL + '/' + minorKbnVersion
);
});
test('client should use the Kibana Version as my_app_version query param', () => {
const clientParams = new URLSearchParams(emsClient.extendUrlWithParams('https://my.host'));
expect(clientParams.get('my_app_version')).toBe(kbnVersion);
});
test('client should point to /vX.Y on the services URLs ', async () => {
const { services } = await emsClient.getMainManifest();
['tms', 'file'].map((type) => {
const service = services.find((s) => s.type === type);
expect(service?.manifest.match(minorKbnVersion));
});
});
test('fetch function should not include the serverless header', async () => {
global.fetch = jest.fn((_, { headers }: { headers: Headers }) => {
expect(headers.has(ELASTIC_HTTP_VERSION_HEADER)).toBeFalsy();
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ layers: [{ layer_id: 'mock_1' }] }),
});
}) as jest.Mock;
const mocked = await emsClient.getFileLayers();
// Ensure we ran the mocked function
expect(mocked[0].getId()).toBe('mock_1');
});
});
describe('Override settings for serverless', () => {
const kbnVersion: string = 'foo';
const build: BuildFlavor = 'serverless';
const emsClient = createEMSClient(emsSettings, kbnVersion, build);
test('should point to the root', () => {
expect(emsClient.getLandingPageUrl()).toBe(DEFAULT_EMS_LANDING_PAGE_URL);
});
test('client should use DEFAULT_EMS_REST_VERSION as my_app_version query param ', () => {
const clientParams = new URLSearchParams(emsClient.extendUrlWithParams('https://my.host'));
expect(clientParams.get('my_app_version')).toBe(DEFAULT_EMS_REST_VERSION);
});
test('client should point to /LATEST_API_URL_PATH on the services URLs ', async () => {
const { services } = await emsClient.getMainManifest();
['tms', 'file'].map((type) => {
const service = services.find((s) => s.type === type);
expect(service?.manifest.match(LATEST_API_URL_PATH));
});
});
test('fetch function should include the serverless header', async () => {
global.fetch = jest.fn((_, { headers }: { headers: Headers }) => {
expect(headers.get(ELASTIC_HTTP_VERSION_HEADER)).toBe(DEFAULT_EMS_REST_VERSION);
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ layers: [{ layer_id: 'mock_2' }] }),
});
}) as jest.Mock;
const mocked = await emsClient.getFileLayers();
// Ensure we ran the mocked function
expect(mocked[0].getId()).toBe('mock_2');
});
});
});

View file

@ -8,10 +8,11 @@
import coerce from 'semver/functions/coerce';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { BuildFlavor } from '@kbn/config/src/types';
import { i18n } from '@kbn/i18n';
import { EMSClient } from '@elastic/ems-client';
import { EMS_APP_NAME, EMSSettings } from '../../common';
import { EMS_APP_NAME, EMSSettings, DEFAULT_EMS_REST_VERSION } from '../../common';
export function createEMSClient(
emsSettings: EMSSettings,
@ -20,22 +21,30 @@ export function createEMSClient(
): EMSClient {
let landingPageUrl = emsSettings!.getEMSLandingPageUrl();
const kbnSemVer = coerce(kbnVersion);
const isServerless = buildFlavor === 'serverless';
const headers = new Headers();
if (buildFlavor === 'traditional' && kbnSemVer) {
if (!isServerless && kbnSemVer) {
landingPageUrl = `${landingPageUrl}/v${kbnSemVer.major}.${kbnSemVer.minor}`;
}
if (isServerless) {
headers.append(ELASTIC_HTTP_VERSION_HEADER, DEFAULT_EMS_REST_VERSION);
}
const version = isServerless ? DEFAULT_EMS_REST_VERSION : kbnVersion;
return new EMSClient({
language: i18n.getLocale(),
appVersion: kbnVersion,
appVersion: version,
emsVersion: version,
appName: EMS_APP_NAME,
tileApiUrl: emsSettings!.getEMSTileApiUrl(),
fileApiUrl: emsSettings!.getEMSFileApiUrl(),
landingPageUrl,
fetchFunction(url: string) {
return fetch(url);
return fetch(url, { headers });
},
proxyPath: '',
emsVersion: kbnVersion,
});
}

View file

@ -10,6 +10,7 @@
"@kbn/i18n",
"@kbn/config-schema",
"@kbn/config",
"@kbn/core-http-common",
],
"exclude": [
"target/**/*",

View file

@ -1605,10 +1605,10 @@
"@elastic/transport" "^8.3.3"
tslib "^2.4.0"
"@elastic/ems-client@8.4.0":
version "8.4.0"
resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.4.0.tgz#e77bbddda998a77e29b86767fe4ff403c5a62133"
integrity sha512-uMxZl6BxCPLvIJiG80gqQbOlxKqNLYCo0KtHDFVwUGpTgUQnrVzAZUJ9PZ7fSD7cUDW6pE+QekDddDxB2nOCwA==
"@elastic/ems-client@8.5.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.5.0.tgz#5e3988ab01dee54bace9f8c2f9e71470b26a1bfa"
integrity sha512-uEI8teyS3gWErlEL4mEYh0MDHms9Rp3eEHKnMMSdMtAkaa3xs60SOm3Vd0ld9sIPsyarQHrAybAw30GXVXXRjw==
dependencies:
"@types/geojson" "^7946.0.10"
"@types/lru-cache" "^5.1.0"
@ -1617,7 +1617,7 @@
chroma-js "^2.1.0"
lodash "^4.17.15"
lru-cache "^6.0.0"
semver "^7.3.8"
semver "7.5.4"
topojson-client "^3.1.0"
"@elastic/eslint-plugin-eui@0.0.2":
@ -27141,6 +27141,13 @@ semver@5.6.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
semver@7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"