modify approach to post entire entity to k8s backend
This commit is contained in:
committed by
moustafab
parent
99fffed216
commit
4639d20720
+2
-2
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthRequestBody, ClusterDetails } from '../types/types';
|
||||
import { KubernetesRequestBody, ClusterDetails } from '../types/types';
|
||||
|
||||
export class GoogleKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
requestBody: AuthRequestBody,
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<ClusterDetails> {
|
||||
const clusterDetailsWithAuthToken: ClusterDetails = Object.assign(
|
||||
{},
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthRequestBody, ClusterDetails } from '../types/types';
|
||||
import { KubernetesRequestBody, ClusterDetails } from '../types/types';
|
||||
|
||||
export class ServiceAccountKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator {
|
||||
@@ -23,7 +23,7 @@ export class ServiceAccountKubernetesAuthTranslator
|
||||
clusterDetails: ClusterDetails,
|
||||
// To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read.
|
||||
// @ts-ignore-start
|
||||
requestBody: AuthRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
// @ts-ignore-end
|
||||
): Promise<ClusterDetails> {
|
||||
return clusterDetails;
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthRequestBody, ClusterDetails } from '../types/types';
|
||||
import { KubernetesRequestBody, ClusterDetails } from '../types/types';
|
||||
|
||||
export interface KubernetesAuthTranslator {
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
requestBody: AuthRequestBody,
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<ClusterDetails>;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,6 @@ describe('handleGetKubernetesObjectsForService', () => {
|
||||
},
|
||||
getVoidLogger(),
|
||||
{},
|
||||
'',
|
||||
);
|
||||
|
||||
expect(getClustersByServiceId.mock.calls.length).toBe(1);
|
||||
|
||||
@@ -16,25 +16,25 @@
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
AuthRequestBody,
|
||||
KubernetesRequestBody,
|
||||
ClusterDetails,
|
||||
KubernetesServiceLocator,
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectTypes,
|
||||
ObjectsByServiceIdResponse,
|
||||
ObjectsByEntityResponse,
|
||||
} from '../types/types';
|
||||
import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';
|
||||
import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator';
|
||||
import { ComponentEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
|
||||
export type GetKubernetesObjectsForServiceHandler = (
|
||||
serviceId: string,
|
||||
fetcher: KubernetesFetcher,
|
||||
serviceLocator: KubernetesServiceLocator,
|
||||
logger: Logger,
|
||||
requestBody: AuthRequestBody,
|
||||
labelSelector: string,
|
||||
requestBody: KubernetesRequestBody,
|
||||
objectsToFetch?: Set<KubernetesObjectTypes>,
|
||||
) => Promise<ObjectsByServiceIdResponse>;
|
||||
) => Promise<ObjectsByEntityResponse>;
|
||||
|
||||
const DEFAULT_OBJECTS = new Set<KubernetesObjectTypes>([
|
||||
'pods',
|
||||
@@ -46,6 +46,26 @@ const DEFAULT_OBJECTS = new Set<KubernetesObjectTypes>([
|
||||
'ingresses',
|
||||
]);
|
||||
|
||||
function parseLabelSelector(entity: ComponentEntityV1alpha1): string {
|
||||
if (
|
||||
entity &&
|
||||
entity.spec &&
|
||||
entity.spec.kubernetes &&
|
||||
entity.spec.kubernetes.selector
|
||||
) {
|
||||
// TODO: figure out how to convert the selector to the full query param from the yaml
|
||||
// (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go)
|
||||
const { matchLabels } = entity.spec.kubernetes.selector;
|
||||
if (!matchLabels) {
|
||||
return '';
|
||||
}
|
||||
return Object.keys(matchLabels)
|
||||
.map(key => `${key}=${matchLabels[key.toString()]}`)
|
||||
.join(',');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Fans out the request to all clusters that the service lives in, aggregates their responses together
|
||||
export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async (
|
||||
serviceId,
|
||||
@@ -53,7 +73,6 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic
|
||||
serviceLocator,
|
||||
logger,
|
||||
requestBody,
|
||||
labelSelector: string,
|
||||
objectsToFetch = DEFAULT_OBJECTS,
|
||||
) => {
|
||||
const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId(
|
||||
@@ -80,6 +99,8 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic
|
||||
.join(', ')}]`,
|
||||
);
|
||||
|
||||
const labelSelector = parseLabelSelector(requestBody.entity);
|
||||
|
||||
return Promise.all(
|
||||
clusterDetailsDecoratedForAuth.map(cd => {
|
||||
return fetcher
|
||||
|
||||
@@ -21,14 +21,14 @@ import { makeRouter } from './router';
|
||||
import {
|
||||
KubernetesServiceLocator,
|
||||
KubernetesFetcher,
|
||||
ObjectsByServiceIdResponse,
|
||||
ObjectsByEntityResponse,
|
||||
} from '..';
|
||||
|
||||
describe('router', () => {
|
||||
let app: express.Express;
|
||||
let kubernetesFetcher: jest.Mocked<KubernetesFetcher>;
|
||||
let kubernetesServiceLocator: jest.Mocked<KubernetesServiceLocator>;
|
||||
let handleGetByServiceId: jest.Mock<Promise<ObjectsByServiceIdResponse>>;
|
||||
let handleGetByServiceId: jest.Mock<Promise<ObjectsByEntityResponse>>;
|
||||
|
||||
beforeAll(async () => {
|
||||
kubernetesFetcher = {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
handleGetKubernetesObjectsForService,
|
||||
} from './getKubernetesObjectsForServiceHandler';
|
||||
import {
|
||||
AuthRequestBody,
|
||||
KubernetesRequestBody,
|
||||
KubernetesServiceLocator,
|
||||
KubernetesFetcher,
|
||||
ServiceLocatorMethod,
|
||||
@@ -64,23 +64,21 @@ export const makeRouter = (
|
||||
logger: Logger,
|
||||
fetcher: KubernetesFetcher,
|
||||
serviceLocator: KubernetesServiceLocator,
|
||||
handleGetByServiceId: GetKubernetesObjectsForServiceHandler,
|
||||
handleGetByEntity: GetKubernetesObjectsForServiceHandler,
|
||||
): express.Router => {
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.post('/services/:serviceId', async (req, res) => {
|
||||
const serviceId = req.params.serviceId;
|
||||
const labelSelector = req.query.labelSelector;
|
||||
const requestBody: AuthRequestBody = req.body;
|
||||
const requestBody: KubernetesRequestBody = req.body;
|
||||
try {
|
||||
const response = await handleGetByServiceId(
|
||||
const response = await handleGetByEntity(
|
||||
serviceId,
|
||||
fetcher,
|
||||
serviceLocator,
|
||||
logger,
|
||||
requestBody,
|
||||
labelSelector ? labelSelector.toString() : '',
|
||||
);
|
||||
res.send(response);
|
||||
} catch (e) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
V1ReplicaSet,
|
||||
V1Service,
|
||||
} from '@kubernetes/client-node';
|
||||
import { ComponentEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
|
||||
export interface ClusterDetails {
|
||||
name: string;
|
||||
@@ -31,10 +32,11 @@ export interface ClusterDetails {
|
||||
serviceAccountToken?: string | undefined;
|
||||
}
|
||||
|
||||
export interface AuthRequestBody {
|
||||
export interface KubernetesRequestBody {
|
||||
auth?: {
|
||||
google?: string;
|
||||
};
|
||||
entity: ComponentEntityV1alpha1;
|
||||
}
|
||||
|
||||
export interface ClusterObjects {
|
||||
@@ -43,7 +45,7 @@ export interface ClusterObjects {
|
||||
errors: KubernetesFetchError[];
|
||||
}
|
||||
|
||||
export interface ObjectsByServiceIdResponse {
|
||||
export interface ObjectsByEntityResponse {
|
||||
items: ClusterObjects[];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { KubernetesApi } from './types';
|
||||
import {
|
||||
AuthRequestBody,
|
||||
ObjectsByServiceIdResponse,
|
||||
KubernetesRequestBody,
|
||||
ObjectsByEntityResponse,
|
||||
} from '@backstage/plugin-kubernetes-backend';
|
||||
import { V1LabelSelector } from '@kubernetes/client-node';
|
||||
|
||||
export class KubernetesBackendClient implements KubernetesApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
@@ -31,7 +30,7 @@ export class KubernetesBackendClient implements KubernetesApi {
|
||||
|
||||
private async getRequired(
|
||||
path: string,
|
||||
requestBody: AuthRequestBody,
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<any> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;
|
||||
const response = await fetch(url, {
|
||||
@@ -51,26 +50,11 @@ export class KubernetesBackendClient implements KubernetesApi {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
private parseLabelSelector(params: V1LabelSelector): string {
|
||||
// TODO: figure out how to convert the selector to the full query param from the yaml
|
||||
// (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go)
|
||||
const { matchLabels } = params;
|
||||
if (!matchLabels) {
|
||||
return '';
|
||||
}
|
||||
return Object.keys(matchLabels)
|
||||
.map(key => `${key}=${matchLabels[key.toString()]}`)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
async getObjectsByLabelSelector(
|
||||
serviceId: String,
|
||||
labelSelector: V1LabelSelector,
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<ObjectsByServiceIdResponse> {
|
||||
const labelSelectorQueryParams = this.parseLabelSelector(labelSelector);
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<ObjectsByEntityResponse> {
|
||||
return await this.getRequired(
|
||||
`/services/${serviceId}?labelSelector=${labelSelectorQueryParams}`,
|
||||
`/services/${requestBody.entity.metadata.name}`,
|
||||
requestBody,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import {
|
||||
AuthRequestBody,
|
||||
ObjectsByServiceIdResponse,
|
||||
KubernetesRequestBody,
|
||||
ObjectsByEntityResponse,
|
||||
} from '@backstage/plugin-kubernetes-backend';
|
||||
import { V1LabelSelector } from '@kubernetes/client-node';
|
||||
|
||||
@@ -29,8 +29,6 @@ export const kubernetesApiRef = createApiRef<KubernetesApi>({
|
||||
|
||||
export interface KubernetesApi {
|
||||
getObjectsByLabelSelector(
|
||||
serviceId: String,
|
||||
labelSelector: V1LabelSelector,
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<ObjectsByServiceIdResponse>;
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<ObjectsByEntityResponse>;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ import {
|
||||
import { ComponentEntityV1alpha1, Entity } from '@backstage/catalog-model';
|
||||
import { kubernetesApiRef } from '../../api/types';
|
||||
import {
|
||||
AuthRequestBody,
|
||||
KubernetesRequestBody,
|
||||
ClusterObjects,
|
||||
FetchResponse,
|
||||
ObjectsByServiceIdResponse,
|
||||
ObjectsByEntityResponse,
|
||||
} from '@backstage/plugin-kubernetes-backend';
|
||||
import { kubernetesAuthProvidersApiRef } from '../../kubernetes-auth-provider/types';
|
||||
import { DeploymentTables } from '../DeploymentTables';
|
||||
@@ -105,7 +105,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
|
||||
const kubernetesApi = useApi(kubernetesApiRef);
|
||||
|
||||
const [kubernetesObjects, setKubernetesObjects] = useState<
|
||||
ObjectsByServiceIdResponse | undefined
|
||||
ObjectsByEntityResponse | undefined
|
||||
>(undefined);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
@@ -121,7 +121,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
// For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider
|
||||
let requestBody: AuthRequestBody = {};
|
||||
let requestBody: KubernetesRequestBody = {
|
||||
entity: entity as ComponentEntityV1alpha1,
|
||||
};
|
||||
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(
|
||||
@@ -130,29 +132,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
// decide label selector to search by defaulting to this label
|
||||
let labelSelector: V1LabelSelector = {
|
||||
matchLabels: {
|
||||
'backstage.io/kubernetes-id': entity.metadata.name,
|
||||
},
|
||||
};
|
||||
|
||||
const componentEntity = entity as ComponentEntityV1alpha1;
|
||||
if (
|
||||
componentEntity.spec &&
|
||||
componentEntity.spec.kubernetes &&
|
||||
(componentEntity.spec.kubernetes.selector as V1LabelSelector)
|
||||
) {
|
||||
labelSelector = componentEntity.spec.kubernetes.selector;
|
||||
}
|
||||
|
||||
// TODO: Add validation on contents/format of requestBody
|
||||
kubernetesApi
|
||||
.getObjectsByLabelSelector(
|
||||
entity.metadata.name,
|
||||
labelSelector,
|
||||
requestBody,
|
||||
)
|
||||
.getObjectsByLabelSelector(requestBody)
|
||||
.then(result => {
|
||||
setKubernetesObjects(result);
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { KubernetesAuthProvider } from './types';
|
||||
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
|
||||
export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider {
|
||||
authProvider: OAuthApi;
|
||||
@@ -26,8 +26,8 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider {
|
||||
}
|
||||
|
||||
async decorateRequestBodyForAuth(
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<AuthRequestBody> {
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<KubernetesRequestBody> {
|
||||
const googleAuthToken: string = await this.authProvider.getAccessToken(
|
||||
'https://www.googleapis.com/auth/cloud-platform',
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types';
|
||||
import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider';
|
||||
import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider';
|
||||
@@ -40,8 +40,8 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
|
||||
|
||||
async decorateRequestBodyForAuth(
|
||||
authProvider: string,
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<AuthRequestBody> {
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<KubernetesRequestBody> {
|
||||
const kubernetesAuthProvider:
|
||||
| KubernetesAuthProvider
|
||||
| undefined = this.kubernetesAuthProviderMap.get(authProvider);
|
||||
|
||||
+3
-3
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
import { KubernetesAuthProvider } from './types';
|
||||
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
|
||||
export class ServiceAccountKubernetesAuthProvider
|
||||
implements KubernetesAuthProvider {
|
||||
async decorateRequestBodyForAuth(
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<AuthRequestBody> {
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<KubernetesRequestBody> {
|
||||
// No-op, with service account for auth, cluster config/details should already have serviceAccountToken
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend';
|
||||
|
||||
export interface KubernetesAuthProvider {
|
||||
decorateRequestBodyForAuth(
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<AuthRequestBody>;
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<KubernetesRequestBody>;
|
||||
}
|
||||
|
||||
export const kubernetesAuthProvidersApiRef = createApiRef<
|
||||
@@ -33,6 +33,6 @@ export const kubernetesAuthProvidersApiRef = createApiRef<
|
||||
export interface KubernetesAuthProvidersApi {
|
||||
decorateRequestBodyForAuth(
|
||||
authProvider: string,
|
||||
requestBody: AuthRequestBody,
|
||||
): Promise<AuthRequestBody>;
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<KubernetesRequestBody>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user