Skip to content

Commit 10b91b9

Browse files
committed
feat: integrate model registry into the deployment
1 parent 0909a54 commit 10b91b9

File tree

15 files changed

+336
-19
lines changed

15 files changed

+336
-19
lines changed

frontend/config/moduleFederation.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ const readModuleFederationConfigFromPackages = () => {
4242
};
4343

4444
const getModuleFederationConfig = () => {
45+
// Disable module federation for Cypress tests
46+
if (process.env.CYPRESS_TESTS === 'true') {
47+
return [];
48+
}
49+
4550
if (process.env.MODULE_FEDERATION_CONFIG) {
4651
try {
4752
return JSON.parse(process.env.MODULE_FEDERATION_CONFIG);

frontend/config/webpack.common.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ const path = require('path');
33
const HtmlWebpackPlugin = require('html-webpack-plugin');
44
const CopyPlugin = require('copy-webpack-plugin');
55
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
6+
const webpack = require('webpack');
67
const { setupWebpackDotenvFilesForEnv } = require('./dotenv');
78
const GenerateExtensionsPlugin = require('./generateExtensionsPlugin');
9+
const { moduleFederationConfig, moduleFederationPlugins } = require('./moduleFederation');
810

911
const RELATIVE_DIRNAME = process.env._ODH_RELATIVE_DIRNAME;
1012
const IS_PROJECT_ROOT_DIR = process.env._ODH_IS_PROJECT_ROOT_DIR;
@@ -232,6 +234,10 @@ module.exports = (env) => ({
232234
new MonacoWebpackPlugin({
233235
languages: ['yaml'],
234236
}),
237+
new webpack.EnvironmentPlugin({
238+
MF_CONFIG: JSON.stringify(moduleFederationConfig),
239+
}),
240+
...moduleFederationPlugins,
235241
],
236242
resolve: {
237243
extensions: ['.js', '.ts', '.tsx', '.jsx'],

frontend/config/webpack.dev.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable @typescript-eslint/restrict-template-expressions */
22
const { execSync } = require('child_process');
33
const path = require('path');
4-
const webpack = require('webpack');
54
const { merge } = require('webpack-merge');
65
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
76
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
@@ -12,7 +11,7 @@ const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE });
1211

1312
setupDotenvFilesForEnv({ env: 'development' });
1413
const webpackCommon = require('./webpack.common.js');
15-
const { moduleFederationConfig, moduleFederationPlugins } = require('./moduleFederation');
14+
const { moduleFederationConfig } = require('./moduleFederation');
1615

1716
const RELATIVE_DIRNAME = process.env._ODH_RELATIVE_DIRNAME;
1817
const IS_PROJECT_ROOT_DIR = process.env._ODH_IS_PROJECT_ROOT_DIR;
@@ -166,10 +165,6 @@ module.exports = smp.wrap(
166165
plugins: [
167166
new ForkTsCheckerWebpackPlugin(),
168167
new ReactRefreshWebpackPlugin({ overlay: false }),
169-
new webpack.EnvironmentPlugin({
170-
MF_CONFIG: JSON.stringify(moduleFederationConfig),
171-
}),
172-
...moduleFederationPlugins,
173168
],
174169
},
175170
),

frontend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@
5454
"cypress:run:mock:coverage": "rimraf src/__tests__/cypress/coverage src/__tests__/cypress/.nyc_output && CY_COVERAGE=true npm run cypress:run:mock -- {@}",
5555
"cypress:open:record": "CY_RECORD=1 npm run cypress:open",
5656
"cypress:run:record": "CY_RECORD=1 npm run cypress:run && npm run cypress:format",
57-
"cypress:server:build": "ODH_DIST_DIR=./public-cypress POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 WS_HOSTNAME=localhost:9002 npm run build",
58-
"cypress:server:build:coverage": "COVERAGE=true npm run cypress:server:build",
57+
"cypress:server:build": "ODH_DIST_DIR=./public-cypress POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 WS_HOSTNAME=localhost:9002 CYPRESS_TESTS=true npm run build",
58+
"cypress:server:build:coverage": "COVERAGE=true CYPRESS_TESTS=true npm run cypress:server:build",
5959
"cypress:server": "serve ./public-cypress -p 9001 -s -L",
60-
"cypress:server:dev": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 ODH_PORT=9001 WS_HOSTNAME=localhost:9002 npm run start:dev",
60+
"cypress:server:dev": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 ODH_PORT=9001 WS_HOSTNAME=localhost:9002 CYPRESS_TESTS=true npm run start:dev",
6161
"cypress:format": "prettier --write src/__tests__/**/*.snap.json",
6262
"test:cypress:e2e": "cd src/__tests__/cypress && npx cypress run --env grepTags=\"${CY_TEST_TAGS}\",skipTags=\"${CY_SKIP_TAGS}\" --browser chrome",
6363
"test:cypress:e2e:open": "cd src/__tests__/cypress && npx cypress open --env grepTags=\"${CY_TEST_TAGS}\",skipTags=\"${CY_SKIP_TAGS}\""
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Source code for the repos
2+
ARG UI_SOURCE_CODE=./upstream/frontend
3+
ARG BFF_SOURCE_CODE=./upstream/bff
4+
5+
# Set the base images for the build stages
6+
ARG NODE_BASE_IMAGE=node:20
7+
ARG GOLANG_BASE_IMAGE=golang:1.24.3
8+
ARG DISTROLESS_BASE_IMAGE=gcr.io/distroless/static:nonroot
9+
10+
# UI build stage
11+
FROM ${NODE_BASE_IMAGE} AS ui-builder
12+
13+
ARG UI_SOURCE_CODE
14+
ARG DEPLOYMENT_MODE
15+
ARG PLATFORM_MODE
16+
17+
WORKDIR /usr/src/app
18+
19+
# Copy the source code to the container
20+
COPY ${UI_SOURCE_CODE} /usr/src/app
21+
22+
# List files for debugging
23+
RUN ls -la /usr/src/app
24+
25+
# Install the dependencies and build
26+
RUN npm cache clean --force
27+
RUN npm ci --omit=optional
28+
RUN npm run build:prod
29+
30+
# BFF build stage
31+
FROM ${GOLANG_BASE_IMAGE} AS bff-builder
32+
33+
ARG BFF_SOURCE_CODE
34+
35+
ARG TARGETOS
36+
ARG TARGETARCH
37+
38+
WORKDIR /usr/src/app
39+
40+
# Copy the Go Modules manifests
41+
COPY ${BFF_SOURCE_CODE}/go.mod ${BFF_SOURCE_CODE}/go.sum ./
42+
43+
# Download dependencies
44+
RUN go mod download
45+
46+
# Copy the go source files
47+
COPY ${BFF_SOURCE_CODE}/cmd/ cmd/
48+
COPY ${BFF_SOURCE_CODE}/internal/ internal/
49+
50+
# Build the Go application
51+
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o bff ./cmd
52+
53+
# Final stage
54+
# Use distroless as minimal base image to package the application binary
55+
FROM ${DISTROLESS_BASE_IMAGE}
56+
WORKDIR /
57+
COPY --from=bff-builder /usr/src/app/bff ./
58+
COPY --from=ui-builder /usr/src/app/dist ./static/
59+
USER 65532:65532
60+
61+
# Expose port 8080
62+
EXPOSE 8080
63+
64+
ENTRYPOINT ["/bff"]

frontend/packages/model-registry/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
"port": 9000
2929
},
3030
"service": {
31-
"name": "model-registry-ui-service",
32-
"port": 8080
31+
"name": "odh-dashboard",
32+
"port": 8043
3333
}
3434
}
3535
}

