Skip to content

Commit

Permalink
fix(proxy): split standalone proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Hyperkid123 committed Jun 24, 2021
1 parent 4c22945 commit 467b6d4
Show file tree
Hide file tree
Showing 8 changed files with 784 additions and 305 deletions.
453 changes: 363 additions & 90 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion packages/config-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@redhat-cloud-services/frontend-components-config-utilities",
"version": "1.3.0",
"version": "1.3.1",
"description": "Utilities for shared config used in RedHat Cloud Services project.",
"main": "index.js",
"publishConfig": {
Expand All @@ -16,6 +16,9 @@
"url": "https://github.com/RedHatInsights/frontend-components/issues"
},
"homepage": "https://github.com/RedHatInsights/frontend-components#readme",
"dependencies": {
"nodesi": "^1.17.0"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
Expand Down
356 changes: 169 additions & 187 deletions packages/config-utils/proxy.js
Original file line number Diff line number Diff line change
@@ -1,212 +1,194 @@
/* eslint-disable no-console */
// Webpack proxy and express config for `useProxy: true` or `standalone: true`
const { execSync } = require('child_process');
const path = require('path');
const standaloneProxy = require('./standalone/proxy');
const { readFileSync } = require('fs');
const { sync } = require('glob');
const ESI = require('nodesi');
const cookieTransform = require('./cookieTransform');
const router = require('./standalone/helpers/router');
const { getConfig, isGitUrl, getExposedPort, resolvePath } = require('./standalone/helpers/index');
const { checkoutRepo } = require('./standalone/helpers/checkout');
const { startService, stopService } = require('./standalone/startService');
const { NET } = require('./standalone/helpers');
const defaultServices = require('./standalone/services/default');
const { registerChrome } = require('./standalone/services/default/chrome');

const defaultReposDir = path.join(__dirname, 'repos');

module.exports = ({
env = 'ci-beta',
customProxy = [],
routes,
routesPath,
useProxy,
standalone,
port,
reposDir = defaultReposDir,
localChrome,
appUrl = [],
publicPath,
proxyVerbose,
useCloud = false
}) => {
const proxy = [];
const registry = [];
const majorEnv = env.split('-')[0];
const minorEnv = majorEnv === 'prod' ? '' : `${majorEnv}.`;
const target = env === 'prod-stable'
? `https://${useCloud ? 'cloud' : 'console'}.redhat.com/`
: `https://${minorEnv}${useCloud ? 'cloud' : 'console'}.redhat.com/`;
if (!Array.isArray(appUrl)) {
appUrl = [ appUrl ];
const transformUrl = require('./router');

module.exports = (options) => {
const {
betaEnv = 'ci',
customProxy = [],
routes,
routesPath,
standalone,
port = 1337,
localChrome,
appUrl = [],
publicPath,
proxyVerbose,
rootFolder,
useCloud = false,
https = true,
exactUrl,
disableFallback
} = options;

if (standalone) {
return standaloneProxy(options);
}

appUrl.push(publicPath);
let appUrls = appUrl;
if (appUrls && !Array.isArray(appUrls)) {
appUrls = [ appUrls ];
}

if (routesPath) {
routes = require(routesPath);
let comparator = (path, url) => path.includes(url);
if (exactUrl) {
comparator = (path, url) => path === url;
}

if (routes) {
routes = routes.routes || routes;
console.log('Making proxy from SPANDX routes');
proxy.push(
...Object.entries(routes || {}).map(([ route, redirect ]) => {
const currTarget = redirect.host || redirect;
delete redirect.host;
return {
context: (path) => path.includes(route),
target: currTarget === 'PORTAL_BACKEND_MARKER' ? target : currTarget,
secure: false,
changeOrigin: true,
autoRewrite: true,
ws: true,
onProxyReq: cookieTransform,
...(currTarget === 'PORTAL_BACKEND_MARKER' && { router }),
...typeof redirect === 'object' ? redirect : {}
};
})
);
// disable self signed CERT checks for esi
if (appUrl) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
}

if (customProxy) {
proxy.push(...customProxy);
const target = betaEnv === 'prod' ? `https://${useCloud ? 'cloud' : 'console'}.redhat.com/` : `https://${betaEnv}.${useCloud ? 'cloud' : 'console'}.redhat.com/`;

let proxyRoutes = routes;

if (routesPath) {
proxyRoutes = require(routesPath);
}

let standaloneConfig;
if (standalone) {
standaloneConfig = getConfig(standalone, localChrome, env, port);
// Create network for services.
execSync(`docker network inspect ${NET} >/dev/null 2>&1 || docker network create ${NET}`);

// Clone repos
// If we manage the repos it's okay to overwrite the contents
const overwrite = reposDir === defaultReposDir;
// Need to use for loop for `await`
for (const [ projName, proj ] of Object.entries(standaloneConfig)) {
const { services, path, assets, onProxyReq, keycloakUri, register, target, ...rest } = proj;
if (typeof register === 'function') {
registry.push(register);
}

if (isGitUrl(path)) {
// Add typical branch if not included
if (!path.includes('#')) {
proj.path = `${path}#${env}`;
}
if (proxyRoutes) {
proxyRoutes = proxyRoutes.routes || proxyRoutes;
}

proj.path = checkoutRepo({ repo: proj.path, reposDir, overwrite });
}
const esi = appUrl && new ESI({
allowedHosts: [ /^https:\/\/.*cloud.redhat.com$/ ],
baseUrl: `${https ? 'https' : 'http'}://${betaEnv}.foo.redhat.com:${port}`,
cache: false,
onError: (src, error) => {
console.error(
`An error occurred while resolving an ESI tag`
);
console.error(error);
}
});

Object.keys(assets || []).forEach(key => {
if (isGitUrl(assets[key])) {
assets[key] = checkoutRepo({ repo: assets[key], reposDir, overwrite });
}
});

// Resolve functions that depend on env, port, or assets
if (typeof services === 'function') {
proj.services = services({ env, port, assets });
}

// Start standalone services.
const serviceNames = [];
for (let [ subServiceName, subService ] of Object.entries(proj.services || {})) {
const name = [ projName, subServiceName ].join('_');
startService(standaloneConfig, name, subService);
serviceNames.push(name);
const port = getExposedPort(subService.args);
console.log('Container', name, 'listening', port ? 'on' : '', port || '');
}

process.on('SIGINT', () => {
console.log();
serviceNames.forEach(stopService);
process.exit();
});

if (target) {
proxy.push({
secure: false,
changeOrigin: true,
onProxyReq: cookieTransform,
target,
...rest
});
}
proxyVerbose && proxyRoutes && console.log(`Using proxy routes: ${JSON.stringify(proxyRoutes, null, 2)}`);

const isNotCustomContext = (path) => {
if (Object.keys(proxyRoutes || {}).some((route) => path.includes(route))) {
return false;
}
}

if (useProxy) {
// Catch-all
proxy.push({
secure: false,
changeOrigin: true,
autoRewrite: true,
context: url => {
const shouldProxy = !appUrl.find(u => url.startsWith(u));
if (shouldProxy) {
console.log('proxy', url);
return true;
}
return customProxy.length > 0 ? customProxy.reduce((acc, curr) => acc && !curr.context(path), true) : true;
};

return false;
},
target,
router: router(target, useCloud)
});
const isChrome = (path) => localChrome && path.includes(process.env.BETA ? '/beta/apps/chrome/' : '/apps/chrome/');
const pathInCustomUrl = (path) => appUrls && appUrls.some((customPath) => comparator(path, customPath));
const removeHashQuery = (path) => path.replace(/(\?|#).*/, '');

if (appUrls && proxyVerbose) {
console.log('\n\nServing HTML on: ', appUrls.join(', '));
console.log('Exact URL: ', exactUrl ? 'true' : 'false', '\n\n');
}

const router = transformUrl(target, useCloud);

return {
...(proxy.length > 0 && { proxy }),
onListening(server) {
if (useProxy || standaloneConfig) {
const host = useProxy ? `${majorEnv}.foo.redhat.com` : 'localhost';
const origin = `http${server.options.https ? 's' : ''}://${host}:${server.options.port}`;
console.log('App should run on:');

console.log('\u001b[34m'); // Use same webpack-dev-server blue
if (appUrl.length > 0) {
appUrl.forEach(url => console.log(` - ${origin}${url}`));
} else {
console.log(` - ${origin}`);
contentBase: `${rootFolder || ''}/dist`,
index: `${rootFolder || ''}/dist/index.html`,
host: `${betaEnv}.foo.redhat.com`,
port,
https,
inline: true,
disableHostCheck: true,
historyApiFallback: true,
writeToDisk: true,
publicPath,
proxy: [
{
context: (path) => isNotCustomContext(path) && !pathInCustomUrl(removeHashQuery(path)) && !isChrome(path),
target,
secure: false,
changeOrigin: true,
autoRewrite: true,
router
},
...(appUrl ? [{
context: path => pathInCustomUrl(removeHashQuery(path)),
target,
secure: false,
changeOrigin: true,
autoRewrite: true,
selfHandleResponse: true,
// serve index.html from local and replace ESI tags for chrome
onProxyReq: async (_proxyReq, req, res) => {
const fileName = removeHashQuery(req.url.split('/').pop()) || 'index.html';
let localPath = sync(`${rootFolder}/dist/${fileName}`);

if (!localPath[0] && !disableFallback) {
proxyVerbose && console.log(`page ${fileName} not found, fallback to index.html`);
localPath = sync(`${rootFolder}/dist/index.html`);
}

proxyVerbose && console.log('serving locally', req.url, '>', fileName, '--->', localPath[0], '\n\n');

if (localPath[0]) {
const localFile = readFileSync(localPath[0]);
const newData = await esi.process(localFile.toString());
res.end(newData);
}
}

console.log('\u001b[0m');
}
},
before(app, server, compiler) {
app.enable('strict routing'); // trailing slashes are mean
let chromePath = localChrome;
if (standaloneConfig) {
chromePath = resolvePath(reposDir, standaloneConfig.chrome.path);
} else if (!localChrome && useProxy) {
if (typeof defaultServices.chrome === 'function') {
defaultServices.chrome = defaultServices.chrome({});
}] : []),
...(localChrome ? [{
context: (path) => isChrome(path),
target,
secure: false,
changeOrigin: true,
autoRewrite: true,
ws: true,
// When running chrome locally, we have to redirect all requests and serve files locally
selfHandleResponse: true,
onProxyReq: (_proxyReq, req, res) => {
let newPath = req.url;
const fileType = req.url.match(/\.([a-z]|\d)+$/);

newPath = newPath.replace(process.env.BETA ? '/beta/apps/chrome/' : '/apps/chrome/', ''); //remove chrome URL

let localPath = sync(`${localChrome}${newPath}`); // try to find it locally

// if the file does not exist locally and it is a JS/CSS, let's try to remove hash
if (localPath.length === 0 && fileType && (fileType[0] === '.js' || fileType[0] === '.css')) {
newPath = newPath
.replace(/\.([a-z]|\d)+\.[a-z]+$/, '**') //removeHash and add wilcard
.concat(fileType[0]); // add file extension back

localPath = sync(`${localChrome}${newPath}`);
}

proxyVerbose && console.log('serving locally', req.url, '--->', newPath, '------>', localPath[0], '\n\n');

// if there is a local file with the same name, it's served
if (localPath[0]) {
res.sendFile(localPath[0]);
}
}

chromePath = checkoutRepo({
repo: `${defaultServices.chrome.path}#${env}`,
reposDir,
overwrite: true
});
}

if (chromePath) {
registerChrome({
app,
chromePath,
keycloakUri: (standaloneConfig && standaloneConfig.chrome) ? standaloneConfig.chrome.keycloakUri : null,
https: Boolean(server.options.https),
proxyVerbose
});
}

registry.forEach(cb => cb({
app,
server,
compiler,
config: standaloneConfig
}));
}
}] : []),
...proxyRoutes ? Object.entries(proxyRoutes).map(([ route, redirect ]) => {
const currTarget = redirect.host || redirect;
delete redirect.host;
return {
context: (path) => path.includes(route),
target: currTarget === 'PORTAL_BACKEND_MARKER' ? target : currTarget,
secure: false,
changeOrigin: true,
autoRewrite: true,
ws: true,
onProxyReq: (...args) => {
cookieTransform(...args);
},
...(currTarget === 'PORTAL_BACKEND_MARKER' && { router }),
...typeof redirect === 'object' ? redirect : {}
};
}) : [],
...customProxy
]
};
};

Loading

0 comments on commit 467b6d4

Please sign in to comment.