UNPKG

@stolostron/multicluster-sdk

Version:

Provides extensions and APIs that dynamic plugins can use to leverage multicluster capabilities provided by Red Hat Advanced Cluster Management.

206 lines 10.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useFleetK8sWatchResource = void 0; /* Copyright Contributors to the Open Cluster Management project */ const useHubClusterName_1 = require("./useHubClusterName"); const dynamic_plugin_sdk_1 = require("@openshift-console/dynamic-plugin-sdk"); const useFleetK8sAPIPath_1 = require("./useFleetK8sAPIPath"); const useIsFleetAvailable_1 = require("./useIsFleetAvailable"); const react_1 = require("react"); const apiRequests_1 = require("./apiRequests"); const fleetK8sWatchResource_1 = require("../internal/fleetK8sWatchResource"); const requirements_1 = require("./utils/requirements"); const useDeepCompareMemoize_1 = require("../internal/hooks/useDeepCompareMemoize"); /** * A hook for watching Kubernetes resources with support for multi-cluster environments. * It is equivalent to the [`useK8sWatchResource`](https://github.com/openshift/console/blob/main/frontend/packages/console-dynamic-plugin-sdk/docs/api.md#usek8swatchresource) * hook from the [OpenShift Console Dynamic Plugin SDK](https://www.npmjs.com/package/@openshift-console/dynamic-plugin-sdk) * but allows you to retrieve data from any cluster managed by Red Hat Advanced Cluster Management. * * It automatically detects the hub cluster and handles resource watching on both hub * and remote clusters using WebSocket connections for real-time updates. * * @param initResource - The resource to watch. Can be null to disable the watch. * @param initResource.cluster - The managed cluster on which the resource resides; null for the hub cluster * @returns A tuple containing the watched resource data, a boolean indicating if the data is loaded, * and any error that occurred. The hook returns live-updating data. * * @example * ```typescript * // Watch pods on a remote cluster * const [pods, loaded, error] = useFleetK8sWatchResource({ * groupVersionKind: { version: 'v1', kind: 'Pod' }, * isList: true, * cluster: 'remote-cluster', * namespace: 'default' * }) * * // Watch a specific deployment on hub cluster * const [deployment, loaded, error] = useFleetK8sWatchResource({ * groupVersionKind: { group: 'apps', version: 'v1', kind: 'Deployment' }, * name: 'my-app', * namespace: 'default' * }) * ``` */ const useFleetK8sWatchResource = (initResource) => { var _a, _b, _c; const [hubClusterName, hubClusterNameLoaded] = (0, useHubClusterName_1.useHubClusterName)(); const memoizedResource = (0, useDeepCompareMemoize_1.useDeepCompareMemoize)(initResource, true); const clusterSpecified = (memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.cluster) !== undefined; const shouldWaitForHubClusterName = clusterSpecified && !hubClusterNameLoaded; const _d = memoizedResource !== null && memoizedResource !== void 0 ? memoizedResource : {}, { cluster } = _d, resource = __rest(_d, ["cluster"]); const nullResource = !memoizedResource || !(resource === null || resource === void 0 ? void 0 : resource.groupVersionKind); const selector = memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.selector; const { isList, groupVersionKind, namespace, name } = resource !== null && resource !== void 0 ? resource : {}; const [model] = (0, dynamic_plugin_sdk_1.useK8sModel)(groupVersionKind); const [backendAPIPath, backendPathLoaded] = (0, useFleetK8sAPIPath_1.useFleetK8sAPIPath)(cluster); const isFleetAvailable = (0, useIsFleetAvailable_1.useIsFleetAvailable)(); const isRemoteCluster = !!cluster && cluster !== hubClusterName; const useFleet = isFleetAvailable && !shouldWaitForHubClusterName && isRemoteCluster; const useFallback = !isFleetAvailable || (!shouldWaitForHubClusterName && !isRemoteCluster); const noCachedValue = (0, react_1.useMemo)(() => (isList ? [] : undefined), [isList]); const requestPath = (0, react_1.useMemo)(() => backendPathLoaded && model ? (0, apiRequests_1.buildResourceURL)({ model, ns: namespace, name, cluster, queryParams: { fieldSelector: memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.fieldSelector, labelSelector: (0, requirements_1.selectorToString)((memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.selector) || {}), }, basePath: backendAPIPath, }) : '', [ model, namespace, name, cluster, backendPathLoaded, backendAPIPath, memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.selector, memoizedResource === null || memoizedResource === void 0 ? void 0 : memoizedResource.fieldSelector, ]); const [data, setData] = (0, react_1.useState)((_a = fleetK8sWatchResource_1.fleetResourceCache[requestPath]) !== null && _a !== void 0 ? _a : noCachedValue); const [loaded, setLoaded] = (0, react_1.useState)(!!fleetK8sWatchResource_1.fleetResourceCache[requestPath]); const [error, setError] = (0, react_1.useState)(undefined); (0, react_1.useEffect)(() => { let socket; const fetchData = () => __awaiter(void 0, void 0, void 0, function* () { var _a; setError(undefined); if (!useFleet || nullResource) { setData(noCachedValue); setLoaded(false); return; } if (!backendPathLoaded || fleetK8sWatchResource_1.fleetResourceCache[requestPath]) { return; } try { const data = (yield (0, dynamic_plugin_sdk_1.consoleFetchJSON)(requestPath, 'GET')); const processedData = (isList ? data.items.map((i) => (Object.assign({ cluster }, i))) : Object.assign({ cluster }, data)); fleetK8sWatchResource_1.fleetResourceCache[requestPath] = processedData; const watchQuery = { ns: namespace, cluster, }; if (name) { watchQuery.fieldSelector = `metadata.name=${name}`; } if (selector) { watchQuery.labelSelector = selector; } if (isList) { watchQuery.resourceVersion = (_a = data === null || data === void 0 ? void 0 : data.metadata) === null || _a === void 0 ? void 0 : _a.resourceVersion; } const socketKey = (0, fleetK8sWatchResource_1.getCacheKey)({ model, cluster, namespace, name }); const cachedSocket = fleetK8sWatchResource_1.fleetSocketCache[socketKey]; if (!cachedSocket || cachedSocket.readyState !== WebSocket.OPEN) { socket = (0, apiRequests_1.fleetWatch)(model, watchQuery, backendAPIPath); fleetK8sWatchResource_1.fleetSocketCache[socketKey] = socket; socket.onmessage = (event) => { try { (0, fleetK8sWatchResource_1.handleWebsocketEvent)(event, requestPath, setData, isList, fleetK8sWatchResource_1.fleetResourceCache, cluster); } catch (e) { console.error('Failed to parse WebSocket message', e); } }; socket.onclose = () => { delete fleetK8sWatchResource_1.fleetSocketCache[socketKey]; }; } else { socket = cachedSocket; } setData(processedData); } catch (err) { setError(err); } finally { setLoaded(true); } }); fetchData(); // Cleanup function to close the WebSocket if it was created by this effect return () => { if (socket && socket.readyState === WebSocket.OPEN) { socket.close(); } }; }, [ cluster, isList, requestPath, name, namespace, nullResource, noCachedValue, useFleet, backendPathLoaded, model, backendAPIPath, selector, ]); const fallbackResult = (0, dynamic_plugin_sdk_1.useK8sWatchResource)(useFallback ? resource : null); if (!nullResource && useFallback) { return fallbackResult; } else if (!nullResource && useFleet) { return [ (_c = (_b = fleetK8sWatchResource_1.fleetResourceCache[requestPath]) !== null && _b !== void 0 ? _b : data) !== null && _c !== void 0 ? _c : noCachedValue, fleetK8sWatchResource_1.fleetResourceCache[requestPath] ? true : loaded, error, ]; } else { return [undefined, false, undefined]; } }; exports.useFleetK8sWatchResource = useFleetK8sWatchResource; //# sourceMappingURL=useFleetK8sWatchResource.js.map