frontend/src/__tests__/cypress/cypress/support/e2e.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import '@cypress/code-coverage/support';
2020
import 'cypress-mochawesome-reporter/register';
2121
import 'cypress-plugin-steps';
2222
import './commands';
23+
import '#~/__tests__/cypress/cypress/utils/moduleFederationMock';
2324
import { asProjectAdminUser } from '#~/__tests__/cypress/cypress/utils/mockUsers';
2425
import { mockDscStatus } from '#~/__mocks__/mockDscStatus';
2526
import { addCommands as webSocketsAddCommands } from './websockets';
@@ -325,6 +326,10 @@ beforeEach(function beforeEachHook(this: Mocha.Context) {
325326
if (Cypress.env('MOCK')) {
326327
// Fallback: return 404 for all API requests.
327328
cy.intercept({ pathname: '/api/**' }, { statusCode: 404 });
329+
330+
// Return 404 for all module federation requests.
331+
cy.intercept({ pathname: '/_mf/**' }, { statusCode: 404 });
332+
328333
// Default intercepts.
329334
cy.interceptOdh('GET /api/dsc/status', mockDscStatus({}));
330335
asProjectAdminUser();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Module Federation Mock Utilities for Cypress Tests
3+
*
4+
* This file provides utilities to mock module federation behavior
5+
* during Cypress tests to prevent runtime errors.
6+
*/
7+
8+
/**
9+
* Mock remote entry content that provides a basic module federation interface
10+
*/
11+
export const createMockRemoteEntry = (moduleName: string): string => `
12+
// Mock module federation remote entry for ${moduleName}
13+
(function() {
14+
if (!window.__FEDERATION__) {
15+
window.__FEDERATION__ = {};
16+
}
17+
18+
if (!window.__FEDERATION__.moduleMap) {
19+
window.__FEDERATION__.moduleMap = {};
20+
}
21+
22+
// Mock the module exports
23+
window.__FEDERATION__.moduleMap['${moduleName}'] = {
24+
get: function(scope) {
25+
if (scope === 'extensions') {
26+
return Promise.resolve({
27+
default: []
28+
});
29+
}
30+
return Promise.resolve({});
31+
},
32+
init: function() {
33+
return Promise.resolve();
34+
}
35+
};
36+
37+
// Export for ES modules compatibility
38+
if (typeof module !== 'undefined' && module.exports) {
39+
module.exports = window.__FEDERATION__.moduleMap['${moduleName}'];
40+
}
41+
})();
42+
`;
43+
44+
/**
45+
* Cypress command to set up module federation mocks
46+
*/
47+
export const setupModuleFederationMocks = (modules: string[] = ['modelRegistry']): void => {
48+
modules.forEach((moduleName) => {
49+
// Mock the remote entry file
50+
cy.intercept(
51+
{ pathname: `/_mf/${moduleName}/remoteEntry.js` },
52+
{
53+
statusCode: 200,
54+
headers: { 'content-type': 'application/javascript' },
55+
body: createMockRemoteEntry(moduleName),
56+
},
57+
);
58+
59+
// Mock the extensions endpoint
60+
cy.intercept(
61+
{ url: `**/_mf/${moduleName}/**/extensions` },
62+
{
63+
statusCode: 200,
64+
headers: { 'content-type': 'application/javascript' },
65+
body: 'export default [];',
66+
},
67+
);
68+
});
69+
70+
// Mock any other module federation paths with 404
71+
cy.intercept({ pathname: '/_mf/**' }, { statusCode: 404 });
72+
};
73+
74+
/**
75+
* Add the command to Cypress
76+
*/
77+
// eslint-disable-next-line @typescript-eslint/no-namespace
78+
declare global {
79+
// eslint-disable-next-line @typescript-eslint/no-namespace
80+
namespace Cypress {
81+
interface Chainable {
82+
setupModuleFederationMocks: (modules?: string[]) => Chainable<void>;
83+
}
84+
}
85+
}
86+
87+
Cypress.Commands.add('setupModuleFederationMocks', setupModuleFederationMocks);

frontend/src/plugins/useAppExtensions.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,24 @@ const initRemotes = (remotes: MFConfig[]) => {
2424
};
2525

2626
const loadModuleExtensions = (moduleName: string): Promise<Record<string, Extension[]>> =>
27-
loadRemote<{ default: Extension[] }>(`${moduleName}/extensions`).then((result) => ({
28-
[moduleName]: result ? result.default : [],
29-
}));
27+
loadRemote<{ default: Extension[] }>(`${moduleName}/extensions`)
28+
.then((result) => ({
29+
[moduleName]: result ? result.default : [],
30+
}))
31+
.catch((error) => {
32+
// eslint-disable-next-line no-console
33+
console.warn(`Failed to load module extensions for ${moduleName}:`, error);
34+
return { [moduleName]: [] };
35+
});
3036

3137
export const useAppExtensions = (): [Record<string, Extension[]>, boolean] => {
3238
const [appExtensions, setAppExtensions] = React.useState<Record<string, Extension[]>>({});
3339
const [loaded, setLoaded] = React.useState(!MF_CONFIG);
3440

3541
React.useEffect(() => {
3642
if (MF_CONFIG) {
37-
const remotes: MFConfig[] = JSON.parse(MF_CONFIG);
3843
try {
44+
const remotes: MFConfig[] = JSON.parse(MF_CONFIG);
3945
if (remotes.length > 0) {
4046
initRemotes(remotes);
4147
allSettledPromises(remotes.map((r) => loadModuleExtensions(r.name)))
@@ -46,13 +52,17 @@ export const useAppExtensions = (): [Record<string, Extension[]>, boolean] => {
4652
);
4753
}
4854
})
55+
.catch((error) => {
56+
// eslint-disable-next-line no-console
57+
console.warn('Error loading module federation extensions:', error);
58+
})
4959
.finally(() => setLoaded(true));
5060
} else {
5161
setLoaded(true);
5262
}
5363
} catch (error) {
5464
// eslint-disable-next-line no-console
55-
console.error('Error parsing module federation config:', error);
65+
console.warn('Error with module federation setup:', error);
5666
setLoaded(true);
5767
}
5868
}

manifests/core-bases/base/service.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ spec:
88
selector:
99
deployment: odh-dashboard
1010
ports:
11-
- protocol: TCP
12-
targetPort: 8443
13-
port: 8443
11+
- name: dashboard-ui
12+
protocol: TCP
13+
port: 8443
14+
targetPort: 8443

0 commit comments

Comments
 (0)