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

[cloud plugin] Add serverless projectId to configuration and contract #161728

Merged
merged 7 commits into from
Jul 14, 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 @@ -221,6 +221,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.cloud.organization_url (string)',
'xpack.cloud.billing_url (string)',
'xpack.cloud.profile_url (string)',
// can't be used to infer urls or customer id from the outside
'xpack.cloud.serverless.project_id (string)',
Copy link
Member

Choose a reason for hiding this comment

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

optional nit: it'd be great to leave a comment saying that it doesn't include anything sensitive, just in case we have to conduct an audit of this file at some point.

'xpack.discoverEnhanced.actions.exploreDataInChart.enabled (boolean)',
'xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled (boolean)',
'xpack.fleet.agents.enabled (boolean)',
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/cloud/public/mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ function createSetupMock(): jest.Mocked<CloudSetup> {
isElasticStaffOwned: true,
trialEndDate: new Date('2020-10-01T14:13:12Z'),
registerCloudService: jest.fn(),
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
}

Expand All @@ -42,6 +46,10 @@ const createStartMock = (): jest.Mocked<CloudStart> => ({
billingUrl: 'billing-url',
profileUrl: 'profile-url',
organizationUrl: 'organization-url',
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
});

export const cloudMock = {
Expand Down
68 changes: 65 additions & 3 deletions x-pack/plugins/cloud/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

const baseConfig = {
Expand All @@ -25,11 +25,12 @@ describe('Cloud Plugin', () => {

describe('#setup', () => {
describe('interface', () => {
const setupPlugin = () => {
const setupPlugin = (configParts: Partial<CloudConfigType> = {}) => {
const initContext = coreMock.createPluginInitializerContext({
...baseConfig,
id: 'cloudId',
cname: 'cloud.elastic.co',
...configParts,
});
const plugin = new CloudPlugin(initContext);

Expand Down Expand Up @@ -114,11 +115,38 @@ describe('Cloud Plugin', () => {
expect(decodeCloudIdMock).toHaveBeenCalledTimes(1);
expect(decodeCloudIdMock).toHaveBeenCalledWith('cloudId', expect.any(Object));
});

describe('isServerlessEnabled', () => {
it('is `true` when `serverless.projectId` is set', () => {
const { setup } = setupPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
expect(setup.isServerlessEnabled).toBe(true);
});

it('is `false` when `serverless.projectId` is not set', () => {
const { setup } = setupPlugin({
serverless: undefined,
});
expect(setup.isServerlessEnabled).toBe(false);
});
});

it('exposes `serverless.projectId`', () => {
const { setup } = setupPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
expect(setup.serverless.projectId).toBe('my-awesome-project');
});
});
});

describe('#start', () => {
const startPlugin = () => {
const startPlugin = (configParts: Partial<CloudConfigType> = {}) => {
const plugin = new CloudPlugin(
coreMock.createPluginInitializerContext({
id: 'cloudId',
Expand All @@ -132,6 +160,7 @@ describe('Cloud Plugin', () => {
chat: {
enabled: false,
},
...configParts,
})
);
const coreSetup = coreMock.createSetup();
Expand All @@ -154,5 +183,38 @@ describe('Cloud Plugin', () => {
]
`);
});

describe('isServerlessEnabled', () => {
it('is `true` when `serverless.projectId` is set', () => {
const { plugin } = startPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
const coreStart = coreMock.createStart();
const start = plugin.start(coreStart);
expect(start.isServerlessEnabled).toBe(true);
});

it('is `false` when `serverless.projectId` is not set', () => {
const { plugin } = startPlugin({
serverless: undefined,
});
const coreStart = coreMock.createStart();
const start = plugin.start(coreStart);
expect(start.isServerlessEnabled).toBe(false);
});
});

it('exposes `serverless.projectId`', () => {
const { plugin } = startPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
const coreStart = coreMock.createStart();
const start = plugin.start(coreStart);
expect(start.serverless.projectId).toBe('my-awesome-project');
});
});
});
13 changes: 13 additions & 0 deletions x-pack/plugins/cloud/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface CloudConfigType {
organization_url?: string;
trial_end_date?: string;
is_elastic_staff_owned?: boolean;
serverless?: {
project_id: string;
};
}

interface CloudUrls {
Expand All @@ -39,12 +42,14 @@ interface CloudUrls {
export class CloudPlugin implements Plugin<CloudSetup> {
private readonly config: CloudConfigType;
private readonly isCloudEnabled: boolean;
private readonly isServerlessEnabled: 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.isServerlessEnabled = !!this.config.serverless?.project_id;
this.logger = initializerContext.logger.get();
}

Expand Down Expand Up @@ -77,6 +82,10 @@ export class CloudPlugin implements Plugin<CloudSetup> {
trialEndDate: trialEndDate ? new Date(trialEndDate) : undefined,
isElasticStaffOwned,
isCloudEnabled: this.isCloudEnabled,
isServerlessEnabled: this.isServerlessEnabled,
serverless: {
projectId: this.config.serverless?.project_id,
},
registerCloudService: (contextProvider) => {
this.contextProviders.push(contextProvider);
},
Expand Down Expand Up @@ -118,6 +127,10 @@ export class CloudPlugin implements Plugin<CloudSetup> {
organizationUrl,
elasticsearchUrl: decodedId?.elasticsearchUrl,
kibanaUrl: decodedId?.kibanaUrl,
isServerlessEnabled: this.isServerlessEnabled,
serverless: {
projectId: this.config.serverless?.project_id,
},
};
}

Expand Down
30 changes: 30 additions & 0 deletions x-pack/plugins/cloud/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ export interface CloudStart {
* The full URL to the Kibana deployment.
*/
kibanaUrl?: string;
/**
* `true` when running on Serverless Elastic Cloud
* Note that `isCloudEnabled` will always be true when `isServerlessEnabled` is.
*/
isServerlessEnabled: boolean;
/**
* Serverless configuration
*/
serverless: {
/**
* The serverless projectId.
* Will always be present if `isServerlessEnabled` is `true`
*/
projectId?: string;
};
}

export interface CloudSetup {
Expand Down Expand Up @@ -112,4 +127,19 @@ export interface CloudSetup {
* @param contextProvider The React component from the Service Provider.
*/
registerCloudService: (contextProvider: FC) => void;
/**
* `true` when running on Serverless Elastic Cloud
* Note that `isCloudEnabled` will always be true when `isServerlessEnabled` is.
*/
isServerlessEnabled: boolean;
/**
* Serverless configuration
*/
serverless: {
/**
* The serverless projectId.
* Will always be present if `isServerlessEnabled` is `true`
*/
projectId?: string;
};
}
8 changes: 8 additions & 0 deletions x-pack/plugins/cloud/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const configSchema = schema.object({
profile_url: schema.maybe(schema.string()),
trial_end_date: schema.maybe(schema.string()),
is_elastic_staff_owned: schema.maybe(schema.boolean()),
serverless: schema.maybe(
schema.object({
project_id: schema.string(),
})
),
});

export type CloudConfigType = TypeOf<typeof configSchema>;
Expand All @@ -44,6 +49,9 @@ export const config: PluginConfigDescriptor<CloudConfigType> = {
profile_url: true,
trial_end_date: true,
is_elastic_staff_owned: true,
serverless: {
project_id: true,
},
},
schema: configSchema,
};
4 changes: 4 additions & 0 deletions x-pack/plugins/cloud/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ function createSetupMock(): jest.Mocked<CloudSetup> {
url: undefined,
secretToken: undefined,
},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
}

Expand Down
31 changes: 30 additions & 1 deletion x-pack/plugins/cloud/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

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

Expand All @@ -23,11 +24,12 @@ describe('Cloud Plugin', () => {
decodeCloudIdMock.mockReset().mockReturnValue({});
});

const setupPlugin = () => {
const setupPlugin = (configParts: Partial<CloudConfigType> = {}) => {
const initContext = coreMock.createPluginInitializerContext({
...baseConfig,
id: 'cloudId',
cname: 'cloud.elastic.co',
...configParts,
});
const plugin = new CloudPlugin(initContext);

Expand Down Expand Up @@ -90,6 +92,33 @@ describe('Cloud Plugin', () => {
expect(decodeCloudIdMock).toHaveBeenCalledTimes(1);
expect(decodeCloudIdMock).toHaveBeenCalledWith('cloudId', expect.any(Object));
});

describe('isServerlessEnabled', () => {
it('is `true` when `serverless.projectId` is set', () => {
const { setup } = setupPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
expect(setup.isServerlessEnabled).toBe(true);
});

it('is `false` when `serverless.projectId` is not set', () => {
const { setup } = setupPlugin({
serverless: undefined,
});
expect(setup.isServerlessEnabled).toBe(false);
});
});

it('exposes `serverless.projectId`', () => {
const { setup } = setupPlugin({
serverless: {
project_id: 'my-awesome-project',
},
});
expect(setup.serverless.projectId).toBe('my-awesome-project');
});
});
});

Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/cloud/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ export interface CloudSetup {
url?: string;
secretToken?: string;
};
/**
* `true` when running on Serverless Elastic Cloud
* Note that `isCloudEnabled` will always be true when `isServerlessEnabled` is.
*/
isServerlessEnabled: boolean;
/**
* Serverless configuration
*/
serverless: {
/**
* The serverless projectId.
* Will always be present if `isServerlessEnabled` is `true`
*/
projectId?: string;
Copy link
Contributor

@gsoldevila gsoldevila Jul 14, 2023

Choose a reason for hiding this comment

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

NIT I wonder if it should be the whole 'serverless' section that is optional instead.
Does it make sense to have a serverless: {} empty section?

Could the fact that the "serverless" property is defined be enough to determine that "serverless is enabled"? This way we would get rid of the isServerlessEnabled flag.

Serverless does not sound like something you can enable / disable, either you are or not on serverless, WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now it would make more sense. The thing is, we will likely add more serverless attributes to this section, and given their presence may be optional, it could complicate the thing (the same reason why the props are each individually nullable for ESS-related props)

};
}

/**
Expand All @@ -94,6 +109,8 @@ export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {

public setup(core: CoreSetup, { usageCollection }: PluginsSetup): CloudSetup {
const isCloudEnabled = getIsCloudEnabled(this.config.id);
const isServerlessEnabled = !!this.config.serverless?.project_id;

registerCloudDeploymentMetadataAnalyticsContext(core.analytics, this.config);
registerCloudUsageCollector(usageCollection, {
isCloudEnabled,
Expand Down Expand Up @@ -121,6 +138,10 @@ export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {
url: this.config.apm?.url,
secretToken: this.config.apm?.secret_token,
},
isServerlessEnabled,
serverless: {
projectId: this.config.serverless?.project_id,
},
};
}

Expand Down
Loading