From 2fbca83156bf5fa509a347cd5804e41d177b329a Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 28 Sep 2020 14:16:41 +0100 Subject: [PATCH] Add initial kubernetes services and configmap UI support (#2635) * add initial kubernetes services and configmap support * revert accidental change * missed revert * prettier --- .../dice-roller/dice-roller-manifests.yaml | 20 +++ .../getKubernetesObjectsByServiceIdHandler.ts | 1 - .../components/ConfigMaps/ConfigMaps.test.tsx | 41 +++++ .../src/components/ConfigMaps/ConfigMaps.tsx | 51 ++++++ .../ConfigMaps/__fixtures__/configmap.json | 46 +++++ .../src/components/ConfigMaps/index.ts | 16 ++ .../KubernetesContent/KubernetesContent.tsx | 74 +++++++- .../src/components/Services/Services.test.tsx | 54 ++++++ .../src/components/Services/Services.tsx | 62 +++++++ .../Services/__fixtures__/services.json | 164 ++++++++++++++++++ .../src/components/Services/index.ts | 16 ++ 11 files changed, 536 insertions(+), 9 deletions(-) create mode 100644 plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.test.tsx create mode 100644 plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.tsx create mode 100644 plugins/kubernetes/src/components/ConfigMaps/__fixtures__/configmap.json create mode 100644 plugins/kubernetes/src/components/ConfigMaps/index.ts create mode 100644 plugins/kubernetes/src/components/Services/Services.test.tsx create mode 100644 plugins/kubernetes/src/components/Services/Services.tsx create mode 100644 plugins/kubernetes/src/components/Services/__fixtures__/services.json create mode 100644 plugins/kubernetes/src/components/Services/index.ts diff --git a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml index f3a478f4d3..13fe26d051 100644 --- a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml +++ b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml @@ -88,6 +88,26 @@ spec: - protocol: TCP port: 80 targetPort: 9376 + name: port1 + - protocol: TCP + port: 81 + targetPort: 9377 + name: port2 + +--- +apiVersion: v1 +kind: Service +metadata: + name: dice-roller-lb + labels: + 'backstage.io/kubernetes-id': dice-roller +spec: + selector: + app: dice-roller + ports: + - port: 8765 + targetPort: 9376 + type: LoadBalancer --- apiVersion: v1 diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts index 52968d4c20..b2dde1a393 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts @@ -34,7 +34,6 @@ const DEFAULT_OBJECTS = new Set([ 'pods', 'services', 'configmaps', - 'secrets', 'deployments', 'replicasets', ]); diff --git a/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.test.tsx b/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.test.tsx new file mode 100644 index 0000000000..161a0ed487 --- /dev/null +++ b/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { render } from '@testing-library/react'; +import { ConfigMaps } from './ConfigMaps'; +import * as configmapFixture from './__fixtures__/configmap.json'; +import { wrapInTestApp } from '@backstage/test-utils'; + +describe('ConfigMaps', () => { + it('should render configmap', async () => { + const { getByText } = render( + wrapInTestApp( + , + ), + ); + + // title + expect(getByText('dice-roller')).toBeInTheDocument(); + expect(getByText('ConfigMap')).toBeInTheDocument(); + + // values + expect(getByText('Immutable')).toBeInTheDocument(); + expect(getByText('false')).toBeInTheDocument(); + expect(getByText('Data')).toBeInTheDocument(); + expect(getByText('Foo: bar')).toBeInTheDocument(); // TODO wish this wasn't upper case + }); +}); diff --git a/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.tsx b/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.tsx new file mode 100644 index 0000000000..f757d1c0fc --- /dev/null +++ b/plugins/kubernetes/src/components/ConfigMaps/ConfigMaps.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { Grid } from '@material-ui/core'; +import { V1ConfigMap } from '@kubernetes/client-node'; +import { InfoCard, StructuredMetadataTable } from '@backstage/core'; + +type ConfigMapsProps = { + configMaps: V1ConfigMap[]; + children?: React.ReactNode; +}; + +export const ConfigMaps = ({ configMaps }: ConfigMapsProps) => { + return ( + + {configMaps.map((cm, i) => { + return ( + + +
+ +
+
+
+ ); + })} +
+ ); +}; diff --git a/plugins/kubernetes/src/components/ConfigMaps/__fixtures__/configmap.json b/plugins/kubernetes/src/components/ConfigMaps/__fixtures__/configmap.json new file mode 100644 index 0000000000..77ea844611 --- /dev/null +++ b/plugins/kubernetes/src/components/ConfigMaps/__fixtures__/configmap.json @@ -0,0 +1,46 @@ +[ + { + "data": { + "foo": "bar" + }, + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"foo\":\"bar\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"}}\n" + }, + "creationTimestamp": "2020-09-24T11:39:26.000Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:data": { + ".": {}, + "f:foo": {} + }, + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:backstage.io/kubernetes-id": {} + } + } + }, + "manager": "kubectl", + "operation": "Update", + "time": "2020-09-24T11:39:26.000Z" + } + ], + "name": "dice-roller", + "namespace": "default", + "resourceVersion": "503867", + "selfLink": "/api/v1/namespaces/default/configmaps/dice-roller", + "uid": "e9efe5ee-53b9-4422-aef2-877a03c73d5f" + } + } +] diff --git a/plugins/kubernetes/src/components/ConfigMaps/index.ts b/plugins/kubernetes/src/components/ConfigMaps/index.ts new file mode 100644 index 0000000000..b1c7d37a5a --- /dev/null +++ b/plugins/kubernetes/src/components/ConfigMaps/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { ConfigMaps } from './ConfigMaps'; diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index d14a2dc4f7..315a5e0377 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -17,23 +17,33 @@ import React, { useEffect, useState } from 'react'; import { Grid } from '@material-ui/core'; import { + CardTab, Content, - InfoCard, Page, pageTheme, Progress, + TabbedCard, useApi, } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { kubernetesApiRef } from '../../api/types'; import { + ClusterObjects, FetchResponse, ObjectsByServiceIdResponse, } from '@backstage/plugin-kubernetes-backend'; import { DeploymentTables } from '../DeploymentTables'; import { DeploymentTriple } from '../../types/types'; +import { V1ConfigMap, V1Service } from '@kubernetes/client-node'; +import { Services } from '../Services'; +import { ConfigMaps } from '../ConfigMaps'; -const findDeployments = (fetchResponse: FetchResponse[]): DeploymentTriple => { +interface GroupedResponses extends DeploymentTriple { + services: V1Service[]; + configMaps: V1ConfigMap[]; +} + +const groupResponses = (fetchResponse: FetchResponse[]) => { return fetchResponse.reduce( (prev, next) => { switch (next.type) { @@ -46,6 +56,12 @@ const findDeployments = (fetchResponse: FetchResponse[]): DeploymentTriple => { case 'replicasets': prev.replicaSets.push(...next.resources); break; + case 'services': + prev.services.push(...next.resources); + break; + case 'configmaps': + prev.configMaps.push(...next.resources); + break; default: } return prev; @@ -54,7 +70,9 @@ const findDeployments = (fetchResponse: FetchResponse[]): DeploymentTriple => { pods: [], replicaSets: [], deployments: [], - } as DeploymentTriple, + services: [], + configMaps: [], + } as GroupedResponses, ); }; @@ -89,11 +107,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { {error !== undefined &&
{error}
} {kubernetesObjects?.items.map((item, i) => ( - - - + ))} @@ -101,3 +115,47 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { ); }; + +type ClusterProps = { + clusterObjects: ClusterObjects; + children?: React.ReactNode; +}; + +const Cluster = ({ clusterObjects }: ClusterProps) => { + const [selectedTab, setSelectedTab] = useState('one'); + + const handleChange = (_ev: any, newSelectedTab: string | number) => + setSelectedTab(newSelectedTab); + + const groupedResponses = groupResponses(clusterObjects.resources); + + const configMaps = groupedResponses.configMaps; + + return ( + <> + + + + + + + + {configMaps && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes/src/components/Services/Services.test.tsx b/plugins/kubernetes/src/components/Services/Services.test.tsx new file mode 100644 index 0000000000..5e74499bc5 --- /dev/null +++ b/plugins/kubernetes/src/components/Services/Services.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { render } from '@testing-library/react'; +import { Services } from './Services'; +import * as servicesFixture from './__fixtures__/services.json'; +import { wrapInTestApp } from '@backstage/test-utils'; + +describe('Services', () => { + it('should render 2 services', async () => { + const { getByText, getAllByText } = render( + wrapInTestApp(), + ); + + // common elements + expect(getAllByText('Service')).toHaveLength(2); + expect(getAllByText('Ports')).toHaveLength(2); + expect(getAllByText('Type')).toHaveLength(2); + expect(getAllByText('Protocol: TCP')).toHaveLength(3); + + // service 1 + expect(getByText('dice-roller')).toBeInTheDocument(); + expect(getByText('ClusterIP')).toBeInTheDocument(); + + expect(getByText('Name: port1')).toBeInTheDocument(); + expect(getByText('Port: 80')).toBeInTheDocument(); + expect(getByText('Target Port: 9376')).toBeInTheDocument(); + expect(getByText('Name: port1')).toBeInTheDocument(); + expect(getByText('Port: 81')).toBeInTheDocument(); + expect(getByText('Target Port: 9377')).toBeInTheDocument(); + expect(getByText('10.102.223.105')).toBeInTheDocument(); + + // service 2 + expect(getByText('dice-roller-lb')).toBeInTheDocument(); + expect(getByText('LoadBalancer')).toBeInTheDocument(); + expect(getByText('Node Port: 32276')).toBeInTheDocument(); + expect(getByText('Port: 8765')).toBeInTheDocument(); + expect(getByText('Target Port: 9378')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes/src/components/Services/Services.tsx b/plugins/kubernetes/src/components/Services/Services.tsx new file mode 100644 index 0000000000..d0a4f71eae --- /dev/null +++ b/plugins/kubernetes/src/components/Services/Services.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { Grid } from '@material-ui/core'; +import { V1Service } from '@kubernetes/client-node'; +import { InfoCard, StructuredMetadataTable } from '@backstage/core'; + +type ServicesProps = { + services: V1Service[]; + children?: React.ReactNode; +}; + +export const Services = ({ services }: ServicesProps) => { + return ( + + {services.map((s, i) => { + const metadata: any = {}; + + if (s.status?.loadBalancer?.ingress?.length ?? -1 > 0) { + metadata.loadbalancer = s.status?.loadBalancer; + } + + if (s.spec?.type === 'ClusterIP') { + metadata.clusterIP = s.spec.clusterIP; + } + + return ( + + +
+ +
+
+
+ ); + })} +
+ ); +}; diff --git a/plugins/kubernetes/src/components/Services/__fixtures__/services.json b/plugins/kubernetes/src/components/Services/__fixtures__/services.json new file mode 100644 index 0000000000..7f9b49a9f6 --- /dev/null +++ b/plugins/kubernetes/src/components/Services/__fixtures__/services.json @@ -0,0 +1,164 @@ +[ + { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"name\":\"port1\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":9376},{\"name\":\"port2\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":9377}],\"selector\":{\"app\":\"dice-roller\"}}}\n" + }, + "creationTimestamp": "2020-09-23T12:00:55.000Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:backstage.io/kubernetes-id": {} + } + }, + "f:spec": { + "f:ports": { + ".": {}, + "k:{\"port\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:name": {}, + "f:port": {}, + "f:protocol": {}, + "f:targetPort": {} + }, + "k:{\"port\":81,\"protocol\":\"TCP\"}": { + ".": {}, + "f:name": {}, + "f:port": {}, + "f:protocol": {}, + "f:targetPort": {} + } + }, + "f:selector": { + ".": {}, + "f:app": {} + }, + "f:sessionAffinity": {}, + "f:type": {} + } + }, + "manager": "kubectl", + "operation": "Update", + "time": "2020-09-28T08:50:11.000Z" + } + ], + "name": "dice-roller", + "namespace": "default", + "resourceVersion": "665838", + "selfLink": "/api/v1/namespaces/default/services/dice-roller", + "uid": "ae9aff92-a525-4bc9-82dc-a0537bf8034c" + }, + "spec": { + "clusterIP": "10.102.223.105", + "ports": [ + { + "name": "port1", + "port": 80, + "protocol": "TCP", + "targetPort": 9376 + }, + { + "name": "port2", + "port": 81, + "protocol": "TCP", + "targetPort": 9377 + } + ], + "selector": { + "app": "dice-roller" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } + }, + { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller-lb\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"port\":8765,\"targetPort\":9376}],\"selector\":{\"app\":\"dice-roller\"},\"type\":\"LoadBalancer\"}}\n" + }, + "creationTimestamp": "2020-09-28T08:51:21.000Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:backstage.io/kubernetes-id": {} + } + }, + "f:spec": { + "f:externalTrafficPolicy": {}, + "f:ports": { + ".": {}, + "k:{\"port\":8765,\"protocol\":\"TCP\"}": { + ".": {}, + "f:port": {}, + "f:protocol": {}, + "f:targetPort": {} + } + }, + "f:selector": { + ".": {}, + "f:app": {} + }, + "f:sessionAffinity": {}, + "f:type": {} + } + }, + "manager": "kubectl", + "operation": "Update", + "time": "2020-09-28T08:51:21.000Z" + } + ], + "name": "dice-roller-lb", + "namespace": "default", + "resourceVersion": "665998", + "selfLink": "/api/v1/namespaces/default/services/dice-roller-lb", + "uid": "5554da3b-2041-4403-8cf4-cd2ccae760f8" + }, + "spec": { + "clusterIP": "10.99.205.233", + "externalTrafficPolicy": "Cluster", + "ports": [ + { + "nodePort": 32276, + "port": 8765, + "protocol": "TCP", + "targetPort": 9378 + } + ], + "selector": { + "app": "dice-roller" + }, + "sessionAffinity": "None", + "type": "LoadBalancer" + }, + "status": { + "loadBalancer": {} + } + } +] diff --git a/plugins/kubernetes/src/components/Services/index.ts b/plugins/kubernetes/src/components/Services/index.ts new file mode 100644 index 0000000000..d52ebf5f14 --- /dev/null +++ b/plugins/kubernetes/src/components/Services/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Services } from './Services';