Skip to content

Commit

Permalink
[RHOAIENG-10311] Backend service endpoints for connection types (#3048)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 authored Aug 2, 2024
1 parent 45b6306 commit 73e69e5
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 6 deletions.
190 changes: 190 additions & 0 deletions backend/src/routes/api/connection-types/connectionTypeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { PatchUtils, V1ConfigMap } from '@kubernetes/client-node';
import { KnownLabels, KubeFastifyInstance, RecursivePartial } from '../../../types';
import { getNamespaces } from '../../../utils/notebookUtils';
import { errorHandler } from '../../../utils';

const isConnectionTypeConfigMap = (configMap: V1ConfigMap): boolean =>
configMap.metadata.labels &&
configMap.metadata.labels[KnownLabels.DASHBOARD_RESOURCE] === 'true' &&
configMap.metadata.labels[KnownLabels.CONNECTION_TYPE] === 'true';

const isExistingConnectionType = async (
fastify: KubeFastifyInstance,
name: string,
): Promise<boolean> => {
const coreV1Api = fastify.kube.coreV1Api;
const { dashboardNamespace } = getNamespaces(fastify);

const response = await coreV1Api.readNamespacedConfigMap(name, dashboardNamespace);
return isConnectionTypeConfigMap(response.body);
};

export const listConnectionTypes = async (fastify: KubeFastifyInstance): Promise<V1ConfigMap[]> => {
const { dashboardNamespace } = getNamespaces(fastify);
const coreV1Api = fastify.kube.coreV1Api;
const connectionTypes: V1ConfigMap[] = [];

let _continue: string = undefined;
let remainingItemCount = 1;
try {
while (remainingItemCount) {
const response = await coreV1Api.listNamespacedConfigMap(
dashboardNamespace,
undefined,
undefined,
_continue,
undefined,
`${KnownLabels.DASHBOARD_RESOURCE} = true, ${KnownLabels.CONNECTION_TYPE} = true`,
);
connectionTypes.push(...(response?.body?.items ?? []));
remainingItemCount = response?.body.metadata?.remainingItemCount;
_continue = response?.body.metadata?._continue;
}
return connectionTypes;
} catch (e) {
fastify.log.error(`Error fetching configmaps for connection types: `, e);
throw new Error(`Failed to list connection types: ${errorHandler(e)}.`);
}
};

export const getConnectionType = async (
fastify: KubeFastifyInstance,
name: string,
): Promise<V1ConfigMap> => {
const { dashboardNamespace } = getNamespaces(fastify);
const coreV1Api = fastify.kube.coreV1Api;
try {
const response = await coreV1Api.readNamespacedConfigMap(name, dashboardNamespace);
if (!isConnectionTypeConfigMap(response.body)) {
throw new Error(`object is not a connection type.`);
}
return response.body;
} catch (e) {
fastify.log.error(`Error fetching connection type: `, e);
throw new Error(`Failed to get connection type: ${errorHandler(e)}.`);
}
};

export const createConnectionType = async (
fastify: KubeFastifyInstance,
connectionType: V1ConfigMap,
): Promise<{ success: boolean; error: string }> => {
const coreV1Api = fastify.kube.coreV1Api;
const { dashboardNamespace } = getNamespaces(fastify);

if (!isConnectionTypeConfigMap(connectionType)) {
const error = 'Unable to add connection type, incorrect labels.';
fastify.log.error(error);
return { success: false, error };
}

try {
await coreV1Api.createNamespacedConfigMap(dashboardNamespace, connectionType);
return { success: true, error: '' };
} catch (e) {
const error = `Unable to add connection type: ${errorHandler(e)}.`;
fastify.log.error(error);
return { success: false, error };
}
};

export const updateConnectionType = async (
fastify: KubeFastifyInstance,
name: string,
connectionType: V1ConfigMap,
): Promise<{ success: boolean; error: string }> => {
const coreV1Api = fastify.kube.coreV1Api;
const { dashboardNamespace } = getNamespaces(fastify);

if (!isConnectionTypeConfigMap(connectionType)) {
const error = 'Unable to add connection type, incorrect labels.';
fastify.log.error(error);
return { success: false, error };
}

try {
const validConnectionType = await isExistingConnectionType(fastify, name);
if (!validConnectionType) {
const error = `Unable to update connection type, object is not a connection type`;
fastify.log.error(error);
return { success: false, error };
}

await coreV1Api.replaceNamespacedConfigMap(name, dashboardNamespace, connectionType);
return { success: true, error: '' };
} catch (e) {
const error = `Unable to update connection type: ${errorHandler(e)}.`;
fastify.log.error(error);
return { success: false, error };
}
};

export const patchConnectionType = async (
fastify: KubeFastifyInstance,
name: string,
partialConfigMap: RecursivePartial<V1ConfigMap>,
): Promise<{ success: boolean; error: string }> => {
const coreV1Api = fastify.kube.coreV1Api;
const { dashboardNamespace } = getNamespaces(fastify);

if (
(partialConfigMap.metadata.labels?.[KnownLabels.DASHBOARD_RESOURCE] &&
partialConfigMap.metadata.labels[KnownLabels.DASHBOARD_RESOURCE] !== 'true') ||
(partialConfigMap.metadata.labels?.[KnownLabels.CONNECTION_TYPE] &&
partialConfigMap.metadata.labels[KnownLabels.CONNECTION_TYPE] !== 'true')
) {
const error = 'Unable to update connection type, incorrect labels.';
fastify.log.error(error);
return { success: false, error };
}

try {
const validConnectionType = await isExistingConnectionType(fastify, name);
if (!validConnectionType) {
const error = `Unable to update connection type, object is not a connection type`;
fastify.log.error(error);
return { success: false, error };
}
const options = {
headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH },
};

await coreV1Api.patchNamespacedConfigMap(
name,
dashboardNamespace,
partialConfigMap,
undefined,
undefined,
undefined,
undefined,
options,
);
return { success: true, error: '' };
} catch (e) {
const error = `Unable to update connection type: ${errorHandler(e)}.`;
fastify.log.error(error);
return { success: false, error };
}
};

