Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose decoded cloudId components from the cloud plugin's contract #159442

Merged
merged 18 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
* 2.0.
*/

import { loggerMock, MockedLogger } from '@kbn/logging-mocks';
import { decodeCloudId } from './decode_cloud_id';

describe('Fleet - decodeCloudId', () => {
let logger: MockedLogger;

beforeEach(() => {
logger = loggerMock.create();
});

it('parses various CloudID formats', () => {
const tests = [
{
Expand Down Expand Up @@ -73,7 +80,7 @@ describe('Fleet - decodeCloudId', () => {
];

for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
const decoded = decodeCloudId(test.cloudID, logger);
expect(decoded).toBeTruthy();
expect(decoded?.elasticsearchUrl === test.expectedEsURL).toBe(true);
expect(decoded?.kibanaUrl === test.expectedKibanaURL).toBe(true);
Expand All @@ -94,7 +101,7 @@ describe('Fleet - decodeCloudId', () => {
];

for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
const decoded = decodeCloudId(test.cloudID, logger);
expect(decoded).toBe(undefined);
// decodeCloudId currently only logs; not throws errors
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
* 2.0.
*/

import type { Logger } from '@kbn/logging';

export interface DecodedCloudId {
host: string;
defaultPort: string;
elasticsearchUrl: string;
kibanaUrl: string;
}

// decodeCloudId decodes the c.id into c.esURL and c.kibURL
export function decodeCloudId(cid: string):
| {
host: string;
defaultPort: string;
elasticsearchUrl: string;
kibanaUrl: string;
}
| undefined {
export function decodeCloudId(cid: string, logger: Logger): DecodedCloudId | undefined {
// 1. Ignore anything before `:`.
const id = cid.split(':').pop();
if (!id) {
// throw new Error(`Unable to decode ${id}`);
// eslint-disable-next-line no-console
console.debug(`Unable to decode ${id}`);
logger.debug(`Unable to decode ${id}`);
return;
}

Expand All @@ -28,18 +28,14 @@ export function decodeCloudId(cid: string):
try {
decoded = Buffer.from(id, 'base64').toString('utf8');
} catch {
// throw new Error(`base64 decoding failed on ${id}`);
// eslint-disable-next-line no-console
console.debug(`base64 decoding failed on ${id}`);
logger.debug(`base64 decoding failed on ${id}`);
return;
}

// 3. separate based on `$`
const words = decoded.split('$');
if (words.length < 3) {
// throw new Error(`Expected at least 3 parts in ${decoded}`);
// eslint-disable-next-line no-console
console.debug(`Expected at least 3 parts in ${decoded}`);
logger.debug(`Expected at least 3 parts in ${decoded}`);
return;
}
// 4. extract port from the ES and Kibana host
Expand All @@ -56,6 +52,7 @@ export function decodeCloudId(cid: string):
kibanaUrl: kbUrl,
};
}

// extractPortFromName takes a string in the form `id:port` and returns the
// Id and the port. If there's no `:`, the default port is returned
function extractPortFromName(word: string, defaultPort = '443') {
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/cloud/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import { PluginInitializerContext } from '@kbn/core/public';
import { CloudPlugin } from './plugin';

export type { CloudSetup, CloudConfigType, CloudStart } from './plugin';
export type { CloudSetup, CloudStart } from './types';
export type { CloudConfigType } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
return new CloudPlugin(initializerContext);
Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/cloud/public/mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@

import React from 'react';

import { CloudStart } from '.';
import type { CloudSetup, CloudStart } from './types';

function createSetupMock() {
function createSetupMock(): jest.Mocked<CloudSetup> {
return {
cloudId: 'mock-cloud-id',
deploymentId: 'mock-deployment-id',
isCloudEnabled: true,
cname: 'cname',
baseUrl: 'base-url',
deploymentUrl: 'deployment-url',
profileUrl: 'profile-url',
organizationUrl: 'organization-url',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
cloudHost: 'cloud-host',
cloudDefaultPort: '443',
isElasticStaffOwned: true,
trialEndDate: new Date('2020-10-01T14:13:12Z'),
registerCloudService: jest.fn(),
Expand Down
22 changes: 22 additions & 0 deletions x-pack/plugins/cloud/public/plugin.test.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.
*/

export const parseDeploymentIdFromDeploymentUrlMock = jest.fn();

jest.doMock('../common/parse_deployment_id_from_deployment_url', () => {
return {
parseDeploymentIdFromDeploymentUrl: parseDeploymentIdFromDeploymentUrlMock,
};
});

export const decodeCloudIdMock = jest.fn();

jest.doMock('../common/decode_cloud_id', () => {
return {
decodeCloudId: decodeCloudIdMock,
};
});
38 changes: 38 additions & 0 deletions x-pack/plugins/cloud/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* 2.0.
*/

import { decodeCloudIdMock, parseDeploymentIdFromDeploymentUrlMock } from './plugin.test.mocks';
import { coreMock } from '@kbn/core/public/mocks';
import { CloudPlugin } from './plugin';
import type { DecodedCloudId } from '../common/decode_cloud_id';

const baseConfig = {
base_url: 'https://cloud.elastic.co',
Expand All @@ -16,6 +18,11 @@ const baseConfig = {
};

describe('Cloud Plugin', () => {
beforeEach(() => {
parseDeploymentIdFromDeploymentUrlMock.mockReset().mockReturnValue('deployment-id');
decodeCloudIdMock.mockReset().mockReturnValue({});
});

describe('#setup', () => {
describe('interface', () => {
const setupPlugin = () => {
Expand Down Expand Up @@ -76,6 +83,37 @@ describe('Cloud Plugin', () => {
const { setup } = setupPlugin();
expect(setup.registerCloudService).toBeDefined();
});

it('exposes deploymentId', () => {
parseDeploymentIdFromDeploymentUrlMock.mockReturnValue('some-deployment-id');
const { setup } = setupPlugin();
expect(setup.deploymentId).toBe('some-deployment-id');
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledTimes(2); // called when registering the analytic context too
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledWith(
baseConfig.deployment_url
);
});

it('exposes components decoded from the cloudId', () => {
const decodedId: DecodedCloudId = {
defaultPort: '9000',
host: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
};
decodeCloudIdMock.mockReturnValue(decodedId);
const { setup } = setupPlugin();
expect(setup).toEqual(
expect.objectContaining({
cloudDefaultPort: '9000',
cloudHost: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
})
);
expect(decodeCloudIdMock).toHaveBeenCalledTimes(1);
expect(decodeCloudIdMock).toHaveBeenCalledWith('cloudId', expect.any(Object));
});
});
});

Expand Down
92 changes: 16 additions & 76 deletions x-pack/plugins/cloud/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
*/

import React, { FC } from 'react';
import type { Logger } from '@kbn/logging';
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';

import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url';
import { ELASTIC_SUPPORT_LINK, CLOUD_SNAPSHOTS_PATH } from '../common/constants';
import { decodeCloudId, type DecodedCloudId } from '../common/decode_cloud_id';
import type { CloudSetup, CloudStart } from './types';
import { getFullCloudUrl } from './utils';

export interface CloudConfigType {
Expand All @@ -24,81 +27,6 @@ export interface CloudConfigType {
is_elastic_staff_owned?: boolean;
}

export interface CloudStart {
/**
* A React component that provides a pre-wired `React.Context` which connects components to Cloud services.
*/
CloudContextProvider: FC<{}>;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
}

export interface CloudSetup {
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* This value is the same as `baseUrl` on ESS but can be customized on ECE.
*/
cname?: string;
/**
* This is the URL of the Cloud interface.
*/
baseUrl?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
/**
* This is the path to the Snapshots page for the deployment to which the Kibana instance belongs. The value is already prepended with `deploymentUrl`.
*/
snapshotsUrl?: string;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* When the Cloud Trial ends/ended for the organization that owns this deployment. Only available when running on Elastic Cloud.
*/
trialEndDate?: Date;
/**
* `true` if the Elastic Cloud organization that owns this deployment is owned by an Elastician. Only available when running on Elastic Cloud.
*/
isElasticStaffOwned?: boolean;
/**
* Registers CloudServiceProviders so start's `CloudContextProvider` hooks them.
* @param contextProvider The React component from the Service Provider.
*/
registerCloudService: (contextProvider: FC) => void;
}

interface CloudUrls {
deploymentUrl?: string;
profileUrl?: string;
Expand All @@ -110,10 +38,12 @@ export class CloudPlugin implements Plugin<CloudSetup> {
private readonly config: CloudConfigType;
private readonly isCloudEnabled: boolean;
private readonly contextProviders: FC[] = [];
private readonly logger: Logger;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<CloudConfigType>();
this.isCloudEnabled = getIsCloudEnabled(this.config.id);
this.logger = initializerContext.logger.get();
}

public setup(core: CoreSetup): CloudSetup {
Expand All @@ -127,11 +57,21 @@ export class CloudPlugin implements Plugin<CloudSetup> {
is_elastic_staff_owned: isElasticStaffOwned,
} = this.config;

let decodedId: DecodedCloudId | undefined;
if (id) {
decodedId = decodeCloudId(id, this.logger);
}

return {
cloudId: id,
deploymentId: parseDeploymentIdFromDeploymentUrl(this.config.deployment_url),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the deploymentId (which was only exposed on the server-side contract) while I was at it, given some plugins recoded their helper to access it.

cname,
baseUrl,
...this.getCloudUrls(),
elasticsearchUrl: decodedId?.elasticsearchUrl,
kibanaUrl: decodedId?.kibanaUrl,
cloudHost: decodedId?.host,
cloudDefaultPort: decodedId?.defaultPort,
trialEndDate: trialEndDate ? new Date(trialEndDate) : undefined,
isElasticStaffOwned,
isCloudEnabled: this.isCloudEnabled,
Expand Down
Loading
Loading