Merge pull request #8641 from livetocode/feature/addGkeLinkFormatter
implement dashboard link formatter for GKE
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-kubernetes-common': patch
|
||||
---
|
||||
|
||||
implement dashboard link formatter for GKE
|
||||
@@ -39,6 +39,7 @@ kubernetes:
|
||||
region: 'europe-west1'
|
||||
skipTLSVerify: true
|
||||
skipMetricsLookup: true
|
||||
exposeDashboard: true
|
||||
```
|
||||
|
||||
### `serviceLocatorMethod`
|
||||
@@ -113,9 +114,13 @@ kubectl -n <NAMESPACE> get secret $(kubectl -n <NAMESPACE> get sa <SERVICE_ACCOU
|
||||
Specifies the link to the Kubernetes dashboard managing this cluster.
|
||||
|
||||
Note that you should specify the app used for the dashboard using the
|
||||
**dashboardApp property**, in order to properly format links to kubernetes
|
||||
`dashboardApp` property, in order to properly format links to kubernetes
|
||||
resources, otherwise it will assume that you're running the standard one.
|
||||
|
||||
Note also that this attribute is optional for some kinds of dashboards, such as
|
||||
GKE, which requires additional parameters specified in the `dashboardParameters`
|
||||
option.
|
||||
|
||||
##### `clusters.\*.dashboardApp` (optional)
|
||||
|
||||
Specifies the app that provides the Kubernetes dashboard.
|
||||
@@ -124,11 +129,14 @@ This will be used for formatting links to kubernetes objects inside the
|
||||
dashboard.
|
||||
|
||||
The supported dashboards are: `standard`, `rancher`, `openshift`, `gke`, `aks`,
|
||||
`eks` However, not all of them are implemented yet, so please contribute!
|
||||
`eks`. However, not all of them are implemented yet, so please contribute!
|
||||
|
||||
Note that it will default to the regular dashboard provided by the Kubernetes
|
||||
project (`standard`), that can run in any Kubernetes cluster.
|
||||
|
||||
Note that for the `gke` app, you must provide additional information in the
|
||||
`dashboardParameters` option.
|
||||
|
||||
Note that you can add your own formatter by registering it to the
|
||||
`clusterLinksFormatters` dictionary, in the app project.
|
||||
|
||||
@@ -143,6 +151,43 @@ See also
|
||||
https://github.com/backstage/backstage/tree/master/plugins/kubernetes/src/utils/clusterLinks/formatters
|
||||
for real examples.
|
||||
|
||||
##### `clusters.\*.dashboardParameters` (optional)
|
||||
|
||||
Specifies additional information for the selected `dashboardApp` formatter.
|
||||
|
||||
Note that, even though `dashboardParameters` is optional, it might be mandatory
|
||||
for some dashboards, such as GKE.
|
||||
|
||||
###### required parameters for GKE
|
||||
|
||||
| Name | Description |
|
||||
| ----------- | ------------------------------------------------------------------------ |
|
||||
| projectId | the ID of the GCP project containing your Kubernetes clusters |
|
||||
| region | the region of GCP containing your Kubernetes clusters |
|
||||
| clusterName | the name of your kubernetes cluster, within your `projectId` GCP project |
|
||||
|
||||
Note that the GKE cluster locator can automatically provide the values for the
|
||||
`dashboardApp` and `dashboardParameters` options if you set the
|
||||
`exposeDashboard` property to `true`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
kubernetes:
|
||||
serviceLocatorMethod:
|
||||
type: 'multiTenant'
|
||||
clusterLocatorMethods:
|
||||
- type: 'config'
|
||||
clusters:
|
||||
- url: http://127.0.0.1:9999
|
||||
name: my-cluster
|
||||
dashboardApp: gke
|
||||
dashboardParameters:
|
||||
projectId: my-project
|
||||
region: us-east1
|
||||
clusterName: my-cluster
|
||||
```
|
||||
|
||||
##### `clusters.\*.caData` (optional)
|
||||
|
||||
PEM-encoded certificate authority certificates.
|
||||
@@ -184,6 +229,10 @@ For example:
|
||||
Will configure the Kubernetes plugin to connect to all GKE clusters in the
|
||||
project `gke-clusters` in the region `europe-west1`.
|
||||
|
||||
Note that the GKE cluster locator can automatically provide the values for the
|
||||
`dashboardApp` and `dashboardParameters` options if you enable the
|
||||
`exposeDashboard` option.
|
||||
|
||||
##### `projectId`
|
||||
|
||||
The Google Cloud project to look for Kubernetes clusters in.
|
||||
@@ -203,6 +252,14 @@ presented by the API server. Defaults to `false`.
|
||||
This determines whether the Kubernetes client looks up resource metrics
|
||||
CPU/Memory for pods returned by the API server. Defaults to `false`.
|
||||
|
||||
##### `exposeDashboard`
|
||||
|
||||
This determines wether the `dashboardApp` and `dashboardParameters` should be
|
||||
automatically configured in order to expose the GKE dashboard from the
|
||||
Kubernetes plugin.
|
||||
|
||||
Defaults to `false`.
|
||||
|
||||
### `customResources` (optional)
|
||||
|
||||
Configures which [custom resources][3] to look for when returning an entity's
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import type { FetchResponse } from '@backstage/plugin-kubernetes-common';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common';
|
||||
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
@@ -31,6 +32,7 @@ export interface ClusterDetails {
|
||||
// (undocumented)
|
||||
caData?: string | undefined;
|
||||
dashboardApp?: string;
|
||||
dashboardParameters?: JsonObject;
|
||||
dashboardUrl?: string;
|
||||
name: string;
|
||||
// (undocumented)
|
||||
|
||||
@@ -176,4 +176,76 @@ describe('ConfigClusterLocator', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('one cluster with dashboardParameters', async () => {
|
||||
const config: Config = new ConfigReader({
|
||||
clusters: [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authProvider: 'serviceAccount',
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
projectId: 'some-project',
|
||||
region: 'some-region',
|
||||
clusterName: 'cluster1',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const sut = ConfigClusterLocator.fromConfig(config);
|
||||
|
||||
const result = await sut.getClusters();
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
name: 'cluster1',
|
||||
serviceAccountToken: undefined,
|
||||
url: 'http://localhost:8080',
|
||||
authProvider: 'serviceAccount',
|
||||
skipMetricsLookup: false,
|
||||
skipTLSVerify: false,
|
||||
caData: undefined,
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
projectId: 'some-project',
|
||||
region: 'some-region',
|
||||
clusterName: 'cluster1',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('one cluster with dashboardUrl', async () => {
|
||||
const config: Config = new ConfigReader({
|
||||
clusters: [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authProvider: 'serviceAccount',
|
||||
dashboardApp: 'standard',
|
||||
dashboardUrl: 'http://someurl',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const sut = ConfigClusterLocator.fromConfig(config);
|
||||
|
||||
const result = await sut.getClusters();
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
name: 'cluster1',
|
||||
serviceAccountToken: undefined,
|
||||
url: 'http://localhost:8080',
|
||||
authProvider: 'serviceAccount',
|
||||
skipMetricsLookup: false,
|
||||
skipTLSVerify: false,
|
||||
caData: undefined,
|
||||
dashboardApp: 'standard',
|
||||
dashboardUrl: 'http://someurl',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,9 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier {
|
||||
if (dashboardApp) {
|
||||
clusterDetails.dashboardApp = dashboardApp;
|
||||
}
|
||||
if (c.has('dashboardParameters')) {
|
||||
clusterDetails.dashboardParameters = c.get('dashboardParameters');
|
||||
}
|
||||
|
||||
switch (authProvider) {
|
||||
case 'google': {
|
||||
|
||||
@@ -226,5 +226,51 @@ describe('GkeClusterLocator', () => {
|
||||
parent: 'projects/some-project/locations/some-region',
|
||||
});
|
||||
});
|
||||
it('expose GKE dashboard', async () => {
|
||||
mockedListClusters.mockReturnValueOnce([
|
||||
{
|
||||
clusters: [
|
||||
{
|
||||
name: 'some-cluster',
|
||||
endpoint: '1.2.3.4',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const config: Config = new ConfigReader({
|
||||
type: 'gke',
|
||||
projectId: 'some-project',
|
||||
region: 'some-region',
|
||||
skipMetricsLookup: true,
|
||||
exposeDashboard: true,
|
||||
});
|
||||
|
||||
const sut = GkeClusterLocator.fromConfigWithClient(config, {
|
||||
listClusters: mockedListClusters,
|
||||
} as any);
|
||||
|
||||
const result = await sut.getClusters();
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
authProvider: 'google',
|
||||
name: 'some-cluster',
|
||||
url: 'https://1.2.3.4',
|
||||
skipTLSVerify: false,
|
||||
skipMetricsLookup: true,
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
clusterName: 'some-cluster',
|
||||
projectId: 'some-project',
|
||||
region: 'some-region',
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(mockedListClusters).toBeCalledTimes(1);
|
||||
expect(mockedListClusters).toHaveBeenCalledWith({
|
||||
parent: 'projects/some-project/locations/some-region',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ type GkeClusterLocatorOptions = {
|
||||
region?: string;
|
||||
skipTLSVerify?: boolean;
|
||||
skipMetricsLookup?: boolean;
|
||||
exposeDashboard?: boolean;
|
||||
};
|
||||
|
||||
export class GkeClusterLocator implements KubernetesClustersSupplier {
|
||||
@@ -42,6 +43,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
|
||||
skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false,
|
||||
skipMetricsLookup:
|
||||
config.getOptionalBoolean('skipMetricsLookup') ?? false,
|
||||
exposeDashboard: config.getOptionalBoolean('exposeDashboard') ?? false,
|
||||
};
|
||||
return new GkeClusterLocator(options, client);
|
||||
}
|
||||
@@ -55,8 +57,13 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
|
||||
|
||||
// TODO pass caData into the object
|
||||
async getClusters(): Promise<GKEClusterDetails[]> {
|
||||
const { projectId, region, skipTLSVerify, skipMetricsLookup } =
|
||||
this.options;
|
||||
const {
|
||||
projectId,
|
||||
region,
|
||||
skipTLSVerify,
|
||||
skipMetricsLookup,
|
||||
exposeDashboard,
|
||||
} = this.options;
|
||||
const request = {
|
||||
parent: `projects/${projectId}/locations/${region}`,
|
||||
};
|
||||
@@ -70,6 +77,16 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
|
||||
authProvider: 'google',
|
||||
skipTLSVerify,
|
||||
skipMetricsLookup,
|
||||
...(exposeDashboard
|
||||
? {
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
projectId,
|
||||
region,
|
||||
clusterName: r.name,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new ForwardedError(
|
||||
|
||||
@@ -255,6 +255,10 @@ export class KubernetesFanOutHandler {
|
||||
if (clusterDetailsItem.dashboardApp) {
|
||||
objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp;
|
||||
}
|
||||
if (clusterDetailsItem.dashboardParameters) {
|
||||
objects.cluster.dashboardParameters =
|
||||
clusterDetailsItem.dashboardParameters;
|
||||
}
|
||||
return objects;
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import type {
|
||||
FetchResponse,
|
||||
KubernetesFetchError,
|
||||
@@ -111,6 +112,7 @@ export interface ClusterDetails {
|
||||
* using the dashboardApp property, in order to properly format
|
||||
* links to kubernetes resources, otherwise it will assume that you're running the standard one.
|
||||
* @see dashboardApp
|
||||
* @see dashboardParameters
|
||||
*/
|
||||
dashboardUrl?: string;
|
||||
/**
|
||||
@@ -129,6 +131,12 @@ export interface ClusterDetails {
|
||||
* ```
|
||||
*/
|
||||
dashboardApp?: string;
|
||||
/**
|
||||
* Specifies specific parameters used by some dashboard URL formatters.
|
||||
* This is used by the GKE formatter which requires the project, region and cluster name.
|
||||
* @see dashboardApp
|
||||
*/
|
||||
dashboardParameters?: JsonObject;
|
||||
}
|
||||
|
||||
export interface GKEClusterDetails extends ClusterDetails {}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import { V1ConfigMap } from '@kubernetes/client-node';
|
||||
import { V1CronJob } from '@kubernetes/client-node';
|
||||
import { V1Deployment } from '@kubernetes/client-node';
|
||||
@@ -62,6 +63,7 @@ export interface ClientPodStatus {
|
||||
// @public (undocumented)
|
||||
export interface ClusterAttributes {
|
||||
dashboardApp?: string;
|
||||
dashboardParameters?: JsonObject;
|
||||
dashboardUrl?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
V1ConfigMap,
|
||||
V1CronJob,
|
||||
@@ -45,6 +46,7 @@ export interface ClusterAttributes {
|
||||
* Note that you should specify the app used for the dashboard
|
||||
* using the dashboardApp property, in order to properly format
|
||||
* links to kubernetes resources, otherwise it will assume that you're running the standard one.
|
||||
* Also, for cloud clusters such as GKE, you should provide addititonal parameters using dashboardParameters.
|
||||
* @see dashboardApp
|
||||
*/
|
||||
dashboardUrl?: string;
|
||||
@@ -64,6 +66,11 @@ export interface ClusterAttributes {
|
||||
* ```
|
||||
*/
|
||||
dashboardApp?: string;
|
||||
/**
|
||||
* Specifies specific parameters used by some dashboard URL formatters.
|
||||
* This is used by the GKE formatter which requires the project, region and cluster name.
|
||||
*/
|
||||
dashboardParameters?: JsonObject;
|
||||
}
|
||||
|
||||
export interface ClusterObjects {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { OAuthApi } from '@backstage/core-plugin-api';
|
||||
import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
@@ -155,6 +155,7 @@ const KubernetesDrawerContent = <T extends KubernetesDrawerable>({
|
||||
const { clusterLink, errorMessage } = tryFormatClusterLink({
|
||||
dashboardUrl: cluster.dashboardUrl,
|
||||
dashboardApp: cluster.dashboardApp,
|
||||
dashboardParameters: cluster.dashboardParameters,
|
||||
object,
|
||||
kind,
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
V1Deployment,
|
||||
V1Pod,
|
||||
@@ -43,7 +44,8 @@ export interface GroupedResponses extends DeploymentResources {
|
||||
}
|
||||
|
||||
export interface ClusterLinksFormatterOptions {
|
||||
dashboardUrl: URL;
|
||||
dashboardUrl?: URL;
|
||||
dashboardParameters?: JsonObject;
|
||||
object: any;
|
||||
kind: string;
|
||||
}
|
||||
|
||||
@@ -114,5 +114,47 @@ describe('clusterLinks', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('GKE app', () => {
|
||||
it('should return an url on the deployment', () => {
|
||||
const url = formatClusterLink({
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
});
|
||||
expect(url).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the service', () => {
|
||||
const url = formatClusterLink({
|
||||
dashboardApp: 'gke',
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Service',
|
||||
});
|
||||
expect(url).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/service/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,20 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import { defaultFormatterName, clusterLinksFormatters } from './formatters';
|
||||
|
||||
export type FormatClusterLinkOptions = {
|
||||
dashboardUrl?: string;
|
||||
dashboardApp?: string;
|
||||
dashboardParameters?: JsonObject;
|
||||
object: any;
|
||||
kind: string;
|
||||
};
|
||||
|
||||
export function formatClusterLink(options: FormatClusterLinkOptions) {
|
||||
if (!options.dashboardUrl) {
|
||||
if (!options.dashboardUrl && !options.dashboardParameters) {
|
||||
return undefined;
|
||||
}
|
||||
if (!options.object) {
|
||||
if (options.dashboardUrl && !options.object) {
|
||||
return options.dashboardUrl;
|
||||
}
|
||||
const app = options.dashboardApp || defaultFormatterName;
|
||||
@@ -36,7 +38,10 @@ export function formatClusterLink(options: FormatClusterLinkOptions) {
|
||||
throw new Error(`Could not find Kubernetes dashboard app named '${app}'`);
|
||||
}
|
||||
const url = formatter({
|
||||
dashboardUrl: new URL(options.dashboardUrl),
|
||||
dashboardUrl: options.dashboardUrl
|
||||
? new URL(options.dashboardUrl)
|
||||
: undefined,
|
||||
dashboardParameters: options.dashboardParameters,
|
||||
object: options.object,
|
||||
kind: options.kind,
|
||||
});
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
import { gkeFormatter } from './gke';
|
||||
|
||||
describe('clusterLinks - GKE formatter', () => {
|
||||
it('should return an url on the workloads when there is a namespace only', () => {
|
||||
it('should provide a dashboardParameters in the options', () => {
|
||||
expect(() =>
|
||||
gkeFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com'),
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
@@ -28,6 +27,196 @@ describe('clusterLinks - GKE formatter', () => {
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError('GKE formatter is not yet implemented. Please, contribute!');
|
||||
).toThrowError('GKE dashboard requires a dashboardParameters option');
|
||||
});
|
||||
it('should provide a projectId in the dashboardParameters options', () => {
|
||||
expect(() =>
|
||||
gkeFormatter({
|
||||
dashboardParameters: {
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError(
|
||||
'GKE dashboard requires a "projectId" of type string in the dashboardParameters option',
|
||||
);
|
||||
});
|
||||
it('should provide a region in the dashboardParameters options', () => {
|
||||
expect(() =>
|
||||
gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError(
|
||||
'GKE dashboard requires a "region" of type string in the dashboardParameters option',
|
||||
);
|
||||
});
|
||||
it('should provide a clusterName in the dashboardParameters options', () => {
|
||||
expect(() =>
|
||||
gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError(
|
||||
'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option',
|
||||
);
|
||||
});
|
||||
it('should return an url on the cluster when there is a namespace only', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'foo',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the cluster when the kind is not recognizeed', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'UnknownKind',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the deployment', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an url on the service', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Service',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/service/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the pod', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Pod',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/pod/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the ingress', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Ingress',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/ingress/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
it('should return an url on the deployment for a hpa', () => {
|
||||
const url = gkeFormatter({
|
||||
dashboardParameters: {
|
||||
projectId: 'foobar-333614',
|
||||
region: 'us-east1-c',
|
||||
clusterName: 'cluster-1',
|
||||
},
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'HorizontalPodAutoscaler',
|
||||
});
|
||||
expect(url.href).toBe(
|
||||
'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,53 @@
|
||||
*/
|
||||
import { ClusterLinksFormatterOptions } from '../../../types/types';
|
||||
|
||||
export function gkeFormatter(_options: ClusterLinksFormatterOptions): URL {
|
||||
throw new Error('GKE formatter is not yet implemented. Please, contribute!');
|
||||
const kindMappings: Record<string, string> = {
|
||||
deployment: 'deployment',
|
||||
pod: 'pod',
|
||||
ingress: 'ingress',
|
||||
service: 'service',
|
||||
horizontalpodautoscaler: 'deployment',
|
||||
};
|
||||
|
||||
export function gkeFormatter(options: ClusterLinksFormatterOptions): URL {
|
||||
if (!options.dashboardParameters) {
|
||||
throw new Error('GKE dashboard requires a dashboardParameters option');
|
||||
}
|
||||
const args = options.dashboardParameters;
|
||||
if (typeof args.projectId !== 'string') {
|
||||
throw new Error(
|
||||
'GKE dashboard requires a "projectId" of type string in the dashboardParameters option',
|
||||
);
|
||||
}
|
||||
if (typeof args.region !== 'string') {
|
||||
throw new Error(
|
||||
'GKE dashboard requires a "region" of type string in the dashboardParameters option',
|
||||
);
|
||||
}
|
||||
if (typeof args.clusterName !== 'string') {
|
||||
throw new Error(
|
||||
'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option',
|
||||
);
|
||||
}
|
||||
const basePath = new URL('https://console.cloud.google.com/');
|
||||
const region = encodeURIComponent(args.region);
|
||||
const clusterName = encodeURIComponent(args.clusterName);
|
||||
const name = encodeURIComponent(options.object.metadata?.name ?? '');
|
||||
const namespace = encodeURIComponent(
|
||||
options.object.metadata?.namespace ?? '',
|
||||
);
|
||||
const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')];
|
||||
let path = '';
|
||||
if (namespace && name && validKind) {
|
||||
const kindsWithDetails = ['ingress', 'pod'];
|
||||
const landingPage = kindsWithDetails.includes(validKind)
|
||||
? 'details'
|
||||
: 'overview';
|
||||
path = `kubernetes/${validKind}/${region}/${clusterName}/${namespace}/${name}/${landingPage}`;
|
||||
} else {
|
||||
path = `kubernetes/clusters/details/${region}/${clusterName}/details`;
|
||||
}
|
||||
const result = new URL(path, basePath);
|
||||
result.searchParams.set('project', args.projectId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,19 @@
|
||||
import { openshiftFormatter } from './openshift';
|
||||
|
||||
describe('clusterLinks - OpenShift formatter', () => {
|
||||
it('should provide a dashboardUrl in the options', () => {
|
||||
expect(() =>
|
||||
openshiftFormatter({
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError('OpenShift dashboard requires a dashboardUrl option');
|
||||
});
|
||||
it('should return an url on the workloads when there is a namespace only', () => {
|
||||
const url = openshiftFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com'),
|
||||
|
||||
@@ -24,6 +24,9 @@ const kindMappings: Record<string, string> = {
|
||||
};
|
||||
|
||||
export function openshiftFormatter(options: ClusterLinksFormatterOptions): URL {
|
||||
if (!options.dashboardUrl) {
|
||||
throw new Error('OpenShift dashboard requires a dashboardUrl option');
|
||||
}
|
||||
const basePath = new URL(options.dashboardUrl.href);
|
||||
const name = encodeURIComponent(options.object.metadata?.name ?? '');
|
||||
const namespace = encodeURIComponent(
|
||||
|
||||
@@ -16,6 +16,19 @@
|
||||
import { rancherFormatter } from './rancher';
|
||||
|
||||
describe('clusterLinks - rancher formatter', () => {
|
||||
it('should provide a dashboardUrl in the options', () => {
|
||||
expect(() =>
|
||||
rancherFormatter({
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError('Rancher dashboard requires a dashboardUrl option');
|
||||
});
|
||||
it('should return a url on the workloads when there is a namespace only', () => {
|
||||
const url = rancherFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com'),
|
||||
|
||||
@@ -23,6 +23,9 @@ const kindMappings: Record<string, string> = {
|
||||
};
|
||||
|
||||
export function rancherFormatter(options: ClusterLinksFormatterOptions): URL {
|
||||
if (!options.dashboardUrl) {
|
||||
throw new Error('Rancher dashboard requires a dashboardUrl option');
|
||||
}
|
||||
const basePath = new URL(options.dashboardUrl.href);
|
||||
const name = encodeURIComponent(options.object.metadata?.name ?? '');
|
||||
const namespace = encodeURIComponent(
|
||||
|
||||
@@ -23,6 +23,19 @@ function formatUrl(url: URL) {
|
||||
}
|
||||
|
||||
describe('clusterLinks - standard formatter', () => {
|
||||
it('should provide a dashboardUrl in the options', () => {
|
||||
expect(() =>
|
||||
standardFormatter({
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
}),
|
||||
).toThrowError('standard dashboard requires a dashboardUrl option');
|
||||
});
|
||||
it('should return an url on the workloads when there is a namespace only', () => {
|
||||
const url = standardFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com'),
|
||||
@@ -67,6 +80,21 @@ describe('clusterLinks - standard formatter', () => {
|
||||
'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar',
|
||||
);
|
||||
});
|
||||
it('should return an url on the pod', () => {
|
||||
const url = standardFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com/'),
|
||||
object: {
|
||||
metadata: {
|
||||
name: 'foobar',
|
||||
namespace: 'bar',
|
||||
},
|
||||
},
|
||||
kind: 'Pod',
|
||||
});
|
||||
expect(formatUrl(url)).toBe(
|
||||
'https://k8s.foo.com/#/pod/bar/foobar?namespace=bar',
|
||||
);
|
||||
});
|
||||
it('should return an url on the deployment with a prefix 1', () => {
|
||||
const url = standardFormatter({
|
||||
dashboardUrl: new URL('https://k8s.foo.com/some/prefix'),
|
||||
|
||||
@@ -17,12 +17,16 @@ import { ClusterLinksFormatterOptions } from '../../../types/types';
|
||||
|
||||
const kindMappings: Record<string, string> = {
|
||||
deployment: 'deployment',
|
||||
pod: 'pod',
|
||||
ingress: 'ingress',
|
||||
service: 'service',
|
||||
horizontalpodautoscaler: 'deployment',
|
||||
};
|
||||
|
||||
export function standardFormatter(options: ClusterLinksFormatterOptions) {
|
||||
if (!options.dashboardUrl) {
|
||||
throw new Error('standard dashboard requires a dashboardUrl option');
|
||||
}
|
||||
const result = new URL(options.dashboardUrl.href);
|
||||
const name = encodeURIComponent(options.object.metadata?.name ?? '');
|
||||
const namespace = encodeURIComponent(
|
||||
|
||||
Reference in New Issue
Block a user