Merge pull request #4080 from K-Phoen/kubernetes-services

Show kubernetes services
This commit is contained in:
Ben Lambert
2021-01-21 09:23:40 +01:00
committed by GitHub
14 changed files with 684 additions and 4 deletions
@@ -0,0 +1,38 @@
/*
* 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 * as ingresses from './__fixtures__/2-ingresses.json';
import { wrapInTestApp } from '@backstage/test-utils';
import { IngressDrawer } from './IngressDrawer';
describe('IngressDrawer', () => {
it('should render ingress drawer', async () => {
const { getByText, getAllByText } = render(
wrapInTestApp(
<IngressDrawer ingress={(ingresses as any).ingresses[0]} expanded />,
),
);
expect(getAllByText('awesome-service')).toHaveLength(2);
expect(getByText('YAML')).toBeInTheDocument();
expect(getByText('Rules')).toBeInTheDocument();
expect(getByText('Host: api.awesome-host.io')).toBeInTheDocument();
expect(getAllByText('Service Port: 80')).toHaveLength(2);
expect(getAllByText('Service Name: awesome-service')).toHaveLength(2);
});
});
@@ -0,0 +1,58 @@
/*
* 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 { ExtensionsV1beta1Ingress } from '@kubernetes/client-node';
import { KubernetesDrawer } from '../KubernetesDrawer/KubernetesDrawer';
import { Typography, Grid } from '@material-ui/core';
export const IngressDrawer = ({
ingress,
expanded,
}: {
ingress: ExtensionsV1beta1Ingress;
expanded?: boolean;
}) => {
return (
<KubernetesDrawer
object={ingress}
expanded={expanded}
kind="Ingress"
renderObject={(ingress: ExtensionsV1beta1Ingress) => {
return ingress.spec || {};
}}
>
<Grid
container
direction="column"
justify="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item>
<Typography variant="h5">
{ingress.metadata?.name ?? 'unknown object'}
</Typography>
</Grid>
<Grid item>
<Typography color="textSecondary" variant="body1">
Ingress
</Typography>
</Grid>
</Grid>
</KubernetesDrawer>
);
};
@@ -0,0 +1,34 @@
/*
* 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 * as oneIngressFixture from './__fixtures__/2-ingresses.json';
import { wrapInTestApp } from '@backstage/test-utils';
import { IngressesAccordions } from './IngressesAccordions';
describe('IngressesAccordions', () => {
it('should render 1 ingress', async () => {
const { getByText } = render(
wrapInTestApp(
<IngressesAccordions deploymentResources={oneIngressFixture as any} />,
),
);
expect(getByText('awesome-service')).toBeInTheDocument();
expect(getByText('Ingress')).toBeInTheDocument();
});
});
@@ -0,0 +1,101 @@
/*
* 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 { GroupedResponses } from '../../types/types';
import React from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Divider,
Grid,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { ExtensionsV1beta1Ingress } from '@kubernetes/client-node';
import { StructuredMetadataTable } from '@backstage/core';
import { IngressDrawer } from './IngressDrawer';
type IngressesAccordionsProps = {
deploymentResources: GroupedResponses;
};
export const IngressesAccordions = ({
deploymentResources,
}: IngressesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.ingresses.map((ingress, i) => (
<Grid item key={i} xs>
<IngressAccordion ingress={ingress} />
</Grid>
))}
</Grid>
);
};
type IngressAccordionProps = {
ingress: ExtensionsV1beta1Ingress;
};
const IngressAccordion = ({ ingress }: IngressAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<IngressSummary ingress={ingress} />
</AccordionSummary>
<AccordionDetails>
<IngressCard ingress={ingress} />
</AccordionDetails>
</Accordion>
);
};
type IngressSummaryProps = {
ingress: ExtensionsV1beta1Ingress;
};
const IngressSummary = ({ ingress }: IngressSummaryProps) => {
return (
<Grid container direction="row" justify="flex-start" alignItems="center">
<Grid xs={3} item>
<IngressDrawer ingress={ingress} />
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '5em' }} orientation="vertical" />
</Grid>
</Grid>
);
};
type IngressCardProps = {
ingress: ExtensionsV1beta1Ingress;
};
const IngressCard = ({ ingress }: IngressCardProps) => {
return (
<StructuredMetadataTable
metadata={{
...ingress.spec,
}}
/>
);
};
@@ -0,0 +1,57 @@
{
"ingresses": [
{
"metadata": {
"annotations": {
"artifact.spinnaker.io/location": "default",
"artifact.spinnaker.io/name": "awesome-service",
"artifact.spinnaker.io/type": "kubernetes/ingress",
"kubernetes.io/ingress.class": "traefik",
"kubernetes.io/ingress.global-static-ip-name": "traefik-tcp-lb",
"moniker.spinnaker.io/application": "awesome-service",
"moniker.spinnaker.io/cluster": "ingress awesome-service"
},
"creationTimestamp": "2018-11-16T14:00:13.000Z",
"generation": 11,
"labels": {
"app": "awesome-service",
"app.kubernetes.io/managed-by": "spinnaker",
"app.kubernetes.io/name": "awesome-service"
},
"name": "awesome-service",
"namespace": "default",
"resourceVersion": "564824116",
"selfLink": "/apis/networking.k8s.io/v1beta1/namespaces/default/ingresses/awesome-service",
"uid": "f072e0b4-e9a7-11e8-af65-42010a9c0022"
},
"spec": {
"rules": [
{
"host": "api.awesome-host.io",
"http": {
"paths": [
{
"backend": {
"serviceName": "awesome-service",
"servicePort": 80
},
"path": "/v1/awesome-service"
},
{
"backend": {
"serviceName": "awesome-service",
"servicePort": 80
},
"path": "/v1/awesome-services"
}
]
}
}
]
},
"status": {
"loadBalancer": {}
}
}
]
}
@@ -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 { IngressesAccordions } from './IngressesAccordions';
@@ -47,6 +47,8 @@ import { DeploymentsAccordions } from '../DeploymentsAccordions';
import { ErrorReporting } from '../ErrorReporting';
import { groupResponses } from '../../utils/response';
import { DetectedError, detectErrors } from '../../error-detection';
import { IngressesAccordions } from '../IngressesAccordions';
import { ServicesAccordions } from '../ServicesAccordions';
type KubernetesContentProps = { entity: Entity; children?: React.ReactNode };
@@ -187,10 +189,22 @@ const Cluster = ({ clusterObjects, detectedErrors }: ClusterProps) => {
/>
</AccordionSummary>
<AccordionDetails>
<DeploymentsAccordions
deploymentResources={groupedResponses}
clusterPodNamesWithErrors={podsWithErrors}
/>
<Grid container direction="column">
<Grid item>
<DeploymentsAccordions
deploymentResources={groupedResponses}
clusterPodNamesWithErrors={podsWithErrors}
/>
</Grid>
<Grid item>
<IngressesAccordions deploymentResources={groupedResponses} />
</Grid>
<Grid item>
<ServicesAccordions deploymentResources={groupedResponses} />
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</>
@@ -0,0 +1,39 @@
/*
* 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 * as services from './__fixtures__/2-services.json';
import { wrapInTestApp } from '@backstage/test-utils';
import { ServiceDrawer } from './ServiceDrawer';
describe('ServiceDrawer', () => {
it('should render deployment drawer', async () => {
const { getByText, getAllByText } = render(
wrapInTestApp(
<ServiceDrawer service={(services as any).services[0]} expanded />,
),
);
expect(getAllByText('awesome-service-grpc')).toHaveLength(2);
expect(getAllByText('Service')).toHaveLength(2);
expect(getByText('YAML')).toBeInTheDocument();
expect(getByText('Cluster IP')).toBeInTheDocument();
expect(getByText('Ports')).toBeInTheDocument();
expect(getByText('Target Port: 1997')).toBeInTheDocument();
expect(getByText('App: awesome-service')).toBeInTheDocument();
});
});
@@ -0,0 +1,58 @@
/*
* 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 { V1Service } from '@kubernetes/client-node';
import { KubernetesDrawer } from '../KubernetesDrawer/KubernetesDrawer';
import { Typography, Grid } from '@material-ui/core';
export const ServiceDrawer = ({
service,
expanded,
}: {
service: V1Service;
expanded?: boolean;
}) => {
return (
<KubernetesDrawer
object={service}
expanded={expanded}
kind="Service"
renderObject={(service: V1Service) => {
return service.spec || {};
}}
>
<Grid
container
direction="column"
justify="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item>
<Typography variant="h5">
{service.metadata?.name ?? 'unknown object'}
</Typography>
</Grid>
<Grid item>
<Typography color="textSecondary" variant="body1">
Service
</Typography>
</Grid>
</Grid>
</KubernetesDrawer>
);
};
@@ -0,0 +1,37 @@
/*
* 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 * as twoDeployFixture from './__fixtures__/2-services.json';
import { wrapInTestApp } from '@backstage/test-utils';
import { ServicesAccordions } from './ServicesAccordions';
describe('ServicesAccordions', () => {
it('should render 2 services', async () => {
const { getByText } = render(
wrapInTestApp(
<ServicesAccordions deploymentResources={twoDeployFixture as any} />,
),
);
expect(getByText('awesome-service-grpc')).toBeInTheDocument();
expect(getByText('Type: ClusterIP')).toBeInTheDocument();
expect(getByText('awesome-service-pg')).toBeInTheDocument();
expect(getByText('Type: ExternalName')).toBeInTheDocument();
});
});
@@ -0,0 +1,123 @@
/*
* 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 { GroupedResponses } from '../../types/types';
import React from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Divider,
Grid,
Typography,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Service } from '@kubernetes/client-node';
import { StructuredMetadataTable } from '@backstage/core';
import { ServiceDrawer } from './ServiceDrawer';
type ServicesAccordionsProps = {
deploymentResources: GroupedResponses;
};
export const ServicesAccordions = ({
deploymentResources,
}: ServicesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.services.map((service, i) => (
<Grid item key={i} xs>
<ServiceAccordion service={service} />
</Grid>
))}
</Grid>
);
};
type ServiceAccordionProps = {
service: V1Service;
};
const ServiceAccordion = ({ service }: ServiceAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ServiceSummary service={service} />
</AccordionSummary>
<AccordionDetails>
<ServiceCard service={service} />
</AccordionDetails>
</Accordion>
);
};
type ServiceSummaryProps = {
service: V1Service;
};
const ServiceSummary = ({ service }: ServiceSummaryProps) => {
return (
<Grid container direction="row" justify="flex-start" alignItems="center">
<Grid xs={3} item>
<ServiceDrawer service={service} />
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '5em' }} orientation="vertical" />
</Grid>
<Grid item>
<Typography variant="subtitle2">
Type: {service.spec?.type ?? '?'}
</Typography>
</Grid>
</Grid>
);
};
type ServiceCardProps = {
service: V1Service;
};
const ServiceCard = ({ service }: ServiceCardProps) => {
const metadata: any = {};
if (service.status?.loadBalancer?.ingress?.length ?? -1 > 0) {
metadata.loadbalancer = service.status?.loadBalancer;
}
if (service.spec?.type === 'ClusterIP') {
metadata.clusterIP = service.spec.clusterIP;
}
if (service.spec?.type === 'ExternalName') {
metadata.externalName = service.spec.externalName;
}
return (
<StructuredMetadataTable
metadata={{
type: service.spec?.type,
ports: service.spec?.ports,
...metadata,
}}
/>
);
};
@@ -0,0 +1,83 @@
{
"services": [
{
"metadata": {
"annotations": {
"artifact.spinnaker.io/location": "default",
"artifact.spinnaker.io/name": "awesome-service-grpc",
"artifact.spinnaker.io/type": "kubernetes/service",
"moniker.spinnaker.io/application": "awesome-service",
"moniker.spinnaker.io/cluster": "service awesome-service-grpc"
},
"creationTimestamp": "2021-01-04T16:35:04.000Z",
"labels": {
"app": "awesome-service",
"app.kubernetes.io/managed-by": "spinnaker",
"app.kubernetes.io/name": "awesome-service"
},
"name": "awesome-service-grpc",
"namespace": "default",
"resourceVersion": "548901649",
"selfLink": "/api/v1/namespaces/default/services/awesome-service-grpc",
"uid": "461cdcd7-8c61-4125-91f9-e03d745f2f2c"
},
"spec": {
"clusterIP": "None",
"ports": [
{
"name": "grpc",
"port": 1997,
"protocol": "TCP",
"targetPort": 1997
}
],
"selector": {
"app": "awesome-service"
},
"sessionAffinity": "None",
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
},
{
"metadata": {
"annotations": {
"artifact.spinnaker.io/location": "default",
"artifact.spinnaker.io/name": "awesome-service-pg",
"artifact.spinnaker.io/type": "kubernetes/service",
"moniker.spinnaker.io/application": "awesome-service",
"moniker.spinnaker.io/cluster": "service awesome-service-pg"
},
"creationTimestamp": "2021-01-04T16:35:02.000Z",
"labels": {
"app": "awesome-service",
"app.kubernetes.io/managed-by": "spinnaker",
"app.kubernetes.io/name": "awesome-service"
},
"name": "awesome-service-pg",
"namespace": "default",
"resourceVersion": "548901625",
"selfLink": "/api/v1/namespaces/default/services/awesome-service-pg",
"uid": "7d7ff8f2-6caa-4888-ae55-b6d41833ab92"
},
"spec": {
"externalName": "10.244.0.5",
"ports": [
{
"name": "pg",
"port": 5432,
"protocol": "TCP",
"targetPort": 5432
}
],
"sessionAffinity": "None",
"type": "ExternalName"
},
"status": {
"loadBalancer": {}
}
}
]
}
@@ -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 { ServicesAccordions } from './ServicesAccordions';