Kubernetes: lookup cluster config from google api when using GKE (#4844)

* Restructure config; add GKE cluster locator

Signed-off-by: mclarke <mclarke@spotify.com>

* PR feedback

Signed-off-by: mclarke <mclarke@spotify.com>

* fix region typo

Signed-off-by: mclarke <mclarke@spotify.com>

* replace config api call with clusters endpoint

Signed-off-by: mclarke <mclarke@spotify.com>

* missed tsc error

Signed-off-by: mclarke <mclarke@spotify.com>

* doc tweak

Signed-off-by: mclarke <mclarke@spotify.com>
This commit is contained in:
Matthew Clarke
2021-03-08 10:27:09 +00:00
committed by GitHub
parent 8e15b19a30
commit 9581ff0b4f
20 changed files with 710 additions and 142 deletions
+10 -20
View File
@@ -13,33 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ClusterLocatorMethod } from '@backstage/plugin-kubernetes-backend';
export interface Config {
kubernetes?: {
/**
* @visibility frontend
*/
serviceLocatorMethod: 'multiTenant';
serviceLocatorMethod: {
/**
* @visibility frontend
*/
type: 'multiTenant';
};
/**
* @visibility frontend
*/
clusterLocatorMethods: 'config'[];
clusters: {
/**
* @visibility frontend
*/
url: string;
/**
* @visibility frontend
*/
name: string;
/**
* @visibility secret
*/
serviceAccountToken: string | undefined;
/**
* @visibility frontend
*/
authProvider: 'aws' | 'google' | 'serviceAccount';
}[];
clusterLocatorMethods: ClusterLocatorMethod[];
};
}
@@ -14,44 +14,26 @@
* limitations under the License.
*/
import { ConfigApi, DiscoveryApi, IdentityApi } from '@backstage/core';
import { DiscoveryApi, IdentityApi } from '@backstage/core';
import { KubernetesApi } from './types';
import {
KubernetesRequestBody,
ObjectsByEntityResponse,
} from '@backstage/plugin-kubernetes-backend';
import { Config } from '@backstage/config';
export class KubernetesBackendClient implements KubernetesApi {
private readonly discoveryApi: DiscoveryApi;
private readonly identityApi: IdentityApi;
private readonly configApi: ConfigApi;
constructor(options: {
discoveryApi: DiscoveryApi;
identityApi: IdentityApi;
configApi: ConfigApi;
}) {
this.discoveryApi = options.discoveryApi;
this.identityApi = options.identityApi;
this.configApi = options.configApi;
}
private async getRequired(
path: string,
requestBody: KubernetesRequestBody,
): Promise<any> {
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;
const idToken = await this.identityApi.getIdToken();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
body: JSON.stringify(requestBody),
});
private async handleResponse(response: Response): Promise<any> {
if (!response.ok) {
const payload = await response.text();
let message;
@@ -69,16 +51,40 @@ export class KubernetesBackendClient implements KubernetesApi {
return await response.json();
}
private async postRequired(
path: string,
requestBody: KubernetesRequestBody,
): Promise<any> {
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;
const idToken = await this.identityApi.getIdToken();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
body: JSON.stringify(requestBody),
});
return this.handleResponse(response);
}
async getObjectsByEntity(
requestBody: KubernetesRequestBody,
): Promise<ObjectsByEntityResponse> {
return await this.getRequired(
return await this.postRequired(
`/services/${requestBody.entity.metadata.name}`,
requestBody,
);
}
getClusters(): Config[] {
return this.configApi.getConfigArray('kubernetes.clusters');
async getClusters(): Promise<{ name: string; authProvider: string }[]> {
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`;
const response = await fetch(url, {
method: 'GET',
});
return (await this.handleResponse(response)).items;
}
}
+1 -2
View File
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import { createApiRef } from '@backstage/core';
import {
KubernetesRequestBody,
@@ -31,5 +30,5 @@ export interface KubernetesApi {
getObjectsByEntity(
requestBody: KubernetesRequestBody,
): Promise<ObjectsByEntityResponse>;
getClusters(): Config[];
getClusters(): Promise<{ name: string; authProvider: string }[]>;
}
@@ -23,7 +23,6 @@ import {
Grid,
Typography,
} from '@material-ui/core';
import { Config } from '@backstage/config';
import {
Content,
Page,
@@ -168,16 +167,14 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const clusters: Config[] = kubernetesApi.getClusters();
const allAuthProviders: string[] = clusters.map(c =>
c.getString('authProvider'),
);
const authProviders: string[] = [...new Set(allAuthProviders)];
const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef);
useEffect(() => {
(async () => {
const clusters = await kubernetesApi.getClusters();
const authProviders: string[] = [
...new Set(clusters.map(c => c.authProvider)),
];
// For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider
let requestBody: KubernetesRequestBody = {
entity,
+2 -4
View File
@@ -21,7 +21,6 @@ import {
identityApiRef,
googleAuthApiRef,
createRoutableExtension,
configApiRef,
} from '@backstage/core';
import { KubernetesBackendClient } from './api/KubernetesBackendClient';
import { kubernetesApiRef } from './api/types';
@@ -41,10 +40,9 @@ export const kubernetesPlugin = createPlugin({
deps: {
discoveryApi: discoveryApiRef,
identityApi: identityApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, identityApi, configApi }) =>
new KubernetesBackendClient({ discoveryApi, identityApi, configApi }),
factory: ({ discoveryApi, identityApi }) =>
new KubernetesBackendClient({ discoveryApi, identityApi }),
}),
createApiFactory({
api: kubernetesAuthProvidersApiRef,