Skip to content

Commit

Permalink
Use chrome containers instead of chrome build repo.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hyperkid123 committed Jun 5, 2024
1 parent 69399c1 commit 10f4e1f
Show file tree
Hide file tree
Showing 9 changed files with 583 additions and 171 deletions.
491 changes: 327 additions & 164 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions packages/config-utils/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ export type ProxyOptions = {
useAgent?: boolean;
useDevBuild?: boolean;
localApps?: string;
/**
* Used to block running chrome from build repos.
* Chrome should be running from container from now on.
*/
blockLegacyChrome?: boolean;
};

const proxy = ({
Expand All @@ -140,6 +145,7 @@ const proxy = ({
useAgent = true,
useDevBuild = true,
localApps = process.env.LOCAL_APPS,
blockLegacyChrome = false,
}: ProxyOptions) => {
const proxy: ProxyConfigItem[] = [];
const majorEnv = env.split('-')[0];
Expand Down Expand Up @@ -369,14 +375,14 @@ const proxy = ({
* Allow serving chrome assets
* This will allow running chrome as a host application
*/
if (!isChrome) {
if (localChrome || (!blockLegacyChrome && !isChrome)) {
let chromePath = localChrome;
if (standaloneConfig) {
if (standaloneConfig.chrome) {
chromePath = resolvePath(reposDir, standaloneConfig.chrome.path);
keycloakUri = standaloneConfig.chrome.keycloakUri;
}
} else if (!localChrome && useProxy) {
} else if (!blockLegacyChrome && !localChrome && useProxy) {
const chromeConfig = typeof defaultServices.chrome === 'function' ? defaultServices.chrome({}) : defaultServices.chrome;

const chromeEnv = useDevBuild ? (env.includes('-beta') ? 'dev-beta' : 'dev-stable') : env;
Expand Down
8 changes: 6 additions & 2 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
},
"homepage": "https://github.com/RedHatInsights/frontend-components/tree/master/packages/config#readme",
"scripts": {
"build": "tsc"
"build": "tsc",
"watch": "tsc -w"
},
"dependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.8",
Expand Down Expand Up @@ -61,15 +62,18 @@
"source-map-loader": "^3.0.1",
"stream-browserify": "^3.0.0",
"swc-loader": "^0.2.3",
"tree-kill": "^1.2.2",
"ts-loader": "^9.4.4",
"url": "^0.11.0",
"util": "^0.12.4",
"wait-on": "^7.2.0",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "4.15.1",
"yargs": "^17.6.2"
},
"devDependencies": {
"@types/inquirer": "^9.0.3"
"@types/inquirer": "^9.0.3",
"@types/wait-on": "^5.3.4"
}
}
69 changes: 66 additions & 3 deletions packages/config/src/bin/dev-script.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
/* eslint-disable no-console */
import inquirer from 'inquirer';
const { resolve } = require('path');
const { spawn } = require('child_process');
import { spawn } from 'child_process';
import treeKill from 'tree-kill';
import { getWebpackConfigPath, validateFECConfig } from './common';
import serveChrome from './serve-chrome';
import { LogType, fecLogger } from '@redhat-cloud-services/frontend-components-config-utilities';
const DEFAULT_CHROME_SERVER_PORT = 9998;

async function setEnv(cwd: string) {
return inquirer
Expand Down Expand Up @@ -34,18 +38,30 @@ async function devScript(
clouddotEnv?: string;
uiEnv?: string;
port?: string;
chromeServerPort?: number | string;
},
cwd: string
) {
try {
let localChrome = false;
let fecConfig: any = {};
let configPath;
let chromeHost;
if (typeof argv.webpackConfig !== 'undefined') {
configPath = getWebpackConfigPath(argv.webpackConfig, cwd);
if (typeof argv.chromeServerPort !== 'undefined') {
process.env.FEC_CHROME_PORT = `${argv.chromeServerPort}`;
} else {
fecLogger(LogType.info, `No chrome server port provided, using default port ${DEFAULT_CHROME_SERVER_PORT}`);
process.env.FEC_CHROME_PORT = `${DEFAULT_CHROME_SERVER_PORT}`;
}
} else {
// validate the FEC config only if a custom webpack config is not provided
validateFECConfig(cwd);
fecConfig = require(process.env.FEC_CONFIG_PATH!);
localChrome = fecConfig.localChrome;
process.env.FEC_CHROME_PORT = fecConfig.chromePort ?? DEFAULT_CHROME_SERVER_PORT;
chromeHost = fecConfig.chromeHost;
configPath = resolve(__dirname, './dev.webpack.config.js');
}

Expand Down Expand Up @@ -74,7 +90,54 @@ async function devScript(
process.env.PORT = argv.port;
}

spawn(`npm exec -- webpack serve -c ${configPath}`, [], {
let webpackProcess: ReturnType<typeof spawn> | undefined = undefined;
let interceptorProcess: ReturnType<typeof spawn> | undefined = undefined;

if (!chromeHost) {
chromeHost = `${process.env.CLOUDOT_ENV === 'prod' ? 'prod' : 'stage'}.foo.redhat.com`;
}

process.env.FEC_CHROME_HOST = chromeHost;

// ignore chrome server if a localChrome is provided
if (!localChrome) {
// get the directory if the build
// hsa to require here after all FEC env variables are set
const devConfig = require('./dev.webpack.config');
const outputPath = devConfig.output?.path;
// start chrome frontend server
try {
const handleServerError = (error: Error) => {
fecLogger(LogType.error, error);
if (webpackProcess?.pid) {
treeKill(webpackProcess.pid, 'SIGKILL');
}

if (interceptorProcess?.pid) {
treeKill(interceptorProcess.pid, 'SIGKILL');
}
process.exit(1);
};

await serveChrome(
outputPath,
chromeHost,
handleServerError,
process.env.CLOUDOT_ENV === 'prod',
argv.uiEnv === 'beta',
parseInt(process.env.FEC_CHROME_PORT!)
).catch((error) => {
fecLogger(LogType.error, 'Chrome server stopped!');
handleServerError(error);
});
} catch (error) {
fecLogger(LogType.error, 'Unable to start local Chrome UI server!');
fecLogger(LogType.error, error);
process.exit(1);
}
}

webpackProcess = spawn(`npm exec -- webpack serve -c ${configPath}`, [], {
stdio: [process.stdout, process.stdout, process.stdout],
cwd,
shell: true,
Expand All @@ -83,7 +146,7 @@ async function devScript(
const interceptorServerPath = resolve(__dirname, './csc-interceptor-server.js');
const interceptorServerArgs = [interceptorServerPath];
// Ensure ipv4 DNS is hit first. Currently there are issues with IPV4
spawn('NODE_OPTIONS=--dns-result-order=ipv4first node', interceptorServerArgs, {
interceptorProcess = spawn('NODE_OPTIONS=--dns-result-order=ipv4first node', interceptorServerArgs, {
stdio: [process.stdout, process.stdout, process.stdout],
cwd,
shell: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/config/src/bin/dev.webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const { plugins: externalPlugins = [], interceptChromeConfig, routes, ...externa

const internalProxyRoutes: { [endpoint: string]: ProxyConfigArrayItem } = {
...routes,
'/apps/chrome': {
target: `http://${process.env.FEC_CHROME_HOST}:${process.env.FEC_CHROME_PORT}`,
},
...(interceptChromeConfig === true
? {
'/api/chrome-service/v1/static': {
Expand All @@ -60,6 +63,7 @@ const { config: webpackConfig, plugins } = config({
deployment: isBeta ? 'beta/apps' : 'apps',
env: `${process.env.CLOUDOT_ENV}-${isBeta === true ? 'beta' : 'stable'}` as FrontendEnv,
rootFolder: process.env.FEC_ROOT_DIR || process.cwd(),
blockLegacyChrome: true,
});
plugins.push(...commonPlugins, ...externalPlugins);

Expand Down
166 changes: 166 additions & 0 deletions packages/config/src/bin/serve-chrome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import axios from 'axios';
import path from 'path';
import fs from 'fs';
import { execSync, spawn } from 'child_process';
import { load } from 'js-yaml';
import { LogType, fecLogger } from '@redhat-cloud-services/frontend-components-config-utilities';
import waitOn from 'wait-on';

const REPO_OWNER = 'RedHatInsights';
const REPO_NAME = 'insights-chrome';
const CONTAINER_PORT = 8000;
const CONTAINER_NAME = 'fec-chrome-local';
const IMAGE_REPO = 'quay.io/cloudservices/insights-chrome-frontend';

type ContainerRuntime = 'docker' | 'podman';
let execBin: ContainerRuntime | undefined = undefined;

const chromeDeploymentConfig = {
repo: 'git@gitlab.cee.redhat.com:service/app-interface.git',
deployFile: 'data/services/insights/frontend-base/deploy.yml',
tarTarget: path.resolve(__dirname, 'chrome-deploy/'),
};

const checkoutCommand = `git archive --remote=${chromeDeploymentConfig.repo} HEAD ${chromeDeploymentConfig.deployFile} | tar xvf - -C ${chromeDeploymentConfig.tarTarget}`;

function checkContainerRuntime(): ContainerRuntime {
try {
if (execSync('which podman').toString().trim().length > 0) {
return 'podman';
}
if (execSync('which docker').toString().trim().length > 0) {
return 'docker';
}
} catch (error) {
throw new Error('No container runtime found');
}

throw new Error('No container runtime found');
}

async function getLatestCommits(): Promise<string> {
const { data } = await axios.get(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/commits`, {
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
params: {
per_page: 1,
},
});
if (data.length === 0) {
throw new Error('No commits for chrome found!');
}

const { sha } = data[0];
return sha.substring(0, 7);
}

type Deployment = {
resourceTemplates: {
name: string;
targets: {
ref: string;
namespace: {
$ref: string;
};
}[];
}[];
};

async function getProdRelease() {
try {
if (!fs.existsSync(chromeDeploymentConfig.tarTarget)) {
fs.mkdirSync(chromeDeploymentConfig.tarTarget);
}
execSync(checkoutCommand, {
stdio: 'inherit',
});

const deployment = load(
fs.readFileSync(path.join(chromeDeploymentConfig.tarTarget, chromeDeploymentConfig.deployFile), {
encoding: 'utf-8',
})
) as Deployment;
const chromeProd = deployment.resourceTemplates
.find((template) => {
return template.name === 'insights-chrome';
})
?.targets.find((target) => {
return target.namespace.$ref.includes('prod-frontends');
});
if (!chromeProd) {
throw new Error('Unable to find chrome prod deployment configuration.');
}

return chromeProd.ref.substring(0, 7);
} catch (error) {
fecLogger(LogType.error, error);
fecLogger(LogType.warn, 'Unable to find chrome prod deployment! Falling back to latest image.');
return getLatestCommits();
}
}

function pullImage(tag: string) {
execSync(`${execBin} pull ${IMAGE_REPO}:${tag}`, {
stdio: 'inherit',
});
}

async function startServer(tag: string, serverPort: number) {
return new Promise<void>((resolve, reject) => {
try {
execSync(`${execBin} stop ${CONTAINER_NAME}`, {
stdio: 'inherit',
});
execSync(`${execBin} rm ${CONTAINER_NAME}`, {
stdio: 'inherit',
});
} catch (error) {
fecLogger(LogType.info, 'No existing chrome container found');
}
const runCommand = `${execBin} run -p ${serverPort}:${CONTAINER_PORT} --name ${CONTAINER_NAME} ${IMAGE_REPO}:${tag}`;
const child = spawn(runCommand, [], {
stdio: 'ignore',
shell: true,
});
child.stderr?.on('data', (data) => {
reject(data.toString());
});
child.on('exit', () => {
return reject(`Chrome server stopped unexpectedly! The server port ${serverPort} is already in use!`);
});
});
}

function copyIndex(path: string, isPreview = false) {
const copyCommand = `${execBin} cp ${CONTAINER_NAME}:/opt/app-root/src/build/${isPreview ? 'preview' : 'stable'}/index.html ${path}`;
execSync(copyCommand, {
stdio: 'inherit',
});
}

async function serveChrome(distPath: string, host: string, onError: (error: Error) => void, isProd = false, isPreview = false, serverPort = 9999) {
if (!distPath) {
throw new Error('No distPath provided! Provide an absolute path to the UI dist directory.');
}
fecLogger(LogType.info, 'Starting chrome server...');
execBin = checkContainerRuntime();
let tag: string;
if (isProd) {
tag = await getProdRelease();
} else {
tag = await getLatestCommits();
}
pullImage(tag);
startServer(tag, serverPort).catch((error) => {
onError(error);
process.exit(1);
});
await waitOn({
resources: [`http://${host}:${serverPort}`],
});
copyIndex(distPath, isPreview);
}

export default serveChrome;
3 changes: 3 additions & 0 deletions packages/config/src/lib/createConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface CreateConfigOptions extends CommonConfigOptions {
cacheConfig?: Partial<CacheOptions>;
nodeModulesDirectories?: string[];
resolve?: ResolveOptions;
blockLegacyChrome?: boolean;
}

export const createConfig = ({
Expand Down Expand Up @@ -91,6 +92,7 @@ export const createConfig = ({
resolve = {},
// additional node_modules dirs for searchIgnoredStyles, usefull in monorepo scenario
nodeModulesDirectories = [],
blockLegacyChrome,
}: CreateConfigOptions): Configuration => {
if (typeof _unstableHotReload !== 'undefined') {
fecLogger(LogType.warn, `The _unstableHotReload option in shared webpack config is deprecated. Use hotReload config instead.`);
Expand Down Expand Up @@ -295,6 +297,7 @@ export const createConfig = ({
bounceProd,
useAgent,
useDevBuild,
blockLegacyChrome,
}),
},
};
Expand Down
2 changes: 2 additions & 0 deletions packages/config/src/lib/fec.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface FECConfiguration
interceptChromeConfig?: boolean;
moduleFederation: Omit<FederatedModulesConfig, 'root' | 'separateRuntime'>;
debug?: boolean;
chromeHost?: string;
chromePort?: number;
}

export default FECConfiguration;
Loading

0 comments on commit 10f4e1f

Please sign in to comment.