refactor code in order to support multiple dashboard link formatters

Signed-off-by: Morgan Martinet <morgan.martinet@montreal.ca>
This commit is contained in:
Morgan Martinet
2021-08-29 19:07:34 -04:00
parent c59294d1e5
commit abf7c46404
29 changed files with 757 additions and 144 deletions
+36
View File
@@ -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 <NAMESPACE> get secret $(kubectl -n <NAMESPACE> get sa <SERVICE_ACCOU
| base64 --decode
```
##### `clusters.\*.dashboardUrl` (optional)
Specifies the link to the Kubernetes dashboard managing this cluster.
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.
##### `clusters.\*.dashboardApp` (optional)
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
Note that it will default to the regular dashboard provided by the Kubernetes
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.
Example:
```ts
import { clusterLinksFormatters } from '@backstage/plugin-kubernetes';
clusterLinksFormatters.myDashboard = (options) => ...;
```
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
+1 -2
View File
@@ -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;
@@ -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': {
@@ -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;
});
}),
@@ -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 {}
+1 -2
View File
@@ -21,9 +21,8 @@ export type AuthProviderType = 'google' | 'serviceAccount' | 'aws';
//
// @public (undocumented)
export interface ClusterAttributes {
// (undocumented)
dashboardApp?: string;
dashboardUrl?: string;
// (undocumented)
name: string;
}
+27
View File
@@ -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 {
+16
View File
@@ -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<string, ClusterLinksFormatter>;
// 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)
//
@@ -112,6 +112,7 @@ const KubernetesDrawerContent = <T extends KubernetesDrawerable>({
const cluster = useContext(ClusterContext);
const clusterLink = formatClusterLink({
dashboardUrl: cluster.dashboardUrl,
dashboardApp: cluster.dashboardApp,
object,
kind,
});
+1
View File
@@ -20,3 +20,4 @@ export {
} from './plugin';
export { Router } from './Router';
export * from './kubernetes-auth-provider';
export * from './utils/clusterLinks';
+10
View File
@@ -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;
@@ -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',
);
});
});
});
@@ -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',
);
});
});
});
});
@@ -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}`;
}
@@ -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');
});
});
@@ -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');
}
@@ -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');
});
});
@@ -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');
}
@@ -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');
});
});
@@ -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');
}
@@ -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<string, ClusterLinksFormatter> = {
standard: standardFormatter,
rancher: rancherFormatter,
openshift: openshiftFormatter,
};
export const defaultFormatterName = 'standard';
@@ -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');
});
});
@@ -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');
}
@@ -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');
});
});
@@ -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');
}
@@ -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',
);
});
});
@@ -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<string, string> = {
deployment: 'deployment',
@@ -21,30 +22,18 @@ const KindMappings: Record<string, string> = {
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;
}
@@ -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';
+16
View File
@@ -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';