export const deleteConnectionType = async (
fastify: KubeFastifyInstance,
name: string,
): Promise<{ success: boolean; error: string }> => {
const { dashboardNamespace } = getNamespaces(fastify);
const coreV1Api = fastify.kube.coreV1Api;
try {
const validConnectionType = await isExistingConnectionType(fastify, name);
if (!validConnectionType) {
const error = `Unable to delete connection type, object is not a connection type`;
fastify.log.error(error);
return { success: false, error };
}
await coreV1Api.deleteNamespacedConfigMap(name, dashboardNamespace);
return { success: true, error: '' };
} catch (e) {
const error = `Unable to delete connection type: ${errorHandler(e)}.`;
fastify.log.error(error);
return { success: false, error };
}
};
91 changes: 91 additions & 0 deletions backend/src/routes/api/connection-types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { V1ConfigMap } from '@kubernetes/client-node';
import { FastifyReply, FastifyRequest } from 'fastify';
import { KubeFastifyInstance, RecursivePartial } from '../../../types';
import { secureAdminRoute } from '../../../utils/route-security';
import {
getConnectionType,
listConnectionTypes,
createConnectionType,
updateConnectionType,
patchConnectionType,
deleteConnectionType,
} from './connectionTypeUtils';

module.exports = async (fastify: KubeFastifyInstance) => {
fastify.get(
'/',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) =>
listConnectionTypes(fastify)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);

fastify.get(
'/:name',
secureAdminRoute(fastify)(
async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) =>
getConnectionType(fastify, request.params.name)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);

fastify.post(
'/',
secureAdminRoute(fastify)(
async (request: FastifyRequest<{ Body: V1ConfigMap }>, reply: FastifyReply) =>
createConnectionType(fastify, request.body)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);

fastify.put(
'/:name',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{ Params: { name: string }; Body: V1ConfigMap }>,
reply: FastifyReply,
) =>
updateConnectionType(fastify, request.params.name, request.body)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);

fastify.patch(
'/:name',
secureAdminRoute(fastify)(
async (
request: FastifyRequest<{ Params: { name: string }; Body: RecursivePartial<V1ConfigMap> }>,
reply: FastifyReply,
) =>
patchConnectionType(fastify, request.params.name, request.body)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);

fastify.delete(
'/:name',
secureAdminRoute(fastify)(
async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) =>
deleteConnectionType(fastify, request.params.name)
.then((res) => res)
.catch((res) => {
reply.send(res);
}),
),
);
};
1 change: 1 addition & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,7 @@ export enum KnownLabels {
PROJECT_SHARING = 'opendatahub.io/project-sharing',
MODEL_SERVING_PROJECT = 'modelmesh-enabled',
DATA_CONNECTION_AWS = 'opendatahub.io/managed',
CONNECTION_TYPE = 'opendatahub.io/connection-type',
}

type ComponentNames =
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ describe('utils', () => {
it('should serialize / deserialize connection type fields', () => {
const ct = mockConnectionTypeConfigMapObj({});
const configMap = toConnectionTypeConfigMap(ct);
expect(typeof configMap.data.fields).toBe('string');
expect(typeof configMap.data?.fields).toBe('string');
expect(ct).toEqual(toConnectionTypeConfigMapObj(toConnectionTypeConfigMap(ct)));
});

it('should serialize / deserialize connection type with missing fields', () => {
const ct = mockConnectionTypeConfigMapObj({ fields: undefined });
const configMap = toConnectionTypeConfigMap(ct);
expect(configMap.data.fields).toBeUndefined();
expect(configMap.data?.fields).toBeUndefined();
expect(ct).toEqual(toConnectionTypeConfigMapObj(configMap));
});
});
4 changes: 2 additions & 2 deletions frontend/src/concepts/connectionTypes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ export type ConnectionTypeConfigMap = K8sResourceCommon & {
'opendatahub.io/connection-type': 'true';
};
};
data: {
data?: {
// JSON of type ConnectionTypeField
fields?: string;
};
};

export type ConnectionTypeConfigMapObj = Omit<ConnectionTypeConfigMap, 'data'> & {
data: {
data?: {
fields?: ConnectionTypeField[];
};
};
8 changes: 6 additions & 2 deletions frontend/src/concepts/connectionTypes/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ export const toConnectionTypeConfigMapObj = (
configMap: ConnectionTypeConfigMap,
): ConnectionTypeConfigMapObj => ({
...configMap,
data: { fields: configMap.data.fields ? JSON.parse(configMap.data.fields) : undefined },
data: configMap.data
? { fields: configMap.data.fields ? JSON.parse(configMap.data.fields) : undefined }
: undefined,
});

export const toConnectionTypeConfigMap = (
obj: ConnectionTypeConfigMapObj,
): ConnectionTypeConfigMap => ({
...obj,
data: { fields: obj.data.fields ? JSON.stringify(obj.data.fields) : undefined },
data: obj.data
? { fields: obj.data.fields ? JSON.stringify(obj.data.fields) : undefined }
: undefined,
});
Loading

0 comments on commit 73e69e5

Please sign in to comment.