/** * Copyright 2023 Kapeta Inc. * SPDX-License-Identifier: BUSL-1.1 */ import Router from 'express-promise-router'; import { Response } from 'express'; import { Resource } from '@kapeta/schemas'; import { proxyRestRequest } from './types/rest'; import { proxyHttpRequest } from './types/web'; import { ProxyRequestHandler } from '../types'; import { stringBody, StringBodyRequest } from '../middleware/stringBody'; import { serviceManager } from '../serviceManager'; import { clusterService } from '../clusterService'; import { assetManager } from '../assetManager'; import _ from 'lodash'; const router = Router(); /** * @var {{[key:string]:ProxyRequestHandler}} */ const TYPE_HANDLERS: { [p: string]: ProxyRequestHandler } = { rest: proxyRestRequest, http: proxyHttpRequest, }; function getResource(resources: Resource[], resourceName: string) { return resources.find((resource) => { return resource.metadata.name.toLowerCase() === resourceName.toLowerCase(); }); } router.use('/:systemId/:consumerInstanceId/:consumerResourceName', stringBody); router.all( '/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async (req: StringBodyRequest, res: Response) => { try { let typeHandler = TYPE_HANDLERS[req.params.type.toLowerCase()]; if (!typeHandler) { // Default to http typeHandler = TYPE_HANDLERS['http']; } const plan = await assetManager.getPlan(req.params.systemId); // We can find the connection by the consumer information alone since // only 1 provider can be connected to a consumer resource at a time const connection = plan.spec.connections.find((connection) => { return ( connection.consumer.blockId.toLowerCase() === req.params.consumerInstanceId.toLowerCase() && connection.consumer.resourceName.toLowerCase() === req.params.consumerResourceName.toLowerCase() ); }); if (!connection) { res.status(401).send({ error: `No connection found for consumer "${req.params.consumerInstanceId}::${req.params.consumerResourceName}"`, }); return; } const consumerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => { return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase(); }); if (!consumerBlockInstance) { res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` }); return; } const consumerBlockAsset = await assetManager.getAsset(consumerBlockInstance.block.ref); if (!consumerBlockAsset) { res.status(401).send({ error: `Block asset not found "${consumerBlockInstance.block.ref}` }); return; } const consumerResource = getResource( consumerBlockAsset.data.spec.consumers, req.params.consumerResourceName ); if (!consumerResource) { res.status(401).send({ error: `Block resource not found "${req.params.consumerInstanceId}::${req.params.consumerResourceName}`, }); return; } const basePath = clusterService.getProxyPath( req.params.systemId, req.params.consumerInstanceId, req.params.consumerResourceName, req.params.type ); const providerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => { return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase(); }); if (!providerBlockInstance) { res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` }); return; } const providerBlockAsset = await assetManager.getAsset(providerBlockInstance.block.ref); if (!providerBlockAsset) { res.status(401).send({ error: `Block asset not found "${providerBlockInstance.block.ref}` }); return; } const providerResource = getResource( providerBlockAsset.data.spec.providers, connection.provider.resourceName ); if (!providerResource) { res.status(401).send({ error: `Block resource not found "${connection.provider.blockId}::${connection.provider.resourceName}`, }); return; } //Get target address let address = await serviceManager.getProviderAddress( req.params.systemId, connection.provider.blockId, req.params.type ); while (address.endsWith('/')) { address = address.substring(0, address.length - 1); } // Get the path the consumer requested. Note that this might not match the path the // destination is expecting so we need to identify the method that is being called and // identify the destination path from the connection. const consumerPath = req.originalUrl.substring(basePath.length - 1); typeHandler(req, res, { address, connection, consumerPath, consumerResource, consumerBlockAsset, providerResource, providerBlockAsset, }); } catch (err: any) { console.warn('Failed to process proxy request', err); res.status(400).send({ error: err.message }); } } ); export default router;