chore: resolve conflicts

This commit is contained in:
Marvin9
2020-10-11 19:19:35 +05:30
660 changed files with 11848 additions and 11656 deletions
+21
View File
@@ -11,3 +11,24 @@ Your plugin has been added to the example app in this repository, meaning you'll
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
## Surfacing your Kubernetes components as part of an entity
### Adding the entity annotation
In order for Backstage to detect that an entity has Kubernetes components,
the following annotation should be added to the entity.
```yaml
annotations:
'backstage.io/kubernetes-id': dice-roller
```
### Labeling Kubernetes components
In order for Kubernetes components to show up in the service catalog
as a part of an entity, Kubernetes components must be labeled with the following label:
```yaml
'backstage.io/kubernetes-id': <ENTITY_NAME>
```
+9 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes",
"version": "0.1.1-alpha.23",
"version": "0.1.1-alpha.24",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,10 +20,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@backstage/catalog-model": "^0.1.1-alpha.24",
"@backstage/config": "^0.1.1-alpha.24",
"@backstage/core": "^0.1.1-alpha.24",
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.24",
"@backstage/theme": "^0.1.1-alpha.24",
"@kubernetes/client-node": "^0.12.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -34,9 +35,9 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@backstage/cli": "^0.1.1-alpha.24",
"@backstage/dev-utils": "^0.1.1-alpha.24",
"@backstage/test-utils": "^0.1.1-alpha.24",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+2 -6
View File
@@ -20,7 +20,7 @@ import { Route, Routes } from 'react-router-dom';
import { rootCatalogKubernetesRouteRef } from './plugin';
import { KubernetesContent } from './components/KubernetesContent';
import { WarningPanel } from '@backstage/core';
import { MissingAnnotationEmptyState } from '@backstage/core';
const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id';
@@ -29,11 +29,7 @@ export const Router = ({ entity }: { entity: Entity }) => {
entity.metadata.annotations?.[KUBERNETES_ANNOTATION];
if (!kubernetesAnnotationValue) {
return (
<WarningPanel title="Kubernetes plugin:">
<pre>{KUBERNETES_ANNOTATION}</pre> annotation is missing on the entity.
</WarningPanel>
);
return <MissingAnnotationEmptyState annotation={KUBERNETES_ANNOTATION} />;
}
return (
@@ -16,7 +16,10 @@
import { DiscoveryApi } from '@backstage/core';
import { KubernetesApi } from './types';
import { ObjectsByServiceIdResponse } from '@backstage/plugin-kubernetes-backend';
import {
AuthRequestBody,
ObjectsByServiceIdResponse,
} from '@backstage/plugin-kubernetes-backend';
export class KubernetesBackendClient implements KubernetesApi {
private readonly discoveryApi: DiscoveryApi;
@@ -25,9 +28,18 @@ export class KubernetesBackendClient implements KubernetesApi {
this.discoveryApi = options.discoveryApi;
}
private async getRequired(path: string): Promise<any> {
private async getRequired(
path: string,
requestBody: AuthRequestBody,
): Promise<any> {
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;
const response = await fetch(url);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const payload = await response.text();
@@ -40,7 +52,8 @@ export class KubernetesBackendClient implements KubernetesApi {
async getObjectsByServiceId(
serviceId: String,
requestBody: AuthRequestBody,
): Promise<ObjectsByServiceIdResponse> {
return await this.getRequired(`/services/${serviceId}`);
return await this.getRequired(`/services/${serviceId}`, requestBody);
}
}
+8 -2
View File
@@ -15,7 +15,10 @@
*/
import { createApiRef } from '@backstage/core';
import { ObjectsByServiceIdResponse } from '@backstage/plugin-kubernetes-backend';
import {
AuthRequestBody,
ObjectsByServiceIdResponse,
} from '@backstage/plugin-kubernetes-backend';
export const kubernetesApiRef = createApiRef<KubernetesApi>({
id: 'plugin.kubernetes.service',
@@ -24,5 +27,8 @@ export const kubernetesApiRef = createApiRef<KubernetesApi>({
});
export interface KubernetesApi {
getObjectsByServiceId(serviceId: String): Promise<ObjectsByServiceIdResponse>;
getObjectsByServiceId(
serviceId: String,
requestBody: AuthRequestBody,
): Promise<ObjectsByServiceIdResponse>;
}
@@ -76,7 +76,7 @@ describe('ErrorPanel', () => {
expect(getByText('Cluster: THIS_CLUSTER')).toBeInTheDocument();
expect(
getByText(
"Error fetching Kubernetes resource: 'some/resource', error: SYSTEM_ERROR",
"Error fetching Kubernetes resource: 'some/resource', error: SYSTEM_ERROR, status code: 500",
),
).toBeInTheDocument();
});
@@ -29,7 +29,7 @@ const clustersWithErrorsToErrorMessage = (
{c.errors.map((e, j) => {
return (
<Typography variant="body2" key={j}>
{`Error fetching Kubernetes resource: '${e.resourcePath}', error: ${e.errorType}`}
{`Error fetching Kubernetes resource: '${e.resourcePath}', error: ${e.errorType}, status code: ${e.statusCode}`}
</Typography>
);
})}
@@ -17,8 +17,10 @@
import { BackstageTheme } from '@backstage/theme';
import React, { ReactElement, useEffect, useState } from 'react';
import { Grid, TabProps, useTheme } from '@material-ui/core';
import { Config } from '@backstage/config';
import {
CardTab,
configApiRef,
Content,
Page,
Progress,
@@ -28,10 +30,12 @@ import {
import { Entity } from '@backstage/catalog-model';
import { kubernetesApiRef } from '../../api/types';
import {
AuthRequestBody,
ClusterObjects,
FetchResponse,
ObjectsByServiceIdResponse,
} from '@backstage/plugin-kubernetes-backend';
import { kubernetesAuthProvidersApiRef } from '../../kubernetes-auth-provider/types';
import { DeploymentTables } from '../DeploymentTables';
import { DeploymentTriple } from '../../types/types';
import {
@@ -106,16 +110,40 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const configApi = useApi(configApiRef);
const clusters: Config[] = configApi.getConfigArray('kubernetes.clusters');
const allAuthProviders: string[] = clusters.map(c =>
c.getString('authProvider'),
);
const authProviders: string[] = [...new Set(allAuthProviders)];
const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef);
useEffect(() => {
kubernetesApi
.getObjectsByServiceId(entity.metadata.name)
.then(result => {
setKubernetesObjects(result);
})
.catch(e => {
setError(e.message);
});
}, [entity.metadata.name, kubernetesApi]);
(async () => {
// For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider
let requestBody: AuthRequestBody = {};
for (const authProviderStr of authProviders) {
// Multiple asyncs done sequentially instead of all at once to prevent same requestBody from being modified simultaneously
requestBody = await kubernetesAuthProvidersApi.decorateRequestBodyForAuth(
authProviderStr,
requestBody,
);
}
// TODO: Add validation on contents/format of requestBody
kubernetesApi
.getObjectsByServiceId(entity.metadata.name, requestBody)
.then(result => {
setKubernetesObjects(result);
})
.catch(e => {
setError(e.message);
});
})();
/* eslint-disable react-hooks/exhaustive-deps */
}, [entity.metadata.name, kubernetesApi, kubernetesAuthProvidersApi]);
/* eslint-enable react-hooks/exhaustive-deps */
const clustersWithErrors =
kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? [];
@@ -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 { OAuthApi } from '@backstage/core';
import { KubernetesAuthProvider } from './types';
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider {
authProvider: OAuthApi;
constructor(authProvider: OAuthApi) {
this.authProvider = authProvider;
}
async decorateRequestBodyForAuth(
requestBody: AuthRequestBody,
): Promise<AuthRequestBody> {
const googleAuthToken: string = await this.authProvider.getAccessToken(
'https://www.googleapis.com/auth/cloud-platform',
);
if ('auth' in requestBody) {
requestBody.auth!.google = googleAuthToken;
} else {
requestBody.auth = { google: googleAuthToken };
}
return requestBody;
}
}
@@ -0,0 +1,57 @@
/*
* 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 { OAuthApi } from '@backstage/core';
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types';
import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider';
import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider';
export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
private readonly kubernetesAuthProviderMap: Map<
string,
KubernetesAuthProvider
>;
constructor(options: { googleAuthApi: OAuthApi }) {
this.kubernetesAuthProviderMap = new Map<string, KubernetesAuthProvider>();
this.kubernetesAuthProviderMap.set(
'google',
new GoogleKubernetesAuthProvider(options.googleAuthApi),
);
this.kubernetesAuthProviderMap.set(
'serviceAccount',
new ServiceAccountKubernetesAuthProvider(),
);
}
async decorateRequestBodyForAuth(
authProvider: string,
requestBody: AuthRequestBody,
): Promise<AuthRequestBody> {
const kubernetesAuthProvider:
| KubernetesAuthProvider
| undefined = this.kubernetesAuthProviderMap.get(authProvider);
if (kubernetesAuthProvider) {
return await kubernetesAuthProvider.decorateRequestBodyForAuth(
requestBody,
);
}
throw new Error(
`authProvider "${authProvider}" has no KubernetesAuthProvider defined for it`,
);
}
}
@@ -0,0 +1,28 @@
/*
* 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 { KubernetesAuthProvider } from './types';
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
export class ServiceAccountKubernetesAuthProvider
implements KubernetesAuthProvider {
async decorateRequestBodyForAuth(
requestBody: AuthRequestBody,
): Promise<AuthRequestBody> {
// No-op, with service account for auth, cluster config/details should already have serviceAccountToken
return requestBody;
}
}
@@ -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 { createApiRef } from '@backstage/core';
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
export interface KubernetesAuthProvider {
decorateRequestBodyForAuth(
requestBody: AuthRequestBody,
): Promise<AuthRequestBody>;
}
export const kubernetesAuthProvidersApiRef = createApiRef<
KubernetesAuthProvidersApi
>({
id: 'plugin.kubernetes-auth-providers.service',
description: 'Used by the Kubernetes plugin to fetch KubernetesAuthProviders',
});
export interface KubernetesAuthProvidersApi {
decorateRequestBodyForAuth(
authProvider: string,
requestBody: AuthRequestBody,
): Promise<AuthRequestBody>;
}
+10
View File
@@ -18,9 +18,12 @@ import {
createPlugin,
createRouteRef,
discoveryApiRef,
googleAuthApiRef,
} from '@backstage/core';
import { KubernetesBackendClient } from './api/KubernetesBackendClient';
import { kubernetesApiRef } from './api/types';
import { kubernetesAuthProvidersApiRef } from './kubernetes-auth-provider/types';
import { KubernetesAuthProviders } from './kubernetes-auth-provider/KubernetesAuthProviders';
export const rootCatalogKubernetesRouteRef = createRouteRef({
path: '*',
@@ -36,5 +39,12 @@ export const plugin = createPlugin({
factory: ({ discoveryApi }) =>
new KubernetesBackendClient({ discoveryApi }),
}),
createApiFactory({
api: kubernetesAuthProvidersApiRef,
deps: { googleAuthApi: googleAuthApiRef },
factory: ({ googleAuthApi }) => {
return new KubernetesAuthProviders({ googleAuthApi });
},
}),
],
});