Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RHOAIENG-10311] Backend service endpoints for connection types #3048

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading