From 2e9c37d51e5942fc2fd9d356859a125aab698e87 Mon Sep 17 00:00:00 2001 From: Jason Madigan Date: Tue, 8 Oct 2024 12:15:53 +0100 Subject: [PATCH] dynamic topology Signed-off-by: Jason Madigan --- Dockerfile | 7 ++- README.md | 19 +++---- entrypoint.sh | 12 ++++ install.yaml | 10 ++++ src/components/PolicyTopologyPage.tsx | 80 ++++++++++++++++++++++----- 5 files changed, 101 insertions(+), 27 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 729f1c4..46a8192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/nodejs-20 AS build +FROM registry.access.redhat.com/ubi9/nodejs-20 AS build USER root RUN command -v yarn || npm i -g yarn @@ -9,6 +9,9 @@ RUN yarn install && yarn build FROM registry.access.redhat.com/ubi9/nginx-124:1-10 COPY --from=build /usr/src/app/dist /usr/share/nginx/html + +COPY entrypoint.sh /usr/share/nginx/html/entrypoint.sh + USER 1001 -ENTRYPOINT ["nginx", "-g", "daemon off;"] +ENTRYPOINT ["/usr/share/nginx/html/entrypoint.sh"] diff --git a/README.md b/README.md index d30040c..412cf72 100644 --- a/README.md +++ b/README.md @@ -64,21 +64,16 @@ push it to an image registry. 1. Build the image: - ```sh - docker build -t quay.io/kuadrant/console-plugin:latest . - ``` +```bash +docker buildx create --use +docker buildx build --platform linux/amd64,linux/arm64 -t quay.io/kuadrant/console-plugin:latest --push . +``` 2. Run the image: - ```sh - docker run -it --rm -d -p 9001:80 quay.io/kuadrant/console-plugin:latest - ``` - -3. Push the image: - - ```sh - docker push quay.io/kuadrant/console-plugin:latest - ``` +```bash +docker run -it --rm -d -p 9001:80 quay.io/kuadrant/console-plugin:latest +``` NOTE: If you have a Mac with Apple silicon, you will need to add the flag `--platform=linux/amd64` when building the image to target the correct platform diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..0a4ba33 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Inject topology ConfigMap location +cat < /tmp/config.js +window.kuadrant_config = { + TOPOLOGY_CONFIGMAP_NAME: '${TOPOLOGY_CONFIGMAP_NAME}', + TOPOLOGY_CONFIGMAP_NAMESPACE: '${TOPOLOGY_CONFIGMAP_NAMESPACE}' +}; +EOF + +# Start Nginx +nginx -g "daemon off;" diff --git a/install.yaml b/install.yaml index 7d0ee3f..61f1521 100644 --- a/install.yaml +++ b/install.yaml @@ -36,6 +36,11 @@ spec: - containerPort: 9443 protocol: TCP imagePullPolicy: Always + env: + - name: TOPOLOGY_CONFIGMAP_NAME + value: topology + - name: TOPOLOGY_CONFIGMAP_NAMESPACE + value: kuadrant-system volumeMounts: - name: plugin-serving-cert readOnly: true @@ -93,6 +98,11 @@ data: location / { root /usr/share/nginx/html; } + + # Serve config.js from /tmp + location /config.js { + root /tmp; + } } } --- diff --git a/src/components/PolicyTopologyPage.tsx b/src/components/PolicyTopologyPage.tsx index ad31b09..5db4d1e 100644 --- a/src/components/PolicyTopologyPage.tsx +++ b/src/components/PolicyTopologyPage.tsx @@ -41,6 +41,39 @@ import * as dot from 'graphlib-dot'; import { kindToAbbr } from '../utils/modelUtils'; import './kuadrant.css'; +// Fetch the config.js file dynamically at runtime +// Normally served from /api/plugins/kuadrant-console/config.js +const fetchConfig = async () => { + const defaultConfig = { + TOPOLOGY_CONFIGMAP_NAME: 'topology', + TOPOLOGY_CONFIGMAP_NAMESPACE: 'kuadrant-system', + }; + + try { + const response = await fetch('/api/plugins/kuadrant-console/config.js'); + if (!response.ok) { + if (response.status === 404) { + console.warn('config.js not found (running locally perhaps). Falling back to defaults.'); + } else { + throw new Error(`Failed to fetch config.js: ${response.statusText}`); + } + return defaultConfig; // Fallback on 404 + } + + const script = await response.text(); + + const configScript = document.createElement('script'); + configScript.innerHTML = script; + document.head.appendChild(configScript); + + return (window as any).kuadrant_config || defaultConfig; + } catch (error) { + console.error('Error loading config.js:', error); + return defaultConfig; + } +}; + + // TODO: need a generic way to fetch latest versions of resources based on kind + group const resourceGVKMapping: { [key: string]: { group: string; version: string; kind: string } } = { Gateway: { group: 'gateway.networking.k8s.io', version: 'v1', kind: 'Gateway' }, @@ -294,18 +327,36 @@ const customComponentFactory = (kind: ModelKind, type: string) => { }; const PolicyTopologyPage: React.FC = () => { + const [config, setConfig] = React.useState(null); const [parseError, setParseError] = React.useState(null); - // Watch the ConfigMap named "topology" in the "kuadrant-system" namespace - // TODO: lookup instance of `Kuadrant` and read topology from same NS. - const [configMap, loaded, loadError] = useK8sWatchResource({ - groupVersionKind: { - version: 'v1', - kind: 'ConfigMap', - }, - name: 'topology', - namespace: 'kuadrant-system', - }); + // Fetch the configuration on mount + React.useEffect(() => { + const loadConfig = async () => { + try { + const configData = await fetchConfig(); + setConfig(configData); + } catch (error) { + console.error('Error loading config.js:', error); + setParseError('Failed to load configuration.'); + } + }; + loadConfig(); + }, []); + + // Watch the ConfigMap named "topology" in the namespace provided by the config.js + const [configMap, loaded, loadError] = useK8sWatchResource( + config + ? { + groupVersionKind: { + version: 'v1', + kind: 'ConfigMap', + }, + name: config.TOPOLOGY_CONFIGMAP_NAME, + namespace: config.TOPOLOGY_CONFIGMAP_NAMESPACE, + } + : null, // Only watch if config is loaded + ); const controllerRef = React.useRef(null); @@ -324,7 +375,6 @@ const PolicyTopologyPage: React.FC = () => { const visualization = new Visualization(); visualization.registerLayoutFactory(customLayoutFactory); visualization.registerComponentFactory(customComponentFactory); - visualization.setRenderConstraint(false); visualization.fromModel(initialModel, false); controllerRef.current = visualization; } @@ -337,7 +387,7 @@ const PolicyTopologyPage: React.FC = () => { // Handle data updates React.useEffect(() => { - if (loaded && !loadError) { + if (loaded && !loadError && configMap) { const dotString = configMap.data?.topology || ''; if (dotString) { try { @@ -371,6 +421,10 @@ const PolicyTopologyPage: React.FC = () => { // Memoize the controller const controller = controllerRef.current; + if (!config) { + return
Loading configuration...
; + } + return ( <> @@ -392,7 +446,7 @@ const PolicyTopologyPage: React.FC = () => { {!loaded ? ( -
Loading...
+
Loading topology...
) : loadError ? (
Error loading topology: {loadError.message}
) : parseError ? (