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

## Summary

Fix https://github.com/elastic/kibana/issues/161652

Add the `serverless.projectId` config setting to the `cloud` plugin, and
expose the `isCloudServerless` and `serverless.projectId` info from the
cloud plugin's API.
This commit is contained in:
Pierre Gayvallet 2023-07-14 14:17:12 +02:00 committed by GitHub
parent e5bcc87202
commit a9786dfd6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 217 additions and 4 deletions

View file

@ -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)',
'xpack.discoverEnhanced.actions.exploreDataInChart.enabled (boolean)',
'xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled (boolean)',
'xpack.fleet.agents.enabled (boolean)',

View file

@ -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,
},
};
}
@ -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 = {

View file

@ -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 = {
@ -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);
@ -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',
@ -132,6 +160,7 @@ describe('Cloud Plugin', () => {
chat: {
enabled: false,
},
...configParts,
})
);
const coreSetup = coreMock.createSetup();
@ -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');
});
});
});

View file

@ -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 {
@ -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();
}
@ -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);
},
@ -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,
},
};
}

View file

@ -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 {
@ -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;
};
}

View file

@ -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>;
@ -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,
};

View file

@ -23,6 +23,10 @@ function createSetupMock(): jest.Mocked<CloudSetup> {
url: undefined,
secretToken: undefined,
},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
}

View file

@ -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';
@ -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);
@ -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');
});
});
});

View file

@ -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;
};
}
/**
@ -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,
@ -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,
},
};
}

View file

@ -15,6 +15,10 @@ const defaultPortCloud = {
cloudHost: 'us-central1.gcp.cloud.es.io',
cloudDefaultPort: '443',
registerCloudService: jest.fn(),
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
// 9243
const customPortCloud = {
@ -25,17 +29,29 @@ const customPortCloud = {
cloudHost: 'us-central1.gcp.cloud.es.io',
cloudDefaultPort: '9243',
registerCloudService: jest.fn(),
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
const missingDeploymentIdCloud = {
cloudId:
'dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=',
isCloudEnabled: true,
registerCloudService: jest.fn(),
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
const noCloud = {
cloudId: undefined,
isCloudEnabled: false,
registerCloudService: jest.fn(),
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
describe('getCloudEnterpriseSearchHost', () => {

View file

@ -18,6 +18,10 @@ export const getCloud = ({ isCloudEnabled }: { isCloudEnabled: boolean }) => {
profileUrl: 'https://profile.url',
snapshotsUrl: 'https://snapshots.url',
registerCloudService: () => {},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
};
return cloud;

View file

@ -120,6 +120,10 @@ describe('getCloudFleetServersHosts', () => {
deploymentId: 'deployment-id-1',
cloudHost: 'us-east-1.aws.found.io',
apm: {},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
});
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
@ -138,6 +142,10 @@ describe('getCloudFleetServersHosts', () => {
cloudHost: 'test.fr',
cloudDefaultPort: '9243',
apm: {},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
});
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
@ -171,6 +179,10 @@ describe('createCloudFleetServerHostIfNeeded', () => {
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
apm: {},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
});
mockedGetDefaultFleetServerHost.mockResolvedValue({
id: 'test',
@ -190,6 +202,10 @@ describe('createCloudFleetServerHostIfNeeded', () => {
deploymentId: 'deployment-id-1',
cloudHost: 'us-east-1.aws.found.io',
apm: {},
isServerlessEnabled: false,
serverless: {
projectId: undefined,
},
});
mockedGetDefaultFleetServerHost.mockResolvedValue(null);
soClient.create.mockResolvedValue({