From 7a0c3347070805f535fc71feb412715b3a790590 Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Tue, 10 Aug 2021 15:15:45 -0400 Subject: [PATCH 01/10] feat(kubernetes): provide access to the Kubernetes dashboard when viewing a specific resource Signed-off-by: Morgan Martinet --- .changeset/tiny-berries-battle.md | 7 ++ .../cluster-locator/ConfigClusterLocator.ts | 1 + .../src/service/KubernetesFanOutHandler.ts | 1 + .../kubernetes-backend/src/service/router.ts | 1 + plugins/kubernetes-backend/src/types/types.ts | 1 + plugins/kubernetes-common/src/types.ts | 7 +- .../KubernetesContent/KubernetesContent.tsx | 59 ++++----- .../KubernetesDrawer/KubernetesDrawer.tsx | 25 +++- plugins/kubernetes/src/hooks/Cluster.ts | 21 ++++ plugins/kubernetes/src/hooks/index.ts | 1 + .../kubernetes/src/utils/clusterLinks.test.ts | 115 ++++++++++++++++++ plugins/kubernetes/src/utils/clusterLinks.ts | 46 +++++++ 12 files changed, 254 insertions(+), 31 deletions(-) create mode 100644 .changeset/tiny-berries-battle.md create mode 100644 plugins/kubernetes/src/hooks/Cluster.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks.ts diff --git a/.changeset/tiny-berries-battle.md b/.changeset/tiny-berries-battle.md new file mode 100644 index 0000000000..9e3194a317 --- /dev/null +++ b/.changeset/tiny-berries-battle.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': minor +--- + +Provide access to the Kubernetes dashboard when viewing a specific resource diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 8352db6754..a946871e7b 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -33,6 +33,7 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { const clusterDetails = { name: c.getString('name'), url: c.getString('url'), + dashboardUrl: c.getOptionalString('dashboardUrl'), serviceAccountToken: c.getOptionalString('serviceAccountToken'), skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false, authProvider: authProvider, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index fb1876733d..1777d4592a 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -114,6 +114,7 @@ export class KubernetesFanOutHandler { return { cluster: { name: clusterDetailsItem.name, + dashboardUrl: clusterDetailsItem.dashboardUrl, }, resources: result.responses, errors: result.errors, diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 4792ae708f..d8f7c787d9 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -86,6 +86,7 @@ export const makeRouter = ( res.json({ items: clusterDetails.map(cd => ({ name: cd.name, + dashboardUrl: cd.dashboardUrl, authProvider: cd.authProvider, })), }); diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index f4bb019108..f6c3f1e3ab 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -80,6 +80,7 @@ export interface ClusterDetails { authProvider: string; serviceAccountToken?: string | undefined; skipTLSVerify?: boolean; + dashboardUrl?: string; } export interface GKEClusterDetails extends ClusterDetails {} diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index c94a71a184..1da1390207 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -32,8 +32,13 @@ export interface KubernetesRequestBody { entity: Entity; } +export interface ClusterAttributes { + name: string; + dashboardUrl?: string; +} + export interface ClusterObjects { - cluster: { name: string }; + cluster: ClusterAttributes; resources: FetchResponse[]; errors: KubernetesFetchError[]; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index c0dd6d5003..260c77ed9e 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -36,6 +36,7 @@ import { ServicesAccordions } from '../ServicesAccordions'; import { CustomResources } from '../CustomResources'; import EmptyStateImage from '../../assets/emptystate.svg'; import { + ClusterContext, GroupedResponsesContext, PodNamesWithErrorsContext, useKubernetesObjects, @@ -119,35 +120,37 @@ type ClusterProps = { const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => { const groupedResponses = groupResponses(clusterObjects.resources); return ( - - - - }> - - - - - - + + + + + }> + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + ); }; diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index bf714530ce..1fc2f297d3 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ChangeEvent, useState } from 'react'; +import React, { ChangeEvent, useContext, useState } from 'react'; import { Button, Typography, @@ -35,6 +35,8 @@ import { CodeSnippet, StructuredMetadataTable, } from '@backstage/core-components'; +import { ClusterContext } from '../../hooks'; +import { formatClusterLink } from '../../utils/clusterLinks'; const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -56,7 +58,7 @@ const useDrawerContentStyles = makeStyles((_: Theme) => options: { display: 'flex', flexDirection: 'row', - justifyContent: 'flex-end', + justifyContent: 'space-between', }, icon: { fontSize: 20, @@ -105,6 +107,12 @@ const KubernetesDrawerContent = ({ const [isYaml, setIsYaml] = useState(false); const classes = useDrawerContentStyles(); + const cluster = useContext(ClusterContext); + const clusterLink = formatClusterLink( + cluster.dashboardUrl ?? '', + object, + kind, + ); return ( <> @@ -136,6 +144,19 @@ const KubernetesDrawerContent = ({
+
+ {clusterLink && ( + + )} +
({ + name: '', +}); diff --git a/plugins/kubernetes/src/hooks/index.ts b/plugins/kubernetes/src/hooks/index.ts index e25903ad3a..88db2d8197 100644 --- a/plugins/kubernetes/src/hooks/index.ts +++ b/plugins/kubernetes/src/hooks/index.ts @@ -17,3 +17,4 @@ export * from './useKubernetesObjects'; export * from './PodNamesWithErrors'; export * from './GroupedResponses'; +export * from './Cluster'; diff --git a/plugins/kubernetes/src/utils/clusterLinks.test.ts b/plugins/kubernetes/src/utils/clusterLinks.test.ts new file mode 100644 index 0000000000..d363b35b3c --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks.test.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { formatClusterLink } from './clusterLinks'; + +describe('clusterLinks', () => { + describe('formatClusterLink', () => { + it('should not return an url when there is no dashboard url', () => { + const url = formatClusterLink('', {}, 'foo'); + expect(url).toBeUndefined(); + }); + it('should return an url even when there is no object', () => { + const url = formatClusterLink('https://k8s.foo.com', undefined, 'foo'); + expect(url).toBe('https://k8s.foo.com'); + }); + it('should return an url on the workloads when there is a namespace only', () => { + const url = formatClusterLink( + 'https://k8s.foo.com', + { + metadata: { + namespace: 'bar', + }, + }, + 'foo', + ); + expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); + }); + it('should return an url on the workloads when the kind is not recognizeed', () => { + const url = formatClusterLink( + 'https://k8s.foo.com', + { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + 'UnknownKind', + ); + expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); + }); + it('should return an url on the deployment', () => { + const url = formatClusterLink( + 'https://k8s.foo.com/', + { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + 'Deployment', + ); + expect(url).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the service', () => { + const url = formatClusterLink( + 'https://k8s.foo.com/', + { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + 'Service', + ); + expect(url).toBe( + 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the ingress', () => { + const url = formatClusterLink( + 'https://k8s.foo.com/', + { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + 'Ingress', + ); + expect(url).toBe( + 'https://k8s.foo.com/#/ingress/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the deployment for a hpa', () => { + const url = formatClusterLink( + 'https://k8s.foo.com/', + { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + 'HorizontalPodAutoscaler', + ); + expect(url).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks.ts b/plugins/kubernetes/src/utils/clusterLinks.ts new file mode 100644 index 0000000000..342c86956f --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const KindMappings: any = { + deployment: 'deployment', + ingress: 'ingress', + service: 'service', + horizontalpodautoscaler: 'deployment', +}; + +export function formatClusterLink( + dashboardUrl: string, + object: any, + kind: string, +) { + if (!dashboardUrl) { + return undefined; + } + if (!object) { + return dashboardUrl; + } + const host = dashboardUrl.endsWith('/') ? dashboardUrl : `${dashboardUrl}/`; + const name = object.metadata?.name; + const namespace = object.metadata?.namespace; + const validKind = KindMappings[kind.toLocaleLowerCase()]; + if (validKind && name && namespace) { + return `${host}#/${validKind}/${namespace}/${name}?namespace=${namespace}`; + } + if (namespace) { + return `${host}#/workloads?namespace=${namespace}`; + } + return dashboardUrl; +} From 891d2bec18b6344051a498edd2208596f040f2ef Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Tue, 10 Aug 2021 16:23:38 -0400 Subject: [PATCH 02/10] fix failing unit tests in kubernetes-backend Signed-off-by: Morgan Martinet --- .../src/cluster-locator/ConfigClusterLocator.test.ts | 6 ++++++ .../kubernetes-backend/src/cluster-locator/index.test.ts | 2 ++ .../src/service/KubernetesFanOutHandler.test.ts | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index dac96e4cef..cd31db807e 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -49,6 +49,7 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { name: 'cluster1', + dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -66,6 +67,7 @@ describe('ConfigClusterLocator', () => { url: 'http://localhost:8080', authProvider: 'serviceAccount', skipTLSVerify: false, + dashboardUrl: 'https://k8s.foo.com', }, { name: 'cluster2', @@ -83,6 +85,7 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { name: 'cluster1', + dashboardUrl: 'https://k8s.foo.com', serviceAccountToken: 'token', url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -90,6 +93,7 @@ describe('ConfigClusterLocator', () => { }, { name: 'cluster2', + dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8081', authProvider: 'google', @@ -133,6 +137,7 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { assumeRole: undefined, + dashboardUrl: undefined, name: 'cluster1', serviceAccountToken: 'token', externalId: undefined, @@ -142,6 +147,7 @@ describe('ConfigClusterLocator', () => { }, { assumeRole: 'SomeRole', + dashboardUrl: undefined, name: 'cluster2', externalId: undefined, serviceAccountToken: undefined, diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 95be99a8a5..9b01a4b0f3 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -50,6 +50,7 @@ describe('getCombinedClusterDetails', () => { expect(result).toStrictEqual([ { name: 'cluster1', + dashboardUrl: undefined, serviceAccountToken: 'token', url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -57,6 +58,7 @@ describe('getCombinedClusterDetails', () => { }, { name: 'cluster2', + dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8081', authProvider: 'google', diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index a867efaaee..28e8f45603 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -165,6 +165,7 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { + dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -211,6 +212,7 @@ describe('handleGetKubernetesObjectsForService', () => { { name: 'test-cluster', authProvider: 'serviceAccount', + dashboardUrl: 'https://k8s.foo.coom', }, { name: 'other-cluster', @@ -260,6 +262,7 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { + dashboardUrl: 'https://k8s.foo.coom', name: 'test-cluster', }, errors: [], @@ -298,6 +301,7 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { + dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -396,6 +400,7 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { + dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -434,6 +439,7 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { + dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -536,6 +542,7 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { + dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -574,6 +581,7 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { + dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -612,6 +620,7 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { + dashboardUrl: undefined, name: 'error-cluster', }, errors: ['some random cluster error'], From c80f53a4b543e1e5c4cef7798095fe6e85cf79e2 Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Fri, 13 Aug 2021 10:35:08 -0400 Subject: [PATCH 03/10] minor refactorings after code review Signed-off-by: Morgan Martinet --- .changeset/tiny-berries-battle.md | 6 +- .../ConfigClusterLocator.test.ts | 4 -- .../cluster-locator/ConfigClusterLocator.ts | 7 +- .../src/cluster-locator/index.test.ts | 2 - .../service/KubernetesFanOutHandler.test.ts | 7 -- .../src/service/KubernetesFanOutHandler.ts | 12 +++- .../KubernetesDrawer/KubernetesDrawer.tsx | 11 +-- .../kubernetes/src/utils/clusterLinks.test.ts | 68 ++++++++++--------- plugins/kubernetes/src/utils/clusterLinks.ts | 36 +++++----- 9 files changed, 79 insertions(+), 74 deletions(-) diff --git a/.changeset/tiny-berries-battle.md b/.changeset/tiny-berries-battle.md index 9e3194a317..fdcb612d61 100644 --- a/.changeset/tiny-berries-battle.md +++ b/.changeset/tiny-berries-battle.md @@ -1,7 +1,7 @@ --- -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-kubernetes-backend': minor -'@backstage/plugin-kubernetes-common': minor +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch --- Provide access to the Kubernetes dashboard when viewing a specific resource diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index cd31db807e..6d1506c4c7 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -49,7 +49,6 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { name: 'cluster1', - dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -93,7 +92,6 @@ describe('ConfigClusterLocator', () => { }, { name: 'cluster2', - dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8081', authProvider: 'google', @@ -137,7 +135,6 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { assumeRole: undefined, - dashboardUrl: undefined, name: 'cluster1', serviceAccountToken: 'token', externalId: undefined, @@ -147,7 +144,6 @@ describe('ConfigClusterLocator', () => { }, { assumeRole: 'SomeRole', - dashboardUrl: undefined, name: 'cluster2', externalId: undefined, serviceAccountToken: undefined, diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index a946871e7b..49ebeaade1 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -30,14 +30,17 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { return new ConfigClusterLocator( config.getConfigArray('clusters').map(c => { const authProvider = c.getString('authProvider'); - const clusterDetails = { + const clusterDetails: ClusterDetails = { name: c.getString('name'), url: c.getString('url'), - dashboardUrl: c.getOptionalString('dashboardUrl'), serviceAccountToken: c.getOptionalString('serviceAccountToken'), skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false, authProvider: authProvider, }; + const dashboardUrl = c.getOptionalString('dashboardUrl'); + if (dashboardUrl) { + clusterDetails.dashboardUrl = dashboardUrl; + } switch (authProvider) { case 'google': { diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 9b01a4b0f3..95be99a8a5 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -50,7 +50,6 @@ describe('getCombinedClusterDetails', () => { expect(result).toStrictEqual([ { name: 'cluster1', - dashboardUrl: undefined, serviceAccountToken: 'token', url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -58,7 +57,6 @@ describe('getCombinedClusterDetails', () => { }, { name: 'cluster2', - dashboardUrl: undefined, serviceAccountToken: undefined, url: 'http://localhost:8081', authProvider: 'google', diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 28e8f45603..7973448467 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -165,7 +165,6 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { - dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -301,7 +300,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { - dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -400,7 +398,6 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { - dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -439,7 +436,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { - dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -542,7 +538,6 @@ describe('handleGetKubernetesObjectsForService', () => { items: [ { cluster: { - dashboardUrl: undefined, name: 'test-cluster', }, errors: [], @@ -581,7 +576,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { - dashboardUrl: undefined, name: 'other-cluster', }, errors: [], @@ -620,7 +614,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, { cluster: { - dashboardUrl: undefined, name: 'error-cluster', }, errors: ['some random cluster error'], diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 1777d4592a..a1b6f90040 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -22,7 +22,10 @@ import { KubernetesObjectTypes, KubernetesServiceLocator, } from '../types/types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { + ClusterObjects, + KubernetesRequestBody, +} from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; @@ -111,14 +114,17 @@ export class KubernetesFanOutHandler { customResources: this.customResources, }) .then(result => { - return { + const objects: ClusterObjects = { cluster: { name: clusterDetailsItem.name, - dashboardUrl: clusterDetailsItem.dashboardUrl, }, resources: result.responses, errors: result.errors, }; + if (clusterDetailsItem.dashboardUrl) { + objects.cluster.dashboardUrl = clusterDetailsItem.dashboardUrl; + } + return objects; }); }), ).then(r => ({ diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 1fc2f297d3..050d7222d8 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -34,6 +34,7 @@ import jsYaml from 'js-yaml'; import { CodeSnippet, StructuredMetadataTable, + Link, } from '@backstage/core-components'; import { ClusterContext } from '../../hooks'; import { formatClusterLink } from '../../utils/clusterLinks'; @@ -108,11 +109,11 @@ const KubernetesDrawerContent = ({ const classes = useDrawerContentStyles(); const cluster = useContext(ClusterContext); - const clusterLink = formatClusterLink( - cluster.dashboardUrl ?? '', + const clusterLink = formatClusterLink({ + dashboardUrl: cluster.dashboardUrl, object, kind, - ); + }); return ( <> @@ -150,8 +151,8 @@ const KubernetesDrawerContent = ({ variant="contained" color="primary" size="small" - href={clusterLink} - target="_blank" + component={Link} + to={clusterLink} > Open Kubernetes Dashboard... diff --git a/plugins/kubernetes/src/utils/clusterLinks.test.ts b/plugins/kubernetes/src/utils/clusterLinks.test.ts index d363b35b3c..7c649a34b9 100644 --- a/plugins/kubernetes/src/utils/clusterLinks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks.test.ts @@ -19,94 +19,98 @@ import { formatClusterLink } from './clusterLinks'; describe('clusterLinks', () => { describe('formatClusterLink', () => { it('should not return an url when there is no dashboard url', () => { - const url = formatClusterLink('', {}, 'foo'); + const url = formatClusterLink({ object: {}, kind: 'foo' }); expect(url).toBeUndefined(); }); it('should return an url even when there is no object', () => { - const url = formatClusterLink('https://k8s.foo.com', undefined, 'foo'); + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + object: undefined, + kind: 'foo', + }); expect(url).toBe('https://k8s.foo.com'); }); it('should return an url on the workloads when there is a namespace only', () => { - const url = formatClusterLink( - 'https://k8s.foo.com', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + object: { metadata: { namespace: 'bar', }, }, - 'foo', - ); + kind: 'foo', + }); expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); }); it('should return an url on the workloads when the kind is not recognizeed', () => { - const url = formatClusterLink( - 'https://k8s.foo.com', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + object: { metadata: { name: 'foobar', namespace: 'bar', }, }, - 'UnknownKind', - ); + kind: 'UnknownKind', + }); expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); }); it('should return an url on the deployment', () => { - const url = formatClusterLink( - 'https://k8s.foo.com/', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { metadata: { name: 'foobar', namespace: 'bar', }, }, - 'Deployment', - ); + kind: 'Deployment', + }); expect(url).toBe( 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', ); }); it('should return an url on the service', () => { - const url = formatClusterLink( - 'https://k8s.foo.com/', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { metadata: { name: 'foobar', namespace: 'bar', }, }, - 'Service', - ); + kind: 'Service', + }); expect(url).toBe( 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', ); }); it('should return an url on the ingress', () => { - const url = formatClusterLink( - 'https://k8s.foo.com/', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { metadata: { name: 'foobar', namespace: 'bar', }, }, - 'Ingress', - ); + kind: 'Ingress', + }); expect(url).toBe( 'https://k8s.foo.com/#/ingress/bar/foobar?namespace=bar', ); }); it('should return an url on the deployment for a hpa', () => { - const url = formatClusterLink( - 'https://k8s.foo.com/', - { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { metadata: { name: 'foobar', namespace: 'bar', }, }, - 'HorizontalPodAutoscaler', - ); + kind: 'HorizontalPodAutoscaler', + }); expect(url).toBe( 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', ); diff --git a/plugins/kubernetes/src/utils/clusterLinks.ts b/plugins/kubernetes/src/utils/clusterLinks.ts index 342c86956f..53f771a1b6 100644 --- a/plugins/kubernetes/src/utils/clusterLinks.ts +++ b/plugins/kubernetes/src/utils/clusterLinks.ts @@ -14,33 +14,37 @@ * limitations under the License. */ -const KindMappings: any = { +const KindMappings: Record = { deployment: 'deployment', ingress: 'ingress', service: 'service', horizontalpodautoscaler: 'deployment', }; -export function formatClusterLink( - dashboardUrl: string, - object: any, - kind: string, -) { - if (!dashboardUrl) { +export function formatClusterLink(options: { + dashboardUrl?: string; + object: any; + kind: string; +}) { + if (!options.dashboardUrl) { return undefined; } - if (!object) { - return dashboardUrl; + if (!options.object) { + return options.dashboardUrl; } - const host = dashboardUrl.endsWith('/') ? dashboardUrl : `${dashboardUrl}/`; - const name = object.metadata?.name; - const namespace = object.metadata?.namespace; - const validKind = KindMappings[kind.toLocaleLowerCase()]; + const host = options.dashboardUrl.endsWith('/') + ? options.dashboardUrl + : `${options.dashboardUrl}/`; + const name = options.object.metadata?.name; + const namespace = options.object.metadata?.namespace; + const validKind = KindMappings[options.kind.toLocaleLowerCase()]; if (validKind && name && namespace) { - return `${host}#/${validKind}/${namespace}/${name}?namespace=${namespace}`; + return `${host}#/${encodeURIComponent(validKind)}/${encodeURIComponent( + namespace, + )}/${encodeURIComponent(name)}?namespace=${encodeURIComponent(namespace)}`; } if (namespace) { - return `${host}#/workloads?namespace=${namespace}`; + return `${host}#/workloads?namespace=${encodeURIComponent(namespace)}`; } - return dashboardUrl; + return options.dashboardUrl; } From 10f8c37595c40d8fabf889dfa169813edd3e6a21 Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Fri, 13 Aug 2021 18:16:21 -0400 Subject: [PATCH 04/10] fix compilation error in KubernetesDrawer Signed-off-by: Morgan Martinet --- .../src/components/KubernetesDrawer/KubernetesDrawer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 050d7222d8..31b87052c5 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -32,6 +32,7 @@ import { V1ObjectMeta } from '@kubernetes/client-node'; import { withStyles } from '@material-ui/core/styles'; import jsYaml from 'js-yaml'; import { + Button as BackstageButton, CodeSnippet, StructuredMetadataTable, Link, @@ -147,7 +148,7 @@ const KubernetesDrawerContent = ({
{clusterLink && ( - + )}
Date: Fri, 13 Aug 2021 23:09:43 -0400 Subject: [PATCH 05/10] regen api-report.md and rebase branch on master Signed-off-by: Morgan Martinet --- plugins/kubernetes-backend/api-report.md | 7 +++++-- plugins/kubernetes-common/api-report.md | 14 +++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index f69977f5ad..ee3bea69f9 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -3,10 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; import express from 'express'; -import { FetchResponse } from '@backstage/plugin-kubernetes-common'; -import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; +import type { FetchResponse } from '@backstage/plugin-kubernetes-common'; +import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger as Logger_2 } from 'winston'; @@ -27,6 +28,8 @@ export interface ClusterDetails { // (undocumented) authProvider: string; // (undocumented) + dashboardUrl?: string; + // (undocumented) name: string; // (undocumented) serviceAccountToken?: string | undefined; diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 04f54b1f1a..11ce9d10de 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -17,14 +17,22 @@ import { V1Service } from '@kubernetes/client-node'; // @public (undocumented) export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; +// Warning: (ae-missing-release-tag) "ClusterAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClusterAttributes { + // (undocumented) + dashboardUrl?: string; + // (undocumented) + name: string; +} + // Warning: (ae-missing-release-tag) "ClusterObjects" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface ClusterObjects { // (undocumented) - cluster: { - name: string; - }; + cluster: ClusterAttributes; // (undocumented) errors: KubernetesFetchError[]; // (undocumented) From abf7c46404bcb06156f3630fa67be5271eaa46ae Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Sun, 29 Aug 2021 19:07:34 -0400 Subject: [PATCH 06/10] refactor code in order to support multiple dashboard link formatters Signed-off-by: Morgan Martinet --- docs/features/kubernetes/configuration.md | 36 +++++ plugins/kubernetes-backend/api-report.md | 3 +- .../cluster-locator/ConfigClusterLocator.ts | 4 + .../src/service/KubernetesFanOutHandler.ts | 3 + plugins/kubernetes-backend/src/types/types.ts | 27 ++++ plugins/kubernetes-common/api-report.md | 3 +- plugins/kubernetes-common/src/types.ts | 27 ++++ plugins/kubernetes/api-report.md | 16 +++ .../KubernetesDrawer/KubernetesDrawer.tsx | 1 + plugins/kubernetes/src/index.ts | 1 + plugins/kubernetes/src/types/types.ts | 10 ++ .../kubernetes/src/utils/clusterLinks.test.ts | 119 ---------------- .../clusterLinks/formatClusterLink.test.ts | 133 ++++++++++++++++++ .../utils/clusterLinks/formatClusterLink.ts | 45 ++++++ .../utils/clusterLinks/formatters/aks.test.ts | 33 +++++ .../src/utils/clusterLinks/formatters/aks.ts | 20 +++ .../utils/clusterLinks/formatters/eks.test.ts | 33 +++++ .../src/utils/clusterLinks/formatters/eks.ts | 20 +++ .../utils/clusterLinks/formatters/gke.test.ts | 33 +++++ .../src/utils/clusterLinks/formatters/gke.ts | 20 +++ .../utils/clusterLinks/formatters/index.ts | 26 ++++ .../clusterLinks/formatters/openshift.test.ts | 33 +++++ .../clusterLinks/formatters/openshift.ts | 22 +++ .../clusterLinks/formatters/rancher.test.ts | 33 +++++ .../utils/clusterLinks/formatters/rancher.ts | 20 +++ .../clusterLinks/formatters/standard.test.ts | 115 +++++++++++++++ .../formatters/standard.ts} | 31 ++-- .../src/utils/clusterLinks/index.ts | 18 +++ plugins/kubernetes/src/utils/index.ts | 16 +++ 29 files changed, 757 insertions(+), 144 deletions(-) delete mode 100644 plugins/kubernetes/src/utils/clusterLinks.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts create mode 100644 plugins/kubernetes/src/utils/clusterLinks/formatters/standard.test.ts rename plugins/kubernetes/src/utils/{clusterLinks.ts => clusterLinks/formatters/standard.ts} (62%) create mode 100644 plugins/kubernetes/src/utils/clusterLinks/index.ts create mode 100644 plugins/kubernetes/src/utils/index.ts diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 068a17249c..737a753635 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -27,6 +27,8 @@ kubernetes: authProvider: 'serviceAccount' skipTLSVerify: false serviceAccountToken: ${K8S_MINIKUBE_TOKEN} + dashboardUrl: http://127.0.0.1:64713 # url copied from running the command: minikube service kubernetes-dashboard -n kubernetes-dashboard + dashboardApp: standard - url: http://127.0.0.2:9999 name: aws-cluster-1 authProvider: 'aws' @@ -98,6 +100,40 @@ kubectl -n get secret $(kubectl -n get sa ...; +``` + +See also +https://github.com/backstage/backstage/tree/master/plugins/kubernetes/src/utils/clusterLinks/formatters +for real examples. + #### `gke` This cluster locator is designed to work with Kubernetes clusters running in diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index ee3bea69f9..2a52089c49 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -27,9 +27,8 @@ export interface AWSClusterDetails extends ClusterDetails { export interface ClusterDetails { // (undocumented) authProvider: string; - // (undocumented) + dashboardApp?: string; dashboardUrl?: string; - // (undocumented) name: string; // (undocumented) serviceAccountToken?: string | undefined; diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 49ebeaade1..53d3a1d025 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -41,6 +41,10 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { if (dashboardUrl) { clusterDetails.dashboardUrl = dashboardUrl; } + const dashboardApp = c.getOptionalString('dashboardApp'); + if (dashboardApp) { + clusterDetails.dashboardApp = dashboardApp; + } switch (authProvider) { case 'google': { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index a1b6f90040..fd403f49d1 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -124,6 +124,9 @@ export class KubernetesFanOutHandler { if (clusterDetailsItem.dashboardUrl) { objects.cluster.dashboardUrl = clusterDetailsItem.dashboardUrl; } + if (clusterDetailsItem.dashboardApp) { + objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp; + } return objects; }); }), diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index f6c3f1e3ab..fe62d0fd78 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -75,12 +75,39 @@ export interface KubernetesServiceLocator { export type ServiceLocatorMethod = 'multiTenant' | 'http'; // TODO implement http export interface ClusterDetails { + /** + * Specifies the name of the Kubernetes cluster. + */ name: string; url: string; authProvider: string; serviceAccountToken?: string | undefined; skipTLSVerify?: boolean; + /** + * Specifies the link to the Kubernetes dashboard managing this cluster. + * @remarks + * Note that you need to specify the app used for the dashboard + * using the dashboardApp property, in order to properly format + * links to kubernetes resources. + * @see dashboardApp + */ dashboardUrl?: string; + /** + * Specifies the app that provides the Kubernetes dashboard. + * This will be used for formatting links to kubernetes objects inside the dashboard. + * @remarks + * The existing apps are: standard, rancher, openshift, gke, aks, eks + * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). + * Note that you can add your own formatter by registering it to the formatters dictionary. + * @defaultValue standard + * @see dashboardUrl + * @example + * ```ts + * import { clusterLinksFormatters } from '@backstage/plugin-kubernetes'; + * clusterLinksFormatters.myDashboard = (options) => ...; + * ``` + */ + dashboardApp?: string; } export interface GKEClusterDetails extends ClusterDetails {} diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 11ce9d10de..7981c29b4e 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -21,9 +21,8 @@ export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; // // @public (undocumented) export interface ClusterAttributes { - // (undocumented) + dashboardApp?: string; dashboardUrl?: string; - // (undocumented) name: string; } diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 1da1390207..8b8aba9a93 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -33,8 +33,35 @@ export interface KubernetesRequestBody { } export interface ClusterAttributes { + /** + * Specifies the name of the Kubernetes cluster. + */ name: string; + /** + * Specifies the link to the Kubernetes dashboard managing this cluster. + * @remarks + * Note that you need to specify the app used for the dashboard + * using the dashboardApp property, in order to properly format + * links to kubernetes resources. + * @see dashboardApp + */ dashboardUrl?: string; + /** + * Specifies the app that provides the Kubernetes dashboard. + * This will be used for formatting links to kubernetes objects inside the dashboard. + * @remarks + * The supported dashboards are: standard, rancher, openshift, gke, aks, eks + * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). + * Note that you can add your own formatter by registering it to the formatters dictionary. + * @defaultValue standard + * @see dashboardUrl + * @example + * ```ts + * import { clusterLinksFormatters } from '@backstage/plugin-kubernetes'; + * clusterLinksFormatters.myDashboard = (options) => ...; + * ``` + */ + dashboardApp?: string; } export interface ClusterObjects { diff --git a/plugins/kubernetes/api-report.md b/plugins/kubernetes/api-report.md index 0a3b466942..e3c239949b 100644 --- a/plugins/kubernetes/api-report.md +++ b/plugins/kubernetes/api-report.md @@ -12,6 +12,12 @@ import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { OAuthApi } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; +// Warning: (ae-forgotten-export) The symbol "ClusterLinksFormatter" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "clusterLinksFormatters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const clusterLinksFormatters: Record; + // Warning: (ae-missing-release-tag) "EntityKubernetesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -19,6 +25,16 @@ export const EntityKubernetesContent: (_props: { entity?: Entity | undefined; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "formatClusterLink" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function formatClusterLink(options: { + dashboardUrl?: string; + dashboardApp?: string; + object: any; + kind: string; +}): string | undefined; + // Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvidersApi" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "KubernetesAuthProviders" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 31b87052c5..4a51655484 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -112,6 +112,7 @@ const KubernetesDrawerContent = ({ const cluster = useContext(ClusterContext); const clusterLink = formatClusterLink({ dashboardUrl: cluster.dashboardUrl, + dashboardApp: cluster.dashboardApp, object, kind, }); diff --git a/plugins/kubernetes/src/index.ts b/plugins/kubernetes/src/index.ts index 23acc3e7ca..4f4c3d03da 100644 --- a/plugins/kubernetes/src/index.ts +++ b/plugins/kubernetes/src/index.ts @@ -20,3 +20,4 @@ export { } from './plugin'; export { Router } from './Router'; export * from './kubernetes-auth-provider'; +export * from './utils/clusterLinks'; diff --git a/plugins/kubernetes/src/types/types.ts b/plugins/kubernetes/src/types/types.ts index 0817c90f97..05337086be 100644 --- a/plugins/kubernetes/src/types/types.ts +++ b/plugins/kubernetes/src/types/types.ts @@ -37,3 +37,13 @@ export interface GroupedResponses extends DeploymentResources { ingresses: ExtensionsV1beta1Ingress[]; customResources: any[]; } + +export interface ClusterLinksFormatterOptions { + dashboardUrl: URL; + object: any; + kind: string; +} + +export type ClusterLinksFormatter = ( + options: ClusterLinksFormatterOptions, +) => URL; diff --git a/plugins/kubernetes/src/utils/clusterLinks.test.ts b/plugins/kubernetes/src/utils/clusterLinks.test.ts deleted file mode 100644 index 7c649a34b9..0000000000 --- a/plugins/kubernetes/src/utils/clusterLinks.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { formatClusterLink } from './clusterLinks'; - -describe('clusterLinks', () => { - describe('formatClusterLink', () => { - it('should not return an url when there is no dashboard url', () => { - const url = formatClusterLink({ object: {}, kind: 'foo' }); - expect(url).toBeUndefined(); - }); - it('should return an url even when there is no object', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com', - object: undefined, - kind: 'foo', - }); - expect(url).toBe('https://k8s.foo.com'); - }); - it('should return an url on the workloads when there is a namespace only', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com', - object: { - metadata: { - namespace: 'bar', - }, - }, - kind: 'foo', - }); - expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); - }); - it('should return an url on the workloads when the kind is not recognizeed', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'UnknownKind', - }); - expect(url).toBe('https://k8s.foo.com/#/workloads?namespace=bar'); - }); - it('should return an url on the deployment', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', - ); - }); - it('should return an url on the service', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Service', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', - ); - }); - it('should return an url on the ingress', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Ingress', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/ingress/bar/foobar?namespace=bar', - ); - }); - it('should return an url on the deployment for a hpa', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'HorizontalPodAutoscaler', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', - ); - }); - }); -}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.test.ts new file mode 100644 index 0000000000..afc53d9509 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { formatClusterLink } from './formatClusterLink'; + +describe('clusterLinks', () => { + describe('formatClusterLink', () => { + it('should not return an url when there is no dashboard url', () => { + const url = formatClusterLink({ object: {}, kind: 'foo' }); + expect(url).toBeUndefined(); + }); + it('should return an url even when there is no object', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + object: undefined, + kind: 'foo', + }); + expect(url).toBe('https://k8s.foo.com'); + }); + it('should throw when the app is not recognized', () => { + expect(() => + formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + dashboardApp: 'unknownapp', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError( + "Could not find Kubernetes dashboard app named 'unknownapp'", + ); + }); + + describe('default app', () => { + it('should return an url on the deployment', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }); + expect(url).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the service', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Service', + }); + expect(url).toBe( + 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the deployment properly url encoded', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { + metadata: { + name: 'foobar', + namespace: 'bar bar', + }, + }, + kind: 'Deployment', + }); + expect(url).toBe( + 'https://k8s.foo.com/#/deployment/bar%20bar/foobar?namespace=bar+bar', + ); + }); + }); + + describe('standard app', () => { + it('should return an url on the deployment', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + dashboardApp: 'standard', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }); + expect(url).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the service', () => { + const url = formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + dashboardApp: 'standard', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Service', + }); + expect(url).toBe( + 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', + ); + }); + }); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts new file mode 100644 index 0000000000..f04ec1daea --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defaultFormatterName, clusterLinksFormatters } from './formatters'; + +export function formatClusterLink(options: { + dashboardUrl?: string; + dashboardApp?: string; + object: any; + kind: string; +}) { + if (!options.dashboardUrl) { + return undefined; + } + if (!options.object) { + return options.dashboardUrl; + } + const app = options.dashboardApp || defaultFormatterName; + const formatter = clusterLinksFormatters[app]; + if (!formatter) { + throw new Error(`Could not find Kubernetes dashboard app named '${app}'`); + } + const url = formatter({ + dashboardUrl: new URL(options.dashboardUrl), + object: options.object, + kind: options.kind, + }); + // Note that we can't rely on 'url.href' since it will put the search before the hash + // and this won't be properly recognized by SPAs such as Angular in the standard dashboard. + // Note also that pathname, hash and search will be properly url encoded. + return `${url.origin}${url.pathname}${url.hash}${url.search}`; +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts new file mode 100644 index 0000000000..381df0dc28 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { aksFormatter } from './aks'; + +describe('clusterLinks - aks formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + expect(() => + aksFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError('AKS formatter is not yet implemented'); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts new file mode 100644 index 0000000000..ef221b604d --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; + +export function aksFormatter(_options: ClusterLinksFormatterOptions): URL { + throw new Error('AKS formatter is not yet implemented'); +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts new file mode 100644 index 0000000000..a1a5252bc4 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { eksFormatter } from './eks'; + +describe('clusterLinks - aks formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + expect(() => + eksFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError('EKS formatter is not yet implemented'); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts new file mode 100644 index 0000000000..a9fb2f265b --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; + +export function eksFormatter(_options: ClusterLinksFormatterOptions): URL { + throw new Error('EKS formatter is not yet implemented'); +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts new file mode 100644 index 0000000000..56e8b510d3 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { gkeFormatter } from './gke'; + +describe('clusterLinks - aks formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + expect(() => + gkeFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError('GKE formatter is not yet implemented'); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts new file mode 100644 index 0000000000..858b872389 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; + +export function gkeFormatter(_options: ClusterLinksFormatterOptions): URL { + throw new Error('GKE formatter is not yet implemented'); +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts new file mode 100644 index 0000000000..f0530f1f7b --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatter } from '../../../types/types'; +import { standardFormatter } from './standard'; +import { rancherFormatter } from './rancher'; +import { openshiftFormatter } from './openshift'; + +export const clusterLinksFormatters: Record = { + standard: standardFormatter, + rancher: rancherFormatter, + openshift: openshiftFormatter, +}; +export const defaultFormatterName = 'standard'; diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts new file mode 100644 index 0000000000..f9a70d8d9a --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { openshiftFormatter } from './openshift'; + +describe('clusterLinks - aks formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + expect(() => + openshiftFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError('OpenShift formatter is not yet implemented'); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts new file mode 100644 index 0000000000..acab9ec1cb --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; + +export function openshiftFormatter( + _options: ClusterLinksFormatterOptions, +): URL { + throw new Error('OpenShift formatter is not yet implemented'); +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts new file mode 100644 index 0000000000..61de1a0a4e --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { rancherFormatter } from './rancher'; + +describe('clusterLinks - aks formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + expect(() => + rancherFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError('Rancher formatter is not yet implemented'); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts new file mode 100644 index 0000000000..962f494408 --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; + +export function rancherFormatter(_options: ClusterLinksFormatterOptions): URL { + throw new Error('Rancher formatter is not yet implemented'); +} diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.test.ts new file mode 100644 index 0000000000..76bbf5643d --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.test.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { standardFormatter } from './standard'; + +function formatUrl(url: URL) { + // Note that we can't rely on 'url.href' since it will put the search before the hash + // and this won't be properly recognized by SPAs such as Angular in the standard dashboard. + // Note also that pathname, hash and search will be properly url encoded. + return `${url.origin}${url.pathname}${url.hash}${url.search}`; +} + +describe('clusterLinks - standard formatter', () => { + it('should return an url on the workloads when there is a namespace only', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + namespace: 'bar', + }, + }, + kind: 'foo', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/workloads?namespace=bar', + ); + }); + it('should return an url on the workloads when the kind is not recognizeed', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'UnknownKind', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/workloads?namespace=bar', + ); + }); + it('should return an url on the deployment', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com/'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the service', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com/'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Service', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the ingress', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com/'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Ingress', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/ingress/bar/foobar?namespace=bar', + ); + }); + it('should return an url on the deployment for a hpa', () => { + const url = standardFormatter({ + dashboardUrl: new URL('https://k8s.foo.com/'), + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'HorizontalPodAutoscaler', + }); + expect(formatUrl(url)).toBe( + 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', + ); + }); +}); diff --git a/plugins/kubernetes/src/utils/clusterLinks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts similarity index 62% rename from plugins/kubernetes/src/utils/clusterLinks.ts rename to plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts index 53f771a1b6..06a8f9e0a8 100644 --- a/plugins/kubernetes/src/utils/clusterLinks.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ClusterLinksFormatterOptions } from '../../../types/types'; const KindMappings: Record = { deployment: 'deployment', @@ -21,30 +22,18 @@ const KindMappings: Record = { horizontalpodautoscaler: 'deployment', }; -export function formatClusterLink(options: { - dashboardUrl?: string; - object: any; - kind: string; -}) { - if (!options.dashboardUrl) { - return undefined; - } - if (!options.object) { - return options.dashboardUrl; - } - const host = options.dashboardUrl.endsWith('/') - ? options.dashboardUrl - : `${options.dashboardUrl}/`; +export function standardFormatter(options: ClusterLinksFormatterOptions) { + const result = new URL(options.dashboardUrl.href); const name = options.object.metadata?.name; const namespace = options.object.metadata?.namespace; const validKind = KindMappings[options.kind.toLocaleLowerCase()]; - if (validKind && name && namespace) { - return `${host}#/${encodeURIComponent(validKind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}?namespace=${encodeURIComponent(namespace)}`; - } if (namespace) { - return `${host}#/workloads?namespace=${encodeURIComponent(namespace)}`; + result.searchParams.set('namespace', namespace); } - return options.dashboardUrl; + if (validKind && name && namespace) { + result.hash = `/${validKind}/${namespace}/${name}`; + } else if (namespace) { + result.hash = '/workloads'; + } + return result; } diff --git a/plugins/kubernetes/src/utils/clusterLinks/index.ts b/plugins/kubernetes/src/utils/clusterLinks/index.ts new file mode 100644 index 0000000000..0a2a32cdbf --- /dev/null +++ b/plugins/kubernetes/src/utils/clusterLinks/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { formatClusterLink } from './formatClusterLink'; +export { clusterLinksFormatters } from './formatters'; diff --git a/plugins/kubernetes/src/utils/index.ts b/plugins/kubernetes/src/utils/index.ts new file mode 100644 index 0000000000..d47afaf668 --- /dev/null +++ b/plugins/kubernetes/src/utils/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './clusterLinks'; From 06cc986113d2a57b9e90f6e601c5dbe9d72ab3f8 Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Sun, 29 Aug 2021 19:32:22 -0400 Subject: [PATCH 07/10] fix documentation errors Signed-off-by: Morgan Martinet --- docs/features/kubernetes/configuration.md | 9 +++++---- plugins/kubernetes-backend/src/types/types.ts | 2 +- plugins/kubernetes-common/src/types.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 737a753635..4bbc28c178 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -115,13 +115,14 @@ Specifies the app that provides the Kubernetes dashboard. This will be used for formatting links to kubernetes objects inside the dashboard. -The supported dashboards are: standard, rancher, openshift, gke, aks, eks +The supported dashboards are: `standard`, `rancher`, `openshift`, `gke`, `aks`, +`eks` Note that it will default to the regular dashboard provided by the Kubernetes -project (standard), that can run in any kubernetes cluster. +project (`standard`), that can run in any Kubernetes cluster. -Note that you can add your own formatter by registering it to the formatters -dictionary, in the app project. +Note that you can add your own formatter by registering it to the +`clusterLinksFormatters` dictionary, in the app project. Example: diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index fe62d0fd78..3f04ea80d9 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -98,7 +98,7 @@ export interface ClusterDetails { * @remarks * The existing apps are: standard, rancher, openshift, gke, aks, eks * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). - * Note that you can add your own formatter by registering it to the formatters dictionary. + * Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary. * @defaultValue standard * @see dashboardUrl * @example diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 8b8aba9a93..6eda82f9b7 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -52,7 +52,7 @@ export interface ClusterAttributes { * @remarks * The supported dashboards are: standard, rancher, openshift, gke, aks, eks * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). - * Note that you can add your own formatter by registering it to the formatters dictionary. + * Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary. * @defaultValue standard * @see dashboardUrl * @example From 991356f3afc7794a224e227e2235c1dfaa87ff27 Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Sun, 29 Aug 2021 20:30:51 -0400 Subject: [PATCH 08/10] add missing link formatters to the registry Signed-off-by: Morgan Martinet --- .../src/utils/clusterLinks/formatters/aks.test.ts | 2 +- .../src/utils/clusterLinks/formatters/eks.test.ts | 2 +- .../src/utils/clusterLinks/formatters/gke.test.ts | 2 +- .../kubernetes/src/utils/clusterLinks/formatters/index.ts | 6 ++++++ .../src/utils/clusterLinks/formatters/openshift.test.ts | 2 +- .../src/utils/clusterLinks/formatters/rancher.test.ts | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts index 381df0dc28..847cbf83ef 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts @@ -15,7 +15,7 @@ */ import { aksFormatter } from './aks'; -describe('clusterLinks - aks formatter', () => { +describe('clusterLinks - AKS formatter', () => { it('should return an url on the workloads when there is a namespace only', () => { expect(() => aksFormatter({ diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts index a1a5252bc4..808901e60e 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts @@ -15,7 +15,7 @@ */ import { eksFormatter } from './eks'; -describe('clusterLinks - aks formatter', () => { +describe('clusterLinks - EKS formatter', () => { it('should return an url on the workloads when there is a namespace only', () => { expect(() => eksFormatter({ diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts index 56e8b510d3..6a842e0c24 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts @@ -15,7 +15,7 @@ */ import { gkeFormatter } from './gke'; -describe('clusterLinks - aks formatter', () => { +describe('clusterLinks - GKE formatter', () => { it('should return an url on the workloads when there is a namespace only', () => { expect(() => gkeFormatter({ diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts index f0530f1f7b..a2dea97555 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/index.ts @@ -17,10 +17,16 @@ import { ClusterLinksFormatter } from '../../../types/types'; import { standardFormatter } from './standard'; import { rancherFormatter } from './rancher'; import { openshiftFormatter } from './openshift'; +import { aksFormatter } from './aks'; +import { eksFormatter } from './eks'; +import { gkeFormatter } from './gke'; export const clusterLinksFormatters: Record = { standard: standardFormatter, rancher: rancherFormatter, openshift: openshiftFormatter, + aks: aksFormatter, + eks: eksFormatter, + gke: gkeFormatter, }; export const defaultFormatterName = 'standard'; diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts index f9a70d8d9a..67ef5e6c51 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts @@ -15,7 +15,7 @@ */ import { openshiftFormatter } from './openshift'; -describe('clusterLinks - aks formatter', () => { +describe('clusterLinks - OpenShift formatter', () => { it('should return an url on the workloads when there is a namespace only', () => { expect(() => openshiftFormatter({ diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts index 61de1a0a4e..503470acd3 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts @@ -15,7 +15,7 @@ */ import { rancherFormatter } from './rancher'; -describe('clusterLinks - aks formatter', () => { +describe('clusterLinks - Rancher formatter', () => { it('should return an url on the workloads when there is a namespace only', () => { expect(() => rancherFormatter({ From a65206d5a4d90f3ca34385862261bd48d28e616e Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Sun, 5 Sep 2021 20:02:07 -0400 Subject: [PATCH 09/10] minor changes after code review Signed-off-by: Morgan Martinet --- docs/features/kubernetes/configuration.md | 6 +-- plugins/kubernetes-backend/src/types/types.ts | 4 +- plugins/kubernetes-common/src/types.ts | 4 +- plugins/kubernetes/api-report.md | 10 ++-- .../KubernetesDrawer/KubernetesDrawer.tsx | 49 ++++++++++++++++++- .../utils/clusterLinks/formatClusterLink.ts | 6 ++- .../utils/clusterLinks/formatters/aks.test.ts | 2 +- .../src/utils/clusterLinks/formatters/aks.ts | 2 +- .../utils/clusterLinks/formatters/eks.test.ts | 2 +- .../src/utils/clusterLinks/formatters/eks.ts | 2 +- .../utils/clusterLinks/formatters/gke.test.ts | 2 +- .../src/utils/clusterLinks/formatters/gke.ts | 2 +- .../clusterLinks/formatters/openshift.test.ts | 4 +- .../clusterLinks/formatters/openshift.ts | 4 +- .../clusterLinks/formatters/rancher.test.ts | 4 +- .../utils/clusterLinks/formatters/rancher.ts | 4 +- .../utils/clusterLinks/formatters/standard.ts | 4 +- 17 files changed, 83 insertions(+), 28 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 4bbc28c178..f48faa202d 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -104,9 +104,9 @@ kubectl -n get secret $(kubectl -n get sa JSX.Element; +// Warning: (ae-forgotten-export) The symbol "FormatClusterLinkOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "formatClusterLink" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function formatClusterLink(options: { - dashboardUrl?: string; - dashboardApp?: string; - object: any; - kind: string; -}): string | undefined; +export function formatClusterLink( + options: FormatClusterLinkOptions, +): string | undefined; // Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvidersApi" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "KubernetesAuthProviders" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 4a51655484..78f8aa3d5c 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -36,9 +36,12 @@ import { CodeSnippet, StructuredMetadataTable, Link, + WarningPanel, } from '@backstage/core-components'; import { ClusterContext } from '../../hooks'; import { formatClusterLink } from '../../utils/clusterLinks'; +import { ClusterAttributes } from '@backstage/plugin-kubernetes-common'; +import { FormatClusterLinkOptions } from '../../utils/clusterLinks/formatClusterLink'; const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -57,6 +60,10 @@ const useDrawerContentStyles = makeStyles((_: Theme) => flexDirection: 'row', justifyContent: 'space-between', }, + errorMessage: { + marginTop: '1em', + marginBottom: '1em', + }, options: { display: 'flex', flexDirection: 'row', @@ -80,6 +87,27 @@ const PodDrawerButton = withStyles({ }, })(Button); +type ErrorPanelProps = { + cluster: ClusterAttributes; + errorMessage?: string; + children?: React.ReactNode; +}; + +export const ErrorPanel = ({ cluster, errorMessage }: ErrorPanelProps) => ( + + {errorMessage && ( + Errors: {errorMessage} + )} + +); + interface KubernetesDrawerable { metadata?: V1ObjectMeta; } @@ -100,6 +128,20 @@ function replaceNullsWithUndefined(someObj: any) { return JSON.parse(JSON.stringify(someObj, replacer)); } +function tryFormatClusterLink(options: FormatClusterLinkOptions) { + try { + return { + clusterLink: formatClusterLink(options), + errorMessage: '', + }; + } catch (err) { + return { + clusterLink: '', + errorMessage: err.message || err.toString(), + }; + } +} + const KubernetesDrawerContent = ({ toggleDrawer, object, @@ -110,7 +152,7 @@ const KubernetesDrawerContent = ({ const classes = useDrawerContentStyles(); const cluster = useContext(ClusterContext); - const clusterLink = formatClusterLink({ + const { clusterLink, errorMessage } = tryFormatClusterLink({ dashboardUrl: cluster.dashboardUrl, dashboardApp: cluster.dashboardApp, object, @@ -146,6 +188,11 @@ const KubernetesDrawerContent = ({
+ {errorMessage && ( +
+ +
+ )}
{clusterLink && ( diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts index f04ec1daea..83e970248b 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatClusterLink.ts @@ -16,12 +16,14 @@ import { defaultFormatterName, clusterLinksFormatters } from './formatters'; -export function formatClusterLink(options: { +export type FormatClusterLinkOptions = { dashboardUrl?: string; dashboardApp?: string; object: any; kind: string; -}) { +}; + +export function formatClusterLink(options: FormatClusterLinkOptions) { if (!options.dashboardUrl) { return undefined; } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts index 847cbf83ef..320c06e54c 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts @@ -28,6 +28,6 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('AKS formatter is not yet implemented'); + ).toThrowError('AKS formatter is not yet implemented. Please, contribute!'); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts index ef221b604d..d6f39ab72c 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts @@ -16,5 +16,5 @@ import { ClusterLinksFormatterOptions } from '../../../types/types'; export function aksFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('AKS formatter is not yet implemented'); + throw new Error('AKS formatter is not yet implemented. Please, contribute!'); } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts index 808901e60e..5998bfde5e 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.test.ts @@ -28,6 +28,6 @@ describe('clusterLinks - EKS formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('EKS formatter is not yet implemented'); + ).toThrowError('EKS formatter is not yet implemented. Please, contribute!'); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts index a9fb2f265b..975c39797c 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/eks.ts @@ -16,5 +16,5 @@ import { ClusterLinksFormatterOptions } from '../../../types/types'; export function eksFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('EKS formatter is not yet implemented'); + throw new Error('EKS formatter is not yet implemented. Please, contribute!'); } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts index 6a842e0c24..e566404daf 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.test.ts @@ -28,6 +28,6 @@ describe('clusterLinks - GKE formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('GKE formatter is not yet implemented'); + ).toThrowError('GKE formatter is not yet implemented. Please, contribute!'); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts index 858b872389..30e7fa3123 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/gke.ts @@ -16,5 +16,5 @@ import { ClusterLinksFormatterOptions } from '../../../types/types'; export function gkeFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('GKE formatter is not yet implemented'); + throw new Error('GKE formatter is not yet implemented. Please, contribute!'); } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts index 67ef5e6c51..df2529a8ac 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.test.ts @@ -28,6 +28,8 @@ describe('clusterLinks - OpenShift formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('OpenShift formatter is not yet implemented'); + ).toThrowError( + 'OpenShift formatter is not yet implemented. Please, contribute!', + ); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts index acab9ec1cb..bacb747ebb 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/openshift.ts @@ -18,5 +18,7 @@ import { ClusterLinksFormatterOptions } from '../../../types/types'; export function openshiftFormatter( _options: ClusterLinksFormatterOptions, ): URL { - throw new Error('OpenShift formatter is not yet implemented'); + throw new Error( + 'OpenShift formatter is not yet implemented. Please, contribute!', + ); } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts index 503470acd3..754647d5e9 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.test.ts @@ -28,6 +28,8 @@ describe('clusterLinks - Rancher formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('Rancher formatter is not yet implemented'); + ).toThrowError( + 'Rancher formatter is not yet implemented. Please, contribute!', + ); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts index 962f494408..491ca032a9 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/rancher.ts @@ -16,5 +16,7 @@ import { ClusterLinksFormatterOptions } from '../../../types/types'; export function rancherFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('Rancher formatter is not yet implemented'); + throw new Error( + 'Rancher formatter is not yet implemented. Please, contribute!', + ); } diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts index 06a8f9e0a8..fb26cf220d 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/standard.ts @@ -15,7 +15,7 @@ */ import { ClusterLinksFormatterOptions } from '../../../types/types'; -const KindMappings: Record = { +const kindMappings: Record = { deployment: 'deployment', ingress: 'ingress', service: 'service', @@ -26,7 +26,7 @@ export function standardFormatter(options: ClusterLinksFormatterOptions) { const result = new URL(options.dashboardUrl.href); const name = options.object.metadata?.name; const namespace = options.object.metadata?.namespace; - const validKind = KindMappings[options.kind.toLocaleLowerCase()]; + const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; if (namespace) { result.searchParams.set('namespace', namespace); } From 8fcdfaf1afcbbd4e1872073ae2cc03201706d2bc Mon Sep 17 00:00:00 2001 From: Morgan Martinet Date: Sun, 5 Sep 2021 21:05:56 -0400 Subject: [PATCH 10/10] fix appearance of the dashboard link button Signed-off-by: Morgan Martinet --- .../src/components/KubernetesDrawer/KubernetesDrawer.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 78f8aa3d5c..b4ea31dc8d 100644 --- a/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -28,6 +28,7 @@ import { Grid, } from '@material-ui/core'; import Close from '@material-ui/icons/Close'; +import OpenInNewIcon from '@material-ui/icons/OpenInNew'; import { V1ObjectMeta } from '@kubernetes/client-node'; import { withStyles } from '@material-ui/core/styles'; import jsYaml from 'js-yaml'; @@ -35,7 +36,6 @@ import { Button as BackstageButton, CodeSnippet, StructuredMetadataTable, - Link, WarningPanel, } from '@backstage/core-components'; import { ClusterContext } from '../../hooks'; @@ -197,13 +197,13 @@ const KubernetesDrawerContent = ({
{clusterLink && ( } > - Open Kubernetes Dashboard... + Open Kubernetes Dashboard